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