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