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