1/*	$NetBSD: fsdb.c,v 1.2 1995/10/08 23:18:10 thorpej Exp $	*/
2
3/*
4 *  Copyright (c) 1995 John T. Kohl
5 *  All rights reserved.
6 *
7 *  Redistribution and use in source and binary forms, with or without
8 *  modification, are permitted provided that the following conditions
9 *  are met:
10 *  1. Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 *  2. Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 *  3. The name of the author may not be used to endorse or promote products
16 *     derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#ifndef lint
32static const char rcsid[] =
33  "$FreeBSD$";
34#endif /* not lint */
35
36#include <sys/param.h>
37#include <ctype.h>
38#include <err.h>
39#include <grp.h>
40#include <histedit.h>
41#include <pwd.h>
42#include <stdint.h>
43#include <string.h>
44#include <time.h>
45#include <timeconv.h>
46
47#include <ufs/ufs/dinode.h>
48#include <ufs/ufs/dir.h>
49#include <ufs/ffs/fs.h>
50
51#include "fsdb.h"
52#include "fsck.h"
53
54static void usage(void) __dead2;
55int cmdloop(void);
56static int compare_blk32(uint32_t *wantedblk, uint32_t curblk);
57static int compare_blk64(uint64_t *wantedblk, uint64_t curblk);
58static int founddatablk(uint64_t blk);
59static int find_blks32(uint32_t *buf, int size, uint32_t *blknum);
60static int find_blks64(uint64_t *buf, int size, uint64_t *blknum);
61static int find_indirblks32(uint32_t blk, int ind_level, uint32_t *blknum);
62static int find_indirblks64(uint64_t blk, int ind_level, uint64_t *blknum);
63
64static void
65usage(void)
66{
67	fprintf(stderr, "usage: fsdb [-d] [-f] [-r] fsname\n");
68	exit(1);
69}
70
71int returntosingle;
72char nflag;
73
74/*
75 * We suck in lots of fsck code, and just pick & choose the stuff we want.
76 *
77 * fsreadfd is set up to read from the file system, fswritefd to write to
78 * the file system.
79 */
80int
81main(int argc, char *argv[])
82{
83	int ch, rval;
84	char *fsys = NULL;
85
86	while (-1 != (ch = getopt(argc, argv, "fdr"))) {
87		switch (ch) {
88		case 'f':
89			/* The -f option is left for historical
90			 * reasons and has no meaning.
91			 */
92			break;
93		case 'd':
94			debug++;
95			break;
96		case 'r':
97			nflag++; /* "no" in fsck, readonly for us */
98			break;
99		default:
100			usage();
101		}
102	}
103	argc -= optind;
104	argv += optind;
105	if (argc != 1)
106		usage();
107	else
108		fsys = argv[0];
109
110	sblock_init();
111	if (!setup(fsys))
112		errx(1, "cannot set up file system `%s'", fsys);
113	printf("%s file system `%s'\nLast Mounted on %s\n",
114	       nflag? "Examining": "Editing", fsys, sblock.fs_fsmnt);
115	rval = cmdloop();
116	if (!nflag) {
117		sblock.fs_clean = 0;	/* mark it dirty */
118		sbdirty();
119		ckfini(0);
120		printf("*** FILE SYSTEM MARKED DIRTY\n");
121		printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
122		printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
123	}
124	exit(rval);
125}
126
127#define CMDFUNC(func) int func(int argc, char *argv[])
128#define CMDFUNCSTART(func) int func(int argc, char *argv[])
129
130CMDFUNC(helpfn);
131CMDFUNC(focus);				/* focus on inode */
132CMDFUNC(active);			/* print active inode */
133CMDFUNC(blocks);			/* print blocks for active inode */
134CMDFUNC(focusname);			/* focus by name */
135CMDFUNC(zapi);				/* clear inode */
136CMDFUNC(uplink);			/* incr link */
137CMDFUNC(downlink);			/* decr link */
138CMDFUNC(linkcount);			/* set link count */
139CMDFUNC(quit);				/* quit */
140CMDFUNC(findblk);			/* find block */
141CMDFUNC(ls);				/* list directory */
142CMDFUNC(rm);				/* remove name */
143CMDFUNC(ln);				/* add name */
144CMDFUNC(newtype);			/* change type */
145CMDFUNC(chmode);			/* change mode */
146CMDFUNC(chlen);				/* change length */
147CMDFUNC(chaflags);			/* change flags */
148CMDFUNC(chgen);				/* change generation */
149CMDFUNC(chowner);			/* change owner */
150CMDFUNC(chgroup);			/* Change group */
151CMDFUNC(back);				/* pop back to last ino */
152CMDFUNC(chbtime);			/* Change btime */
153CMDFUNC(chmtime);			/* Change mtime */
154CMDFUNC(chctime);			/* Change ctime */
155CMDFUNC(chatime);			/* Change atime */
156CMDFUNC(chinum);			/* Change inode # of dirent */
157CMDFUNC(chname);			/* Change dirname of dirent */
158
159struct cmdtable cmds[] = {
160	{ "help", "Print out help", 1, 1, FL_RO, helpfn },
161	{ "?", "Print out help", 1, 1, FL_RO, helpfn },
162	{ "inode", "Set active inode to INUM", 2, 2, FL_RO, focus },
163	{ "clri", "Clear inode INUM", 2, 2, FL_WR, zapi },
164	{ "lookup", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
165	{ "cd", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
166	{ "back", "Go to previous active inode", 1, 1, FL_RO, back },
167	{ "active", "Print active inode", 1, 1, FL_RO, active },
168	{ "print", "Print active inode", 1, 1, FL_RO, active },
169	{ "blocks", "Print block numbers of active inode", 1, 1, FL_RO, blocks },
170	{ "uplink", "Increment link count", 1, 1, FL_WR, uplink },
171	{ "downlink", "Decrement link count", 1, 1, FL_WR, downlink },
172	{ "linkcount", "Set link count to COUNT", 2, 2, FL_WR, linkcount },
173	{ "findblk", "Find inode owning disk block(s)", 2, 33, FL_RO, findblk},
174	{ "ls", "List current inode as directory", 1, 1, FL_RO, ls },
175	{ "rm", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
176	{ "del", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
177	{ "ln", "Hardlink INO into current inode directory as NAME", 3, 3, FL_WR | FL_ST, ln },
178	{ "chinum", "Change dir entry number INDEX to INUM", 3, 3, FL_WR, chinum },
179	{ "chname", "Change dir entry number INDEX to NAME", 3, 3, FL_WR | FL_ST, chname },
180	{ "chtype", "Change type of current inode to TYPE", 2, 2, FL_WR, newtype },
181	{ "chmod", "Change mode of current inode to MODE", 2, 2, FL_WR, chmode },
182	{ "chlen", "Change length of current inode to LENGTH", 2, 2, FL_WR, chlen },
183	{ "chown", "Change owner of current inode to OWNER", 2, 2, FL_WR, chowner },
184	{ "chgrp", "Change group of current inode to GROUP", 2, 2, FL_WR, chgroup },
185	{ "chflags", "Change flags of current inode to FLAGS", 2, 2, FL_WR, chaflags },
186	{ "chgen", "Change generation number of current inode to GEN", 2, 2, FL_WR, chgen },
187	{ "btime", "Change btime of current inode to BTIME", 2, 2, FL_WR, chbtime },
188	{ "mtime", "Change mtime of current inode to MTIME", 2, 2, FL_WR, chmtime },
189	{ "ctime", "Change ctime of current inode to CTIME", 2, 2, FL_WR, chctime },
190	{ "atime", "Change atime of current inode to ATIME", 2, 2, FL_WR, chatime },
191	{ "quit", "Exit", 1, 1, FL_RO, quit },
192	{ "q", "Exit", 1, 1, FL_RO, quit },
193	{ "exit", "Exit", 1, 1, FL_RO, quit },
194	{ NULL, 0, 0, 0, 0, NULL },
195};
196
197int
198helpfn(int argc, char *argv[])
199{
200    struct cmdtable *cmdtp;
201
202    printf("Commands are:\n%-10s %5s %5s   %s\n",
203	   "command", "min args", "max args", "what");
204
205    for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
206	printf("%-10s %5u %5u   %s\n",
207		cmdtp->cmd, cmdtp->minargc-1, cmdtp->maxargc-1, cmdtp->helptxt);
208    return 0;
209}
210
211char *
212prompt(EditLine *el)
213{
214    static char pstring[64];
215    snprintf(pstring, sizeof(pstring), "fsdb (inum: %ju)> ",
216	(uintmax_t)curinum);
217    return pstring;
218}
219
220
221int
222cmdloop(void)
223{
224    char *line;
225    const char *elline;
226    int cmd_argc, rval = 0, known;
227#define scratch known
228    char **cmd_argv;
229    struct cmdtable *cmdp;
230    History *hist;
231    EditLine *elptr;
232    HistEvent he;
233
234    curinode = ginode(ROOTINO);
235    curinum = ROOTINO;
236    printactive(0);
237
238    hist = history_init();
239    history(hist, &he, H_SETSIZE, 100);	/* 100 elt history buffer */
240
241    elptr = el_init("fsdb", stdin, stdout, stderr);
242    el_set(elptr, EL_EDITOR, "emacs");
243    el_set(elptr, EL_PROMPT, prompt);
244    el_set(elptr, EL_HIST, history, hist);
245    el_source(elptr, NULL);
246
247    while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
248	if (debug)
249	    printf("command `%s'\n", elline);
250
251	history(hist, &he, H_ENTER, elline);
252
253	line = strdup(elline);
254	cmd_argv = crack(line, &cmd_argc);
255	/*
256	 * el_parse returns -1 to signal that it's not been handled
257	 * internally.
258	 */
259	if (el_parse(elptr, cmd_argc, (const char **)cmd_argv) != -1)
260	    continue;
261	if (cmd_argc) {
262	    known = 0;
263	    for (cmdp = cmds; cmdp->cmd; cmdp++) {
264		if (!strcmp(cmdp->cmd, cmd_argv[0])) {
265		    if ((cmdp->flags & FL_WR) == FL_WR && nflag)
266			warnx("`%s' requires write access", cmd_argv[0]),
267			    rval = 1;
268		    else if (cmd_argc >= cmdp->minargc &&
269			cmd_argc <= cmdp->maxargc)
270			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
271		    else if (cmd_argc >= cmdp->minargc &&
272			(cmdp->flags & FL_ST) == FL_ST) {
273			strcpy(line, elline);
274			cmd_argv = recrack(line, &cmd_argc, cmdp->maxargc);
275			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
276		    } else
277			rval = argcount(cmdp, cmd_argc, cmd_argv);
278		    known = 1;
279		    break;
280		}
281	    }
282	    if (!known)
283		warnx("unknown command `%s'", cmd_argv[0]), rval = 1;
284	} else
285	    rval = 0;
286	free(line);
287	if (rval < 0)
288	    /* user typed "quit" */
289	    return 0;
290	if (rval)
291	    warnx("rval was %d", rval);
292    }
293    el_end(elptr);
294    history_end(hist);
295    return rval;
296}
297
298union dinode *curinode;
299ino_t curinum, ocurrent;
300
301#define GETINUM(ac,inum)    inum = strtoul(argv[ac], &cp, 0); \
302    if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
303	printf("inode %ju out of range; range is [%ju,%ju]\n",		\
304	    (uintmax_t)inum, (uintmax_t)ROOTINO, (uintmax_t)maxino);	\
305	return 1; \
306    }
307
308/*
309 * Focus on given inode number
310 */
311CMDFUNCSTART(focus)
312{
313    ino_t inum;
314    char *cp;
315
316    GETINUM(1,inum);
317    curinode = ginode(inum);
318    ocurrent = curinum;
319    curinum = inum;
320    printactive(0);
321    return 0;
322}
323
324CMDFUNCSTART(back)
325{
326    curinum = ocurrent;
327    curinode = ginode(curinum);
328    printactive(0);
329    return 0;
330}
331
332CMDFUNCSTART(zapi)
333{
334    ino_t inum;
335    union dinode *dp;
336    char *cp;
337
338    GETINUM(1,inum);
339    dp = ginode(inum);
340    clearinode(dp);
341    inodirty();
342    if (curinode)			/* re-set after potential change */
343	curinode = ginode(curinum);
344    return 0;
345}
346
347CMDFUNCSTART(active)
348{
349    printactive(0);
350    return 0;
351}
352
353CMDFUNCSTART(blocks)
354{
355    printactive(1);
356    return 0;
357}
358
359CMDFUNCSTART(quit)
360{
361    return -1;
362}
363
364CMDFUNCSTART(uplink)
365{
366    if (!checkactive())
367	return 1;
368    DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) + 1);
369    printf("inode %ju link count now %d\n",
370	(uintmax_t)curinum, DIP(curinode, di_nlink));
371    inodirty();
372    return 0;
373}
374
375CMDFUNCSTART(downlink)
376{
377    if (!checkactive())
378	return 1;
379    DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) - 1);
380    printf("inode %ju link count now %d\n",
381	(uintmax_t)curinum, DIP(curinode, di_nlink));
382    inodirty();
383    return 0;
384}
385
386const char *typename[] = {
387    "unknown",
388    "fifo",
389    "char special",
390    "unregistered #3",
391    "directory",
392    "unregistered #5",
393    "blk special",
394    "unregistered #7",
395    "regular",
396    "unregistered #9",
397    "symlink",
398    "unregistered #11",
399    "socket",
400    "unregistered #13",
401    "whiteout",
402};
403
404int diroff;
405int slot;
406
407int
408scannames(struct inodesc *idesc)
409{
410	struct direct *dirp = idesc->id_dirp;
411
412	printf("slot %d off %d ino %d reclen %d: %s, `%.*s'\n",
413	       slot++, diroff, dirp->d_ino, dirp->d_reclen,
414	       typename[dirp->d_type], dirp->d_namlen, dirp->d_name);
415	diroff += dirp->d_reclen;
416	return (KEEPON);
417}
418
419CMDFUNCSTART(ls)
420{
421    struct inodesc idesc;
422    checkactivedir();			/* let it go on anyway */
423
424    slot = 0;
425    diroff = 0;
426    idesc.id_number = curinum;
427    idesc.id_func = scannames;
428    idesc.id_type = DATA;
429    idesc.id_fix = IGNORE;
430    ckinode(curinode, &idesc);
431    curinode = ginode(curinum);
432
433    return 0;
434}
435
436static int findblk_numtofind;
437static int wantedblksize;
438
439CMDFUNCSTART(findblk)
440{
441    ino_t inum, inosused;
442    uint32_t *wantedblk32;
443    uint64_t *wantedblk64;
444    struct bufarea *cgbp;
445    struct cg *cgp;
446    int c, i, is_ufs2;
447
448    wantedblksize = (argc - 1);
449    is_ufs2 = sblock.fs_magic == FS_UFS2_MAGIC;
450    ocurrent = curinum;
451
452    if (is_ufs2) {
453	wantedblk64 = calloc(wantedblksize, sizeof(uint64_t));
454	if (wantedblk64 == NULL)
455	    err(1, "malloc");
456	for (i = 1; i < argc; i++)
457	    wantedblk64[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
458    } else {
459	wantedblk32 = calloc(wantedblksize, sizeof(uint32_t));
460	if (wantedblk32 == NULL)
461	    err(1, "malloc");
462	for (i = 1; i < argc; i++)
463	    wantedblk32[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
464    }
465    findblk_numtofind = wantedblksize;
466    /*
467     * sblock.fs_ncg holds a number of cylinder groups.
468     * Iterate over all cylinder groups.
469     */
470    for (c = 0; c < sblock.fs_ncg; c++) {
471	/*
472	 * sblock.fs_ipg holds a number of inodes per cylinder group.
473	 * Calculate a highest inode number for a given cylinder group.
474	 */
475	inum = c * sblock.fs_ipg;
476	/* Read cylinder group. */
477	cgbp = cgget(c);
478	cgp = cgbp->b_un.b_cg;
479	/*
480	 * Get a highest used inode number for a given cylinder group.
481	 * For UFS1 all inodes initialized at the newfs stage.
482	 */
483	if (is_ufs2)
484	    inosused = cgp->cg_initediblk;
485	else
486	    inosused = sblock.fs_ipg;
487
488	for (; inosused > 0; inum++, inosused--) {
489	    /* Skip magic inodes: 0, WINO, ROOTINO. */
490	    if (inum < ROOTINO)
491		continue;
492	    /*
493	     * Check if the block we are looking for is just an inode block.
494	     *
495	     * ino_to_fsba() - get block containing inode from its number.
496	     * INOPB() - get a number of inodes in one disk block.
497	     */
498	    if (is_ufs2 ?
499		compare_blk64(wantedblk64, ino_to_fsba(&sblock, inum)) :
500		compare_blk32(wantedblk32, ino_to_fsba(&sblock, inum))) {
501		printf("block %llu: inode block (%ju-%ju)\n",
502		    (unsigned long long)fsbtodb(&sblock,
503			ino_to_fsba(&sblock, inum)),
504		    (uintmax_t)(inum / INOPB(&sblock)) * INOPB(&sblock),
505		    (uintmax_t)(inum / INOPB(&sblock) + 1) * INOPB(&sblock));
506		findblk_numtofind--;
507		if (findblk_numtofind == 0)
508		    goto end;
509	    }
510	    /* Get on-disk inode aka dinode. */
511	    curinum = inum;
512	    curinode = ginode(inum);
513	    /* Find IFLNK dinode with allocated data blocks. */
514	    switch (DIP(curinode, di_mode) & IFMT) {
515	    case IFDIR:
516	    case IFREG:
517		if (DIP(curinode, di_blocks) == 0)
518		    continue;
519		break;
520	    case IFLNK:
521		{
522		    uint64_t size = DIP(curinode, di_size);
523		    if (size > 0 && size < sblock.fs_maxsymlinklen &&
524			DIP(curinode, di_blocks) == 0)
525			continue;
526		    else
527			break;
528		}
529	    default:
530		continue;
531	    }
532	    /* Look through direct data blocks. */
533	    if (is_ufs2 ?
534		find_blks64(curinode->dp2.di_db, NDADDR, wantedblk64) :
535		find_blks32(curinode->dp1.di_db, NDADDR, wantedblk32))
536		goto end;
537	    for (i = 0; i < NIADDR; i++) {
538		/*
539		 * Does the block we are looking for belongs to the
540		 * indirect blocks?
541		 */
542		if (is_ufs2 ?
543		    compare_blk64(wantedblk64, curinode->dp2.di_ib[i]) :
544		    compare_blk32(wantedblk32, curinode->dp1.di_ib[i]))
545		    if (founddatablk(is_ufs2 ? curinode->dp2.di_ib[i] :
546			curinode->dp1.di_ib[i]))
547			goto end;
548		/*
549		 * Search through indirect, double and triple indirect
550		 * data blocks.
551		 */
552		if (is_ufs2 ? (curinode->dp2.di_ib[i] != 0) :
553		    (curinode->dp1.di_ib[i] != 0))
554		    if (is_ufs2 ?
555			find_indirblks64(curinode->dp2.di_ib[i], i,
556			    wantedblk64) :
557			find_indirblks32(curinode->dp1.di_ib[i], i,
558			    wantedblk32))
559			goto end;
560	    }
561	}
562    }
563end:
564    curinum = ocurrent;
565    curinode = ginode(curinum);
566    return 0;
567}
568
569static int
570compare_blk32(uint32_t *wantedblk, uint32_t curblk)
571{
572    int i;
573
574    for (i = 0; i < wantedblksize; i++) {
575	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
576	    wantedblk[i] = 0;
577	    return 1;
578	}
579    }
580    return 0;
581}
582
583static int
584compare_blk64(uint64_t *wantedblk, uint64_t curblk)
585{
586    int i;
587
588    for (i = 0; i < wantedblksize; i++) {
589	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
590	    wantedblk[i] = 0;
591	    return 1;
592	}
593    }
594    return 0;
595}
596
597static int
598founddatablk(uint64_t blk)
599{
600
601    printf("%llu: data block of inode %ju\n",
602	(unsigned long long)fsbtodb(&sblock, blk), (uintmax_t)curinum);
603    findblk_numtofind--;
604    if (findblk_numtofind == 0)
605	return 1;
606    return 0;
607}
608
609static int
610find_blks32(uint32_t *buf, int size, uint32_t *wantedblk)
611{
612    int blk;
613    for (blk = 0; blk < size; blk++) {
614	if (buf[blk] == 0)
615	    continue;
616	if (compare_blk32(wantedblk, buf[blk])) {
617	    if (founddatablk(buf[blk]))
618		return 1;
619	}
620    }
621    return 0;
622}
623
624static int
625find_indirblks32(uint32_t blk, int ind_level, uint32_t *wantedblk)
626{
627#define MAXNINDIR      (MAXBSIZE / sizeof(uint32_t))
628    uint32_t idblk[MAXNINDIR];
629    int i;
630
631    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
632    if (ind_level <= 0) {
633	if (find_blks32(idblk, sblock.fs_bsize / sizeof(uint32_t), wantedblk))
634	    return 1;
635    } else {
636	ind_level--;
637	for (i = 0; i < sblock.fs_bsize / sizeof(uint32_t); i++) {
638	    if (compare_blk32(wantedblk, idblk[i])) {
639		if (founddatablk(idblk[i]))
640		    return 1;
641	    }
642	    if (idblk[i] != 0)
643		if (find_indirblks32(idblk[i], ind_level, wantedblk))
644		    return 1;
645	}
646    }
647#undef MAXNINDIR
648    return 0;
649}
650
651static int
652find_blks64(uint64_t *buf, int size, uint64_t *wantedblk)
653{
654    int blk;
655    for (blk = 0; blk < size; blk++) {
656	if (buf[blk] == 0)
657	    continue;
658	if (compare_blk64(wantedblk, buf[blk])) {
659	    if (founddatablk(buf[blk]))
660		return 1;
661	}
662    }
663    return 0;
664}
665
666static int
667find_indirblks64(uint64_t blk, int ind_level, uint64_t *wantedblk)
668{
669#define MAXNINDIR      (MAXBSIZE / sizeof(uint64_t))
670    uint64_t idblk[MAXNINDIR];
671    int i;
672
673    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
674    if (ind_level <= 0) {
675	if (find_blks64(idblk, sblock.fs_bsize / sizeof(uint64_t), wantedblk))
676	    return 1;
677    } else {
678	ind_level--;
679	for (i = 0; i < sblock.fs_bsize / sizeof(uint64_t); i++) {
680	    if (compare_blk64(wantedblk, idblk[i])) {
681		if (founddatablk(idblk[i]))
682		    return 1;
683	    }
684	    if (idblk[i] != 0)
685		if (find_indirblks64(idblk[i], ind_level, wantedblk))
686		    return 1;
687	}
688    }
689#undef MAXNINDIR
690    return 0;
691}
692
693int findino(struct inodesc *idesc); /* from fsck */
694static int dolookup(char *name);
695
696static int
697dolookup(char *name)
698{
699    struct inodesc idesc;
700
701    if (!checkactivedir())
702	    return 0;
703    idesc.id_number = curinum;
704    idesc.id_func = findino;
705    idesc.id_name = name;
706    idesc.id_type = DATA;
707    idesc.id_fix = IGNORE;
708    if (ckinode(curinode, &idesc) & FOUND) {
709	curinum = idesc.id_parent;
710	curinode = ginode(curinum);
711	printactive(0);
712	return 1;
713    } else {
714	warnx("name `%s' not found in current inode directory", name);
715	return 0;
716    }
717}
718
719CMDFUNCSTART(focusname)
720{
721    char *p, *val;
722
723    if (!checkactive())
724	return 1;
725
726    ocurrent = curinum;
727
728    if (argv[1][0] == '/') {
729	curinum = ROOTINO;
730	curinode = ginode(ROOTINO);
731    } else {
732	if (!checkactivedir())
733	    return 1;
734    }
735    for (p = argv[1]; p != NULL;) {
736	while ((val = strsep(&p, "/")) != NULL && *val == '\0');
737	if (val) {
738	    printf("component `%s': ", val);
739	    fflush(stdout);
740	    if (!dolookup(val)) {
741		curinode = ginode(curinum);
742		return(1);
743	    }
744	}
745    }
746    return 0;
747}
748
749CMDFUNCSTART(ln)
750{
751    ino_t inum;
752    int rval;
753    char *cp;
754
755    GETINUM(1,inum);
756
757    if (!checkactivedir())
758	return 1;
759    rval = makeentry(curinum, inum, argv[2]);
760    if (rval)
761	    printf("Ino %ju entered as `%s'\n", (uintmax_t)inum, argv[2]);
762    else
763	printf("could not enter name? weird.\n");
764    curinode = ginode(curinum);
765    return rval;
766}
767
768CMDFUNCSTART(rm)
769{
770    int rval;
771
772    if (!checkactivedir())
773	return 1;
774    rval = changeino(curinum, argv[1], 0);
775    if (rval & ALTERED) {
776	printf("Name `%s' removed\n", argv[1]);
777	return 0;
778    } else {
779	printf("could not remove name ('%s')? weird.\n", argv[1]);
780	return 1;
781    }
782}
783
784long slotcount, desired;
785
786int
787chinumfunc(struct inodesc *idesc)
788{
789	struct direct *dirp = idesc->id_dirp;
790
791	if (slotcount++ == desired) {
792	    dirp->d_ino = idesc->id_parent;
793	    return STOP|ALTERED|FOUND;
794	}
795	return KEEPON;
796}
797
798CMDFUNCSTART(chinum)
799{
800    char *cp;
801    ino_t inum;
802    struct inodesc idesc;
803
804    slotcount = 0;
805    if (!checkactivedir())
806	return 1;
807    GETINUM(2,inum);
808
809    desired = strtol(argv[1], &cp, 0);
810    if (cp == argv[1] || *cp != '\0' || desired < 0) {
811	printf("invalid slot number `%s'\n", argv[1]);
812	return 1;
813    }
814
815    idesc.id_number = curinum;
816    idesc.id_func = chinumfunc;
817    idesc.id_fix = IGNORE;
818    idesc.id_type = DATA;
819    idesc.id_parent = inum;		/* XXX convenient hiding place */
820
821    if (ckinode(curinode, &idesc) & FOUND)
822	return 0;
823    else {
824	warnx("no %sth slot in current directory", argv[1]);
825	return 1;
826    }
827}
828
829int
830chnamefunc(struct inodesc *idesc)
831{
832	struct direct *dirp = idesc->id_dirp;
833	struct direct testdir;
834
835	if (slotcount++ == desired) {
836	    /* will name fit? */
837	    testdir.d_namlen = strlen(idesc->id_name);
838	    if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
839		dirp->d_namlen = testdir.d_namlen;
840		strcpy(dirp->d_name, idesc->id_name);
841		return STOP|ALTERED|FOUND;
842	    } else
843		return STOP|FOUND;	/* won't fit, so give up */
844	}
845	return KEEPON;
846}
847
848CMDFUNCSTART(chname)
849{
850    int rval;
851    char *cp;
852    struct inodesc idesc;
853
854    slotcount = 0;
855    if (!checkactivedir())
856	return 1;
857
858    desired = strtoul(argv[1], &cp, 0);
859    if (cp == argv[1] || *cp != '\0') {
860	printf("invalid slot number `%s'\n", argv[1]);
861	return 1;
862    }
863
864    idesc.id_number = curinum;
865    idesc.id_func = chnamefunc;
866    idesc.id_fix = IGNORE;
867    idesc.id_type = DATA;
868    idesc.id_name = argv[2];
869
870    rval = ckinode(curinode, &idesc);
871    if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
872	return 0;
873    else if (rval & FOUND) {
874	warnx("new name `%s' does not fit in slot %s\n", argv[2], argv[1]);
875	return 1;
876    } else {
877	warnx("no %sth slot in current directory", argv[1]);
878	return 1;
879    }
880}
881
882struct typemap {
883    const char *typename;
884    int typebits;
885} typenamemap[]  = {
886    {"file", IFREG},
887    {"dir", IFDIR},
888    {"socket", IFSOCK},
889    {"fifo", IFIFO},
890};
891
892CMDFUNCSTART(newtype)
893{
894    int type;
895    struct typemap *tp;
896
897    if (!checkactive())
898	return 1;
899    type = DIP(curinode, di_mode) & IFMT;
900    for (tp = typenamemap;
901	 tp < &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)];
902	 tp++) {
903	if (!strcmp(argv[1], tp->typename)) {
904	    printf("setting type to %s\n", tp->typename);
905	    type = tp->typebits;
906	    break;
907	}
908    }
909    if (tp == &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)]) {
910	warnx("type `%s' not known", argv[1]);
911	warnx("try one of `file', `dir', `socket', `fifo'");
912	return 1;
913    }
914    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
915    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
916    inodirty();
917    printactive(0);
918    return 0;
919}
920
921CMDFUNCSTART(chlen)
922{
923    int rval = 1;
924    long len;
925    char *cp;
926
927    if (!checkactive())
928	return 1;
929
930    len = strtol(argv[1], &cp, 0);
931    if (cp == argv[1] || *cp != '\0' || len < 0) {
932	warnx("bad length `%s'", argv[1]);
933	return 1;
934    }
935
936    DIP_SET(curinode, di_size, len);
937    inodirty();
938    printactive(0);
939    return rval;
940}
941
942CMDFUNCSTART(chmode)
943{
944    int rval = 1;
945    long modebits;
946    char *cp;
947
948    if (!checkactive())
949	return 1;
950
951    modebits = strtol(argv[1], &cp, 8);
952    if (cp == argv[1] || *cp != '\0' || (modebits & ~07777)) {
953	warnx("bad modebits `%s'", argv[1]);
954	return 1;
955    }
956
957    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
958    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
959    inodirty();
960    printactive(0);
961    return rval;
962}
963
964CMDFUNCSTART(chaflags)
965{
966    int rval = 1;
967    u_long flags;
968    char *cp;
969
970    if (!checkactive())
971	return 1;
972
973    flags = strtoul(argv[1], &cp, 0);
974    if (cp == argv[1] || *cp != '\0' ) {
975	warnx("bad flags `%s'", argv[1]);
976	return 1;
977    }
978
979    if (flags > UINT_MAX) {
980	warnx("flags set beyond 32-bit range of field (%lx)\n", flags);
981	return(1);
982    }
983    DIP_SET(curinode, di_flags, flags);
984    inodirty();
985    printactive(0);
986    return rval;
987}
988
989CMDFUNCSTART(chgen)
990{
991    int rval = 1;
992    long gen;
993    char *cp;
994
995    if (!checkactive())
996	return 1;
997
998    gen = strtol(argv[1], &cp, 0);
999    if (cp == argv[1] || *cp != '\0' ) {
1000	warnx("bad gen `%s'", argv[1]);
1001	return 1;
1002    }
1003
1004    if (gen > INT_MAX || gen < INT_MIN) {
1005	warnx("gen set beyond 32-bit range of field (%lx)\n", gen);
1006	return(1);
1007    }
1008    DIP_SET(curinode, di_gen, gen);
1009    inodirty();
1010    printactive(0);
1011    return rval;
1012}
1013
1014CMDFUNCSTART(linkcount)
1015{
1016    int rval = 1;
1017    int lcnt;
1018    char *cp;
1019
1020    if (!checkactive())
1021	return 1;
1022
1023    lcnt = strtol(argv[1], &cp, 0);
1024    if (cp == argv[1] || *cp != '\0' ) {
1025	warnx("bad link count `%s'", argv[1]);
1026	return 1;
1027    }
1028    if (lcnt > USHRT_MAX || lcnt < 0) {
1029	warnx("max link count is %d\n", USHRT_MAX);
1030	return 1;
1031    }
1032
1033    DIP_SET(curinode, di_nlink, lcnt);
1034    inodirty();
1035    printactive(0);
1036    return rval;
1037}
1038
1039CMDFUNCSTART(chowner)
1040{
1041    int rval = 1;
1042    unsigned long uid;
1043    char *cp;
1044    struct passwd *pwd;
1045
1046    if (!checkactive())
1047	return 1;
1048
1049    uid = strtoul(argv[1], &cp, 0);
1050    if (cp == argv[1] || *cp != '\0' ) {
1051	/* try looking up name */
1052	if ((pwd = getpwnam(argv[1]))) {
1053	    uid = pwd->pw_uid;
1054	} else {
1055	    warnx("bad uid `%s'", argv[1]);
1056	    return 1;
1057	}
1058    }
1059
1060    DIP_SET(curinode, di_uid, uid);
1061    inodirty();
1062    printactive(0);
1063    return rval;
1064}
1065
1066CMDFUNCSTART(chgroup)
1067{
1068    int rval = 1;
1069    unsigned long gid;
1070    char *cp;
1071    struct group *grp;
1072
1073    if (!checkactive())
1074	return 1;
1075
1076    gid = strtoul(argv[1], &cp, 0);
1077    if (cp == argv[1] || *cp != '\0' ) {
1078	if ((grp = getgrnam(argv[1]))) {
1079	    gid = grp->gr_gid;
1080	} else {
1081	    warnx("bad gid `%s'", argv[1]);
1082	    return 1;
1083	}
1084    }
1085
1086    DIP_SET(curinode, di_gid, gid);
1087    inodirty();
1088    printactive(0);
1089    return rval;
1090}
1091
1092int
1093dotime(char *name, time_t *secp, int32_t *nsecp)
1094{
1095    char *p, *val;
1096    struct tm t;
1097    int32_t nsec;
1098    p = strchr(name, '.');
1099    if (p) {
1100	*p = '\0';
1101	nsec = strtoul(++p, &val, 0);
1102	if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
1103		warnx("invalid nanoseconds");
1104		goto badformat;
1105	}
1106    } else
1107	nsec = 0;
1108    if (strlen(name) != 14) {
1109badformat:
1110	warnx("date format: YYYYMMDDHHMMSS[.nsec]");
1111	return 1;
1112    }
1113    *nsecp = nsec;
1114
1115    for (p = name; *p; p++)
1116	if (*p < '0' || *p > '9')
1117	    goto badformat;
1118
1119    p = name;
1120#define VAL() ((*p++) - '0')
1121    t.tm_year = VAL();
1122    t.tm_year = VAL() + t.tm_year * 10;
1123    t.tm_year = VAL() + t.tm_year * 10;
1124    t.tm_year = VAL() + t.tm_year * 10 - 1900;
1125    t.tm_mon = VAL();
1126    t.tm_mon = VAL() + t.tm_mon * 10 - 1;
1127    t.tm_mday = VAL();
1128    t.tm_mday = VAL() + t.tm_mday * 10;
1129    t.tm_hour = VAL();
1130    t.tm_hour = VAL() + t.tm_hour * 10;
1131    t.tm_min = VAL();
1132    t.tm_min = VAL() + t.tm_min * 10;
1133    t.tm_sec = VAL();
1134    t.tm_sec = VAL() + t.tm_sec * 10;
1135    t.tm_isdst = -1;
1136
1137    *secp = mktime(&t);
1138    if (*secp == -1) {
1139	warnx("date/time out of range");
1140	return 1;
1141    }
1142    return 0;
1143}
1144
1145CMDFUNCSTART(chbtime)
1146{
1147    time_t secs;
1148    int32_t nsecs;
1149
1150    if (dotime(argv[1], &secs, &nsecs))
1151	return 1;
1152    if (sblock.fs_magic == FS_UFS1_MAGIC)
1153	return 1;
1154    curinode->dp2.di_birthtime = _time_to_time64(secs);
1155    curinode->dp2.di_birthnsec = nsecs;
1156    inodirty();
1157    printactive(0);
1158    return 0;
1159}
1160
1161CMDFUNCSTART(chmtime)
1162{
1163    time_t secs;
1164    int32_t nsecs;
1165
1166    if (dotime(argv[1], &secs, &nsecs))
1167	return 1;
1168    if (sblock.fs_magic == FS_UFS1_MAGIC)
1169	curinode->dp1.di_mtime = _time_to_time32(secs);
1170    else
1171	curinode->dp2.di_mtime = _time_to_time64(secs);
1172    DIP_SET(curinode, di_mtimensec, nsecs);
1173    inodirty();
1174    printactive(0);
1175    return 0;
1176}
1177
1178CMDFUNCSTART(chatime)
1179{
1180    time_t secs;
1181    int32_t nsecs;
1182
1183    if (dotime(argv[1], &secs, &nsecs))
1184	return 1;
1185    if (sblock.fs_magic == FS_UFS1_MAGIC)
1186	curinode->dp1.di_atime = _time_to_time32(secs);
1187    else
1188	curinode->dp2.di_atime = _time_to_time64(secs);
1189    DIP_SET(curinode, di_atimensec, nsecs);
1190    inodirty();
1191    printactive(0);
1192    return 0;
1193}
1194
1195CMDFUNCSTART(chctime)
1196{
1197    time_t secs;
1198    int32_t nsecs;
1199
1200    if (dotime(argv[1], &secs, &nsecs))
1201	return 1;
1202    if (sblock.fs_magic == FS_UFS1_MAGIC)
1203	curinode->dp1.di_ctime = _time_to_time32(secs);
1204    else
1205	curinode->dp2.di_ctime = _time_to_time64(secs);
1206    DIP_SET(curinode, di_ctimensec, nsecs);
1207    inodirty();
1208    printactive(0);
1209    return 0;
1210}
1211