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