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