1/*	$NetBSD: fsdb.c,v 1.54 2023/01/07 19:41:29 chs Exp $	*/
2
3/*-
4 * Copyright (c) 1996, 2017 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.54 2023/01/07 19:41:29 chs 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#include <stdbool.h>
55
56#include <ufs/ufs/dinode.h>
57#include <ufs/ufs/dir.h>
58#include <ufs/ffs/fs.h>
59#include <ufs/ffs/ffs_extern.h>
60
61#include "fsdb.h"
62#include "fsck.h"
63#include "extern.h"
64
65struct bufarea bufhead;
66struct bufarea sblk;
67struct bufarea asblk;
68struct bufarea cgblk;
69struct bufarea appleufsblk;
70struct bufarea *pdirbp;
71struct bufarea *pbp;
72struct fs *sblock;
73struct fs *altsblock;
74struct cg *cgrp;
75struct fs *sblocksave;
76struct dups *duplist;
77struct dups *muldup;
78struct zlncnt *zlnhead;
79struct inoinfo **inphead, **inpsort;
80long numdirs, dirhash, listmax, inplast;
81struct uquot_hash *uquot_user_hash;
82struct uquot_hash *uquot_group_hash;
83uint8_t q2h_hash_shift;
84uint16_t q2h_hash_mask;
85struct inostatlist *inostathead;
86long	dev_bsize;
87long	secsize;
88char	nflag;
89char	yflag;
90int	Uflag;
91int	bflag;
92int	debug;
93int	zflag;
94int	cvtlevel;
95int	eaflag;
96int	doinglevel1;
97int	doinglevel2;
98int	doing2ea;
99int	doing2noea;
100int	newinofmt;
101char	usedsoftdep;
102int	preen;
103int	forceimage;
104int	is_ufs2;
105int	is_ufs2ea;
106int	markclean;
107char	havesb;
108char	skipclean;
109int	fsmodified;
110int	fsreadfd;
111int	fswritefd;
112int	rerun;
113char	resolved;
114int	endian;
115int	doswap;
116int	needswap;
117int	do_blkswap;
118int	do_dirswap;
119int	isappleufs;
120daddr_t maxfsblock;
121char	*blockmap;
122ino_t	maxino;
123int	dirblksiz;
124daddr_t n_blks;
125ino_t n_files;
126long countdirs;
127int	got_siginfo;
128struct	ufs1_dinode ufs1_zino;
129struct	ufs2_dinode ufs2_zino;
130
131/* Used to keep state for "saveblks" command.  */
132struct wrinfo {
133	off_t size;
134	off_t written_size;
135	int fd;
136};
137
138__dead static void usage(void);
139static int cmdloop(void);
140static char *prompt(EditLine *);
141static int scannames(struct inodesc *);
142static int dolookup(char *);
143static int chinumfunc(struct inodesc *);
144static int chnamefunc(struct inodesc *);
145static int chreclenfunc(struct inodesc *);
146static int dotime(char *, int64_t *, int32_t *);
147static void print_blks32(int32_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp);
148static void print_blks64(int64_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp);
149static void print_indirblks32(uint32_t blk, int ind_level,
150    uint64_t *blknum, struct wrinfo *wrp);
151static void print_indirblks64(uint64_t blk, int ind_level,
152    uint64_t *blknum, struct wrinfo *wrp);
153static int compare_blk32(uint32_t *, uint32_t);
154static int compare_blk64(uint64_t *, uint64_t);
155static int founddatablk(uint64_t);
156static int find_blks32(uint32_t *buf, int size, uint32_t *blknum);
157static int find_blks64(uint64_t *buf, int size, uint64_t *blknum);
158static int find_indirblks32(uint32_t blk, int ind_level,
159						uint32_t *blknum);
160static int find_indirblks64(uint64_t blk, int ind_level,
161						uint64_t *blknum);
162
163union dinode *curinode;
164ino_t   curinum;
165
166static void
167usage(void)
168{
169	errx(1, "usage: %s [-dFfNn] <fsname>", getprogname());
170}
171/*
172 * We suck in lots of fsck code, and just pick & choose the stuff we want.
173 *
174 * fsreadfd is set up to read from the file system, fswritefd to write to
175 * the file system.
176 */
177int
178main(int argc, char *argv[])
179{
180	int     ch, rval;
181	char   *fsys = NULL;
182	bool	makedirty = true;
183
184	forceimage = 0;
185	debug = 0;
186	isappleufs = 0;
187	while ((ch = getopt(argc, argv, "dFf:Nn")) != -1) {
188		switch (ch) {
189		case 'd':
190			debug++;
191			break;
192		case 'F':
193			forceimage = 1;
194			break;
195		case 'f':
196			fsys = optarg;
197			break;
198		case 'N':
199			makedirty = false;
200			break;
201		case 'n':
202			nflag++;
203			break;
204		default:
205			usage();
206		}
207	}
208	argc -= optind;
209	argv += optind;
210	if (fsys == NULL)
211		fsys = argv[0];
212	if (fsys == NULL)
213		usage();
214	endian = 0;
215	if (setup(fsys, fsys) <= 0)
216		errx(1, "cannot set up file system `%s'", fsys);
217	printf("Editing file system `%s'\nLast Mounted on %s\n", fsys,
218	    sblock->fs_fsmnt);
219	rval = cmdloop();
220	if (nflag)
221		exit(rval);
222	if (!makedirty) {
223		ckfini(1);
224		exit(rval);
225	}
226	sblock->fs_clean = 0;	/* mark it dirty */
227	sbdirty();
228	markclean = 0;
229	ckfini(1);
230	printf("*** FILE SYSTEM MARKED DIRTY\n");
231	printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
232	printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
233	exit(rval);
234}
235
236#define CMDFUNC(func) static int func (int argc, char *argv[])
237
238CMDFUNC(helpfn);
239CMDFUNC(focus);			/* focus on inode */
240CMDFUNC(active);		/* print active inode */
241CMDFUNC(focusname);		/* focus by name */
242CMDFUNC(zapi);			/* clear inode */
243CMDFUNC(uplink);		/* incr link */
244CMDFUNC(downlink);		/* decr link */
245CMDFUNC(linkcount);		/* set link count */
246CMDFUNC(quit);			/* quit */
247CMDFUNC(ls);			/* list directory */
248CMDFUNC(blks);			/* list blocks */
249CMDFUNC(findblk);		/* find block */
250CMDFUNC(rm);			/* remove name */
251CMDFUNC(ln);			/* add name */
252CMDFUNC(newtype);		/* change type */
253CMDFUNC(chmode);		/* change mode */
254CMDFUNC(chlen);			/* change length */
255CMDFUNC(chaflags);		/* change flags */
256CMDFUNC(chgen);			/* change generation */
257CMDFUNC(chowner);		/* change owner */
258CMDFUNC(chgroup);		/* Change group */
259CMDFUNC(back);			/* pop back to last ino */
260CMDFUNC(chmtime);		/* Change mtime */
261CMDFUNC(chctime);		/* Change ctime */
262CMDFUNC(chatime);		/* Change atime */
263CMDFUNC(chbirthtime);		/* Change birthtime */
264CMDFUNC(chinum);		/* Change inode # of dirent */
265CMDFUNC(chname);		/* Change dirname of dirent */
266CMDFUNC(chreclen);		/* Change reclen of dirent */
267CMDFUNC(chextsize);		/* Change extsize */
268CMDFUNC(chblocks);		/* Change blocks */
269CMDFUNC(chdb);			/* Change direct block pointer */
270CMDFUNC(chib);			/* Change indirect block pointer */
271CMDFUNC(chextb);		/* Change extattr block pointer */
272CMDFUNC(chfreelink);		/* Change freelink pointer */
273CMDFUNC(iptrs);			/* print raw block pointers for active inode */
274CMDFUNC(saveea);		/* Save extattrs */
275
276static struct cmdtable cmds[] = {
277	{"help", "Print out help", 1, 1, helpfn},
278	{"?", "Print out help", 1, 1, helpfn},
279	{"inode", "Set active inode to INUM", 2, 2, focus},
280	{"clri", "Clear inode INUM", 2, 2, zapi},
281	{"lookup", "Set active inode by looking up NAME", 2, 2, focusname},
282	{"cd", "Set active inode by looking up NAME", 2, 2, focusname},
283	{"back", "Go to previous active inode", 1, 1, back},
284	{"active", "Print active inode", 1, 1, active},
285	{"print", "Print active inode", 1, 1, active},
286	{"uplink", "Increment link count", 1, 1, uplink},
287	{"downlink", "Decrement link count", 1, 1, downlink},
288	{"linkcount", "Set link count to COUNT", 2, 2, linkcount},
289	{"ls", "List current inode as directory", 1, 1, ls},
290	{"blks", "List current inode's data blocks", 1, 1, blks},
291	{"saveblks", "Save current inode's data blocks to FILE", 2, 2, blks},
292	{"findblk", "Find inode owning disk block(s)", 2, 33, findblk},
293	{"rm", "Remove NAME from current inode directory", 2, 2, rm},
294	{"del", "Remove NAME from current inode directory", 2, 2, rm},
295	{"ln", "Hardlink INO into current inode directory as NAME", 3, 3, ln},
296	{"chinum", "Change dir entry number INDEX to INUM", 3, 3, chinum},
297	{"chname", "Change dir entry number INDEX to NAME", 3, 3, chname},
298	{"chreclen", "Change dir entry number INDEX to RECLEN", 3, 3, chreclen},
299	{"chtype", "Change type of current inode to TYPE", 2, 2, newtype},
300	{"chmod", "Change mode of current inode to MODE", 2, 2, chmode},
301	{"chown", "Change owner of current inode to OWNER", 2, 2, chowner},
302	{"chlen", "Change length of current inode to LENGTH", 2, 2, chlen},
303	{"chgrp", "Change group of current inode to GROUP", 2, 2, chgroup},
304	{"chflags", "Change flags of current inode to FLAGS", 2, 2, chaflags},
305	{"chgen", "Change generation number of current inode to GEN", 2, 2,
306		    chgen},
307	{ "chextsize", "Change extsize of current inode to EXTSIZE", 2, 2, chextsize },
308	{ "chblocks", "Change blocks of current inode to BLOCKS", 2, 2, chblocks },
309	{ "chdb", "Change db pointer N of current inode to BLKNO", 3, 3, chdb },
310	{ "chib", "Change ib pointer N of current inode to BLKNO", 3, 3, chib },
311	{ "chextb", "Change extb pointer N of current inode to BLKNO", 3, 3, chextb },
312	{ "chfreelink", "Change freelink of current inode to FREELINK", 2, 2, chfreelink },
313	{ "iptrs", "Print raw block pointers of current inode", 1, 1, iptrs },
314	{"mtime", "Change mtime of current inode to MTIME", 2, 2, chmtime},
315	{"ctime", "Change ctime of current inode to CTIME", 2, 2, chctime},
316	{"atime", "Change atime of current inode to ATIME", 2, 2, chatime},
317	{"birthtime", "Change atime of current inode to BIRTHTIME", 2, 2,
318	    chbirthtime},
319	{"saveea", "Save current inode's extattr blocks to FILE", 2, 2, saveea},
320	{"quit", "Exit", 1, 1, quit},
321	{"q", "Exit", 1, 1, quit},
322	{"exit", "Exit", 1, 1, quit},
323	{ .cmd = NULL},
324};
325
326static int
327helpfn(int argc, char *argv[])
328{
329	struct cmdtable *cmdtp;
330
331	printf("Commands are:\n%-10s %5s %5s   %s\n",
332	    "command", "min argc", "max argc", "what");
333
334	for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
335		printf("%-10s %5u %5u   %s\n",
336		    cmdtp->cmd, cmdtp->minargc, cmdtp->maxargc, cmdtp->helptxt);
337	return 0;
338}
339
340static char *
341prompt(EditLine *el)
342{
343	static char pstring[64];
344	snprintf(pstring, sizeof(pstring), "fsdb (inum: %llu)> ",
345	    (unsigned long long)curinum);
346	return pstring;
347}
348
349
350static int
351cmdloop(void)
352{
353	char   *line;
354	const char *elline;
355	int     cmd_argc, rval = 0, known;
356#define scratch known
357	char  **cmd_argv;
358	struct cmdtable *cmdp;
359	History *hist;
360	HistEvent he;
361	EditLine *elptr;
362
363	curinode = ginode(UFS_ROOTINO);
364	curinum = UFS_ROOTINO;
365	printactive();
366
367	hist = history_init();
368	history(hist, &he, H_SETSIZE, 100);	/* 100 elt history buffer */
369
370	elptr = el_init(getprogname(), stdin, stdout, stderr);
371	el_set(elptr, EL_EDITOR, "emacs");
372	el_set(elptr, EL_PROMPT, prompt);
373	el_set(elptr, EL_HIST, history, hist);
374	el_source(elptr, NULL);
375
376	while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
377		if (debug)
378			printf("command `%s'\n", elline);
379
380		history(hist, &he, H_ENTER, elline);
381
382		line = strdup(elline);
383		cmd_argv = crack(line, &cmd_argc);
384		if (cmd_argc) {
385			/*
386		         * el_parse returns -1 to signal that it's not been
387		         * handled internally.
388		         */
389			if (el_parse(elptr, cmd_argc, (void *)cmd_argv) != -1)
390				continue;
391			known = 0;
392			for (cmdp = cmds; cmdp->cmd; cmdp++) {
393				if (!strcmp(cmdp->cmd, cmd_argv[0])) {
394					if (cmd_argc >= cmdp->minargc &&
395					    cmd_argc <= cmdp->maxargc)
396						rval =
397						    (*cmdp->handler)(cmd_argc,
398							cmd_argv);
399					else
400						rval = argcount(cmdp, cmd_argc,
401						    cmd_argv);
402					known = 1;
403					break;
404				}
405			}
406			if (!known)
407				warnx("unknown command `%s'", cmd_argv[0]),
408				    rval = 1;
409		} else
410			rval = 0;
411		free(line);
412		if (rval < 0)
413			return rval;
414		if (rval)
415			warnx("rval was %d", rval);
416	}
417	el_end(elptr);
418	history_end(hist);
419	return rval;
420}
421
422static ino_t ocurrent;
423
424#define GETINUM(ac,inum)    inum = strtoull(argv[ac], &cp, 0); \
425    if (inum < UFS_ROOTINO || inum >= maxino || cp == argv[ac] || *cp != '\0' ) { \
426	printf("inode %llu out of range; range is [%llu,%llu]\n", \
427	   (unsigned long long)inum, (unsigned long long)UFS_ROOTINO, \
428	   (unsigned long long)maxino); \
429	return 1; \
430    }
431
432/*
433 * Focus on given inode number
434 */
435CMDFUNC(focus)
436{
437	ino_t   inum;
438	char   *cp;
439
440	GETINUM(1, inum);
441	curinode = ginode(inum);
442	ocurrent = curinum;
443	curinum = inum;
444	printactive();
445	return 0;
446}
447
448CMDFUNC(back)
449{
450	curinum = ocurrent;
451	curinode = ginode(curinum);
452	printactive();
453	return 0;
454}
455
456CMDFUNC(zapi)
457{
458	ino_t   inum;
459	union dinode *dp;
460	char   *cp;
461
462	GETINUM(1, inum);
463	dp = ginode(inum);
464	clearinode(dp);
465	inodirty();
466	if (curinode)		/* re-set after potential change */
467		curinode = ginode(curinum);
468	return 0;
469}
470
471CMDFUNC(active)
472{
473	printactive();
474	return 0;
475}
476
477CMDFUNC(quit)
478{
479	return -1;
480}
481
482CMDFUNC(uplink)
483{
484	int16_t nlink;
485
486	if (!checkactive())
487		return 1;
488	nlink = iswap16(DIP(curinode, nlink));
489	nlink++;
490	DIP_SET(curinode, nlink, iswap16(nlink));
491	printf("inode %llu link count now %d\n", (unsigned long long)curinum,
492	    nlink);
493	inodirty();
494	return 0;
495}
496
497CMDFUNC(downlink)
498{
499	int16_t nlink;
500
501	if (!checkactive())
502		return 1;
503	nlink = iswap16(DIP(curinode, nlink));
504	nlink--;
505	DIP_SET(curinode, nlink, iswap16(nlink));
506	printf("inode %llu link count now %d\n", (unsigned long long)curinum,
507	    nlink);
508	inodirty();
509	return 0;
510}
511
512static const char *typename[] = {
513	"unknown",
514	"fifo",
515	"char special",
516	"unregistered #3",
517	"directory",
518	"unregistered #5",
519	"blk special",
520	"unregistered #7",
521	"regular",
522	"unregistered #9",
523	"symlink",
524	"unregistered #11",
525	"socket",
526	"unregistered #13",
527	"whiteout",
528};
529
530static int diroff;
531static int slot;
532
533static int
534scannames(struct inodesc *idesc)
535{
536	struct direct *dirp = idesc->id_dirp;
537
538	printf("slot %d off %d ino %d reclen %d: %s, `%.*s'\n",
539	    slot++, diroff, iswap32(dirp->d_ino), iswap16(dirp->d_reclen),
540	    typename[dirp->d_type],
541	    dirp->d_namlen, dirp->d_name);
542	diroff += dirp->d_reclen;
543	return (KEEPON);
544}
545
546CMDFUNC(ls)
547{
548	struct inodesc idesc;
549	checkactivedir();	/* let it go on anyway */
550
551	slot = 0;
552	diroff = 0;
553	idesc.id_number = curinum;
554	idesc.id_func = scannames;
555	idesc.id_type = DATA;
556	idesc.id_fix = IGNORE;
557	ckinode(curinode, &idesc);
558	curinode = ginode(curinum);
559
560	return 0;
561}
562
563CMDFUNC(blks)
564{
565	uint64_t blkno = 0;
566	int i;
567	struct wrinfo wrinfo, *wrp = NULL;
568	bool saveblks;
569
570	saveblks = strcmp(argv[0], "saveblks") == 0;
571	if (saveblks) {
572		wrinfo.fd = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0644);
573		if (wrinfo.fd == -1) {
574			warn("unable to create file %s", argv[1]);
575			return 0;
576		}
577		wrinfo.size = iswap64(DIP(curinode, size));
578		wrinfo.written_size = 0;
579		wrp = &wrinfo;
580	}
581	if (!curinode) {
582		warnx("no current inode");
583		return 0;
584	}
585	if (is_ufs2) {
586		printf("I=%llu %lld blocks\n", (unsigned long long)curinum,
587		    (long long)(iswap64(curinode->dp2.di_blocks)));
588	} else {
589		printf("I=%llu %d blocks\n", (unsigned long long)curinum,
590		    iswap32(curinode->dp1.di_blocks));
591	}
592	printf("Direct blocks:\n");
593	if (is_ufs2)
594		print_blks64(curinode->dp2.di_db, UFS_NDADDR, &blkno, wrp);
595	else
596		print_blks32(curinode->dp1.di_db, UFS_NDADDR, &blkno, wrp);
597
598	if (is_ufs2) {
599		for (i = 0; i < UFS_NIADDR; i++)
600			print_indirblks64(iswap64(curinode->dp2.di_ib[i]), i,
601			    &blkno, wrp);
602		printf("Extattr blocks:\n");
603		blkno = 0;
604		if (saveblks)
605			wrinfo.size += iswap32(curinode->dp2.di_extsize);
606		print_blks64(curinode->dp2.di_extb, UFS_NXADDR, &blkno, wrp);
607	} else {
608		for (i = 0; i < UFS_NIADDR; i++)
609			print_indirblks32(iswap32(curinode->dp1.di_ib[i]), i,
610			    &blkno, wrp);
611	}
612	return 0;
613}
614
615static int findblk_numtofind;
616static int wantedblksize;
617CMDFUNC(findblk)
618{
619	ino_t   inum, inosused;
620	uint32_t *wantedblk32 = NULL;
621	uint64_t *wantedblk64 = NULL;
622	struct cg *cgp = cgrp;
623	int i;
624	uint32_t c;
625
626	ocurrent = curinum;
627	wantedblksize = (argc - 1);
628	if (is_ufs2) {
629		wantedblk64 = malloc(sizeof(uint64_t) * wantedblksize);
630		if (wantedblk64 == NULL) {
631			perror("malloc");
632			return 1;
633		}
634		memset(wantedblk64, 0, sizeof(uint64_t) * wantedblksize);
635		for (i = 1; i < argc; i++)
636			wantedblk64[i - 1] =
637			    FFS_DBTOFSB(sblock, strtoull(argv[i], NULL, 0));
638	} else {
639		wantedblk32 = malloc(sizeof(uint32_t) * wantedblksize);
640		if (wantedblk32 == NULL) {
641			perror("malloc");
642			return 1;
643		}
644		memset(wantedblk32, 0, sizeof(uint32_t) * wantedblksize);
645		for (i = 1; i < argc; i++)
646			wantedblk32[i - 1] =
647			    FFS_DBTOFSB(sblock, strtoull(argv[i], NULL, 0));
648	}
649	findblk_numtofind = wantedblksize;
650	for (c = 0; c < sblock->fs_ncg; c++) {
651		inum = c * sblock->fs_ipg;
652		getblk(&cgblk, cgtod(sblock, c), sblock->fs_cgsize);
653		memcpy(cgp, cgblk.b_un.b_cg, sblock->fs_cgsize);
654		if (needswap)
655			ffs_cg_swap(cgblk.b_un.b_cg, cgp, sblock);
656		if (is_ufs2)
657			inosused = cgp->cg_initediblk;
658		else
659			inosused = sblock->fs_ipg;
660		for (; inosused > 0; inum++, inosused--) {
661			if (inum < UFS_ROOTINO)
662				continue;
663			if (is_ufs2 ? compare_blk64(wantedblk64,
664			        ino_to_fsba(sblock, inum)) :
665			    compare_blk32(wantedblk32,
666			        ino_to_fsba(sblock, inum))) {
667				printf("block %llu: inode block (%llu-%llu)\n",
668				    (unsigned long long)FFS_FSBTODB(sblock,
669					ino_to_fsba(sblock, inum)),
670				    (unsigned long long)
671				    (inum / FFS_INOPB(sblock)) * FFS_INOPB(sblock),
672				    (unsigned long long)
673				    (inum / FFS_INOPB(sblock) + 1) * FFS_INOPB(sblock));
674				findblk_numtofind--;
675				if (findblk_numtofind == 0)
676					goto end;
677			}
678			curinum = inum;
679			curinode = ginode(inum);
680			switch (iswap16(DIP(curinode, mode)) & IFMT) {
681			case IFDIR:
682			case IFREG:
683				if (DIP(curinode, blocks) == 0)
684					continue;
685				break;
686			case IFLNK:
687				{
688				uint64_t size = iswap64(DIP(curinode, size));
689				if (size > 0 &&
690				    size < (uint64_t)sblock->fs_maxsymlinklen &&
691				    DIP(curinode, blocks) == 0)
692					continue;
693				else
694					break;
695				}
696			default:
697				continue;
698			}
699			if (is_ufs2 ?
700			    find_blks64(curinode->dp2.di_db, UFS_NDADDR,
701				wantedblk64) :
702			    find_blks32(curinode->dp1.di_db, UFS_NDADDR,
703				wantedblk32))
704				goto end;
705			for (i = 0; i < UFS_NIADDR; i++) {
706				if (is_ufs2 ?
707				    compare_blk64(wantedblk64,
708					iswap64(curinode->dp2.di_ib[i])) :
709				    compare_blk32(wantedblk32,
710					iswap32(curinode->dp1.di_ib[i])))
711					if (founddatablk(is_ufs2 ?
712					    iswap64(curinode->dp2.di_ib[i]) :
713					    iswap32(curinode->dp1.di_ib[i])))
714						goto end;
715				if (is_ufs2 ? (curinode->dp2.di_ib[i] != 0) :
716				    (curinode->dp1.di_ib[i] != 0))
717					if (is_ufs2 ?
718					    find_indirblks64(
719						iswap64(curinode->dp2.di_ib[i]),
720						i, wantedblk64) :
721					    find_indirblks32(
722						iswap32(curinode->dp1.di_ib[i]),
723						i, wantedblk32))
724						goto end;
725			}
726		}
727	}
728end:
729	if (wantedblk32)
730		free(wantedblk32);
731	if (wantedblk64)
732		free(wantedblk64);
733	curinum = ocurrent;
734	curinode = ginode(curinum);
735	return 0;
736}
737
738static int
739compare_blk32(uint32_t *wantedblk, uint32_t curblk)
740{
741	int i;
742	for (i = 0; i < wantedblksize; i++) {
743		if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
744			wantedblk[i] = 0;
745			return 1;
746		}
747	}
748	return 0;
749}
750
751static int
752compare_blk64(uint64_t *wantedblk, uint64_t curblk)
753{
754	int i;
755	for (i = 0; i < wantedblksize; i++) {
756		if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
757			wantedblk[i] = 0;
758			return 1;
759		}
760	}
761	return 0;
762}
763
764static int
765founddatablk(uint64_t blk)
766{
767	printf("%llu: data block of inode %llu\n",
768	    (unsigned long long)FFS_FSBTODB(sblock, blk),
769	    (unsigned long long)curinum);
770	findblk_numtofind--;
771	if (findblk_numtofind == 0)
772		return 1;
773	return 0;
774}
775
776static int
777find_blks32(uint32_t *buf, int size, uint32_t *wantedblk)
778{
779	int blk;
780	for(blk = 0; blk < size; blk++) {
781		if (buf[blk] == 0)
782			continue;
783		if (compare_blk32(wantedblk, iswap32(buf[blk]))) {
784			if (founddatablk(iswap32(buf[blk])))
785				return 1;
786		}
787	}
788	return 0;
789}
790
791static int
792find_indirblks32(uint32_t blk, int ind_level, uint32_t *wantedblk)
793{
794#define MAXNINDIR	(MAXBSIZE / sizeof(uint32_t))
795	uint32_t idblk[MAXNINDIR];
796	size_t i;
797
798	bread(fsreadfd, (char *)idblk, FFS_FSBTODB(sblock, blk),
799	    (int)sblock->fs_bsize);
800	if (ind_level <= 0) {
801		if (find_blks32(idblk,
802		    sblock->fs_bsize / sizeof(uint32_t), wantedblk))
803			return 1;
804	} else {
805		ind_level--;
806		for (i = 0; i < sblock->fs_bsize / sizeof(uint32_t); i++) {
807			if (compare_blk32(wantedblk, iswap32(idblk[i]))) {
808				if (founddatablk(iswap32(idblk[i])))
809					return 1;
810			}
811			if(idblk[i] != 0)
812				if (find_indirblks32(iswap32(idblk[i]),
813				    ind_level, wantedblk))
814				return 1;
815		}
816	}
817#undef MAXNINDIR
818	return 0;
819}
820
821
822static int
823find_blks64(uint64_t *buf, int size, uint64_t *wantedblk)
824{
825	int blk;
826	for(blk = 0; blk < size; blk++) {
827		if (buf[blk] == 0)
828			continue;
829		if (compare_blk64(wantedblk, iswap64(buf[blk]))) {
830			if (founddatablk(iswap64(buf[blk])))
831				return 1;
832		}
833	}
834	return 0;
835}
836
837static int
838find_indirblks64(uint64_t blk, int ind_level, uint64_t *wantedblk)
839{
840#define MAXNINDIR	(MAXBSIZE / sizeof(uint64_t))
841	uint64_t idblk[MAXNINDIR];
842	size_t i;
843
844	bread(fsreadfd, (char *)idblk, FFS_FSBTODB(sblock, blk),
845	    (int)sblock->fs_bsize);
846	if (ind_level <= 0) {
847		if (find_blks64(idblk,
848		    sblock->fs_bsize / sizeof(uint64_t), wantedblk))
849			return 1;
850	} else {
851		ind_level--;
852		for (i = 0; i < sblock->fs_bsize / sizeof(uint64_t); i++) {
853			if (compare_blk64(wantedblk, iswap64(idblk[i]))) {
854				if (founddatablk(iswap64(idblk[i])))
855					return 1;
856			}
857			if (idblk[i] != 0)
858				if (find_indirblks64(iswap64(idblk[i]),
859				    ind_level, wantedblk))
860				return 1;
861		}
862	}
863#undef MAXNINDIR
864	return 0;
865}
866
867static int
868writefileblk(struct wrinfo *wrp, uint64_t blk)
869{
870	char buf[MAXBSIZE];
871	long long size, rsize;
872
873	size = wrp->size - wrp->written_size;
874	if (size > sblock->fs_bsize)
875		size = sblock->fs_bsize;
876	if (size > (long long)sizeof buf) {
877		warnx("sblock->fs_bsize > MAX_BSIZE");
878		return -1;
879	}
880
881	rsize = roundup(size, DEV_BSIZE);
882	if (bread(fsreadfd, buf, FFS_FSBTODB(sblock, blk), rsize) != 0)
883		return -1;
884	if (write(wrp->fd, buf, size) != size)
885		return -1;
886	wrp->written_size += size;
887	return 0;
888}
889
890
891#define CHARS_PER_LINES 70
892
893static void
894print_blks32(int32_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp)
895{
896	int chars;
897	char prbuf[CHARS_PER_LINES+1];
898	int blk;
899
900	chars = 0;
901	for (blk = 0; blk < size; blk++, (*blknum)++) {
902		if (buf[blk] == 0)
903			continue;
904		if (wrp && writefileblk(wrp, iswap32(buf[blk])) != 0) {
905			warn("unable to write block %d", iswap32(buf[blk]));
906			return;
907		}
908		snprintf(prbuf, CHARS_PER_LINES, "%d ", iswap32(buf[blk]));
909		if ((chars + strlen(prbuf)) > CHARS_PER_LINES) {
910			printf("\n");
911			chars = 0;
912		}
913		if (chars == 0)
914			printf("%" PRIu64 ": ", *blknum);
915		printf("%s", prbuf);
916		chars += strlen(prbuf);
917	}
918	printf("\n");
919}
920
921static void
922print_blks64(int64_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp)
923{
924	int chars;
925	char prbuf[CHARS_PER_LINES+1];
926	int blk;
927
928	chars = 0;
929	for (blk = 0; blk < size; blk++, (*blknum)++) {
930		if (buf[blk] == 0)
931			continue;
932		if (wrp && writefileblk(wrp, iswap64(buf[blk])) != 0) {
933			warn("unable to write block %lld",
934			     (long long)iswap64(buf[blk]));
935			return;
936		}
937		snprintf(prbuf, CHARS_PER_LINES, "%lld ",
938		    (long long)iswap64(buf[blk]));
939		if ((chars + strlen(prbuf)) > CHARS_PER_LINES) {
940			printf("\n");
941			chars = 0;
942		}
943		if (chars == 0)
944			printf("%" PRIu64 ": ", *blknum);
945		printf("%s", prbuf);
946		chars += strlen(prbuf);
947	}
948	printf("\n");
949}
950
951#undef CHARS_PER_LINES
952
953static void
954print_indirblks32(uint32_t blk, int ind_level, uint64_t *blknum, struct wrinfo *wrp)
955{
956#define MAXNINDIR	(MAXBSIZE / sizeof(int32_t))
957	const int ptrperblk_shift = sblock->fs_bshift - 2;
958	const int ptrperblk = 1 << ptrperblk_shift;
959	int32_t idblk[MAXNINDIR];
960	int i;
961
962	if (blk == 0) {
963		*blknum += (uint64_t)ptrperblk << (ptrperblk_shift * ind_level);
964		return;
965	}
966
967	printf("Indirect block %lld (level %d):\n", (long long)blk,
968	    ind_level+1);
969	bread(fsreadfd, (char *)idblk, FFS_FSBTODB(sblock, blk),
970	    (int)sblock->fs_bsize);
971	if (ind_level <= 0) {
972		print_blks32(idblk, ptrperblk, blknum, wrp);
973	} else {
974		ind_level--;
975		for (i = 0; i < ptrperblk; i++)
976			print_indirblks32(iswap32(idblk[i]), ind_level, blknum,
977				wrp);
978	}
979#undef MAXNINDIR
980}
981
982static void
983print_indirblks64(uint64_t blk, int ind_level, uint64_t *blknum, struct wrinfo *wrp)
984{
985#define MAXNINDIR	(MAXBSIZE / sizeof(int64_t))
986	const int ptrperblk_shift = sblock->fs_bshift - 3;
987	const int ptrperblk = 1 << ptrperblk_shift;
988	int64_t idblk[MAXNINDIR];
989	int i;
990
991	if (blk == 0) {
992		*blknum += (uint64_t)ptrperblk << (ptrperblk_shift * ind_level);
993		return;
994	}
995
996	printf("Indirect block %lld (level %d):\n", (long long)blk,
997	    ind_level+1);
998	bread(fsreadfd, (char *)idblk, FFS_FSBTODB(sblock, blk),
999	    (int)sblock->fs_bsize);
1000	if (ind_level <= 0) {
1001		print_blks64(idblk, ptrperblk, blknum, wrp);
1002	} else {
1003		ind_level--;
1004		for (i = 0; i < ptrperblk; i++)
1005			print_indirblks64(iswap64(idblk[i]), ind_level, blknum,
1006				wrp);
1007	}
1008#undef MAXNINDIR
1009}
1010
1011static int
1012dolookup(char *name)
1013{
1014	struct inodesc idesc;
1015
1016	if (!checkactivedir())
1017		return 0;
1018	idesc.id_number = curinum;
1019	idesc.id_func = findino;
1020	idesc.id_name = name;
1021	idesc.id_type = DATA;
1022	idesc.id_fix = IGNORE;
1023	if (ckinode(curinode, &idesc) & FOUND) {
1024		curinum = idesc.id_parent;
1025		curinode = ginode(curinum);
1026		printactive();
1027		return 1;
1028	} else {
1029		warnx("name `%s' not found in current inode directory", name);
1030		return 0;
1031	}
1032}
1033
1034CMDFUNC(focusname)
1035{
1036	char   *p, *val;
1037
1038	if (!checkactive())
1039		return 1;
1040
1041	ocurrent = curinum;
1042
1043	if (argv[1][0] == '/') {
1044		curinum = UFS_ROOTINO;
1045		curinode = ginode(UFS_ROOTINO);
1046	} else {
1047		if (!checkactivedir())
1048			return 1;
1049	}
1050	for (p = argv[1]; p != NULL;) {
1051		while ((val = strsep(&p, "/")) != NULL && *val == '\0');
1052		if (val) {
1053			printf("component `%s': ", val);
1054			fflush(stdout);
1055			if (!dolookup(val)) {
1056				curinode = ginode(curinum);
1057				return (1);
1058			}
1059		}
1060	}
1061	return 0;
1062}
1063
1064CMDFUNC(ln)
1065{
1066	ino_t   inum;
1067	int     rval;
1068	char   *cp;
1069
1070	GETINUM(1, inum);
1071
1072	if (!checkactivedir())
1073		return 1;
1074	rval = makeentry(curinum, inum, argv[2]);
1075	if (rval)
1076		printf("Ino %llu entered as `%s'\n", (unsigned long long)inum,
1077		    argv[2]);
1078	else
1079		printf("could not enter name? weird.\n");
1080	curinode = ginode(curinum);
1081	return rval;
1082}
1083
1084CMDFUNC(rm)
1085{
1086	int     rval;
1087
1088	if (!checkactivedir())
1089		return 1;
1090	rval = changeino(curinum, argv[1], 0);
1091	if (rval & ALTERED) {
1092		printf("Name `%s' removed\n", argv[1]);
1093		return 0;
1094	} else {
1095		printf("could not remove name? weird.\n");
1096		return 1;
1097	}
1098}
1099
1100static long slotcount, desired;
1101
1102static int
1103chinumfunc(struct inodesc *idesc)
1104{
1105	struct direct *dirp = idesc->id_dirp;
1106
1107	if (slotcount++ == desired) {
1108		dirp->d_ino = iswap32(idesc->id_parent);
1109		return STOP | ALTERED | FOUND;
1110	}
1111	return KEEPON;
1112}
1113
1114CMDFUNC(chinum)
1115{
1116	char   *cp;
1117	ino_t   inum;
1118	struct inodesc idesc;
1119
1120	slotcount = 0;
1121	if (!checkactivedir())
1122		return 1;
1123	GETINUM(2, inum);
1124
1125	desired = strtol(argv[1], &cp, 0);
1126	if (cp == argv[1] || *cp != '\0' || desired < 0) {
1127		printf("invalid slot number `%s'\n", argv[1]);
1128		return 1;
1129	}
1130	idesc.id_number = curinum;
1131	idesc.id_func = chinumfunc;
1132	idesc.id_fix = IGNORE;
1133	idesc.id_type = DATA;
1134	idesc.id_parent = inum;	/* XXX convenient hiding place */
1135
1136	if (ckinode(curinode, &idesc) & FOUND)
1137		return 0;
1138	else {
1139		warnx("no %sth slot in current directory", argv[1]);
1140		return 1;
1141	}
1142}
1143
1144static int
1145chnamefunc(struct inodesc *idesc)
1146{
1147	struct direct *dirp = idesc->id_dirp;
1148	struct direct testdir;
1149
1150	if (slotcount++ == desired) {
1151		/* will name fit? */
1152		testdir.d_namlen = strlen(idesc->id_name);
1153		if (UFS_DIRSIZ(UFS_NEWDIRFMT, &testdir, 0) <= iswap16(dirp->d_reclen)) {
1154			dirp->d_namlen = testdir.d_namlen;
1155			strlcpy(dirp->d_name, idesc->id_name,
1156			    sizeof(dirp->d_name));
1157			return STOP | ALTERED | FOUND;
1158		} else
1159			return STOP | FOUND;	/* won't fit, so give up */
1160	}
1161	return KEEPON;
1162}
1163
1164CMDFUNC(chname)
1165{
1166	int     rval;
1167	char   *cp;
1168	struct inodesc idesc;
1169
1170	slotcount = 0;
1171	if (!checkactivedir())
1172		return 1;
1173
1174	desired = strtoul(argv[1], &cp, 0);
1175	if (cp == argv[1] || *cp != '\0') {
1176		printf("invalid slot number `%s'\n", argv[1]);
1177		return 1;
1178	}
1179	idesc.id_number = curinum;
1180	idesc.id_func = chnamefunc;
1181	idesc.id_fix = IGNORE;
1182	idesc.id_type = DATA;
1183	idesc.id_name = argv[2];
1184
1185	rval = ckinode(curinode, &idesc);
1186	if ((rval & (FOUND | ALTERED)) == (FOUND | ALTERED))
1187		return 0;
1188	else
1189		if (rval & FOUND) {
1190			warnx("new name `%s' does not fit in slot %s",
1191			    argv[2], argv[1]);
1192			return 1;
1193		} else {
1194			warnx("no %sth slot in current directory", argv[1]);
1195			return 1;
1196		}
1197}
1198
1199static int
1200chreclenfunc(struct inodesc *idesc)
1201{
1202	struct direct *dirp = idesc->id_dirp;
1203
1204	if (slotcount++ == desired) {
1205		dirp->d_reclen = iswap16(idesc->id_parent);
1206		return STOP | ALTERED | FOUND;
1207	}
1208	return KEEPON;
1209}
1210
1211CMDFUNC(chreclen)
1212{
1213	char   *cp;
1214	uint32_t reclen;
1215	struct inodesc idesc;
1216
1217	slotcount = 0;
1218	if (!checkactivedir())
1219		return 1;
1220
1221	desired = strtoul(argv[1], &cp, 0);
1222	if (cp == argv[1] || *cp != '\0') {
1223		printf("invalid slot number `%s'\n", argv[1]);
1224		return 1;
1225	}
1226	reclen = strtoul(argv[2], &cp, 0);
1227	if (reclen >= UINT16_MAX) {
1228		printf("invalid reclen `%s'\n", argv[2]);
1229		return 1;
1230	}
1231
1232	idesc.id_number = curinum;
1233	idesc.id_func = chreclenfunc;
1234	idesc.id_fix = IGNORE;
1235	idesc.id_type = DATA;
1236	idesc.id_parent = reclen;	/* XXX convenient hiding place */
1237
1238	if (ckinode(curinode, &idesc) & FOUND)
1239		return 0;
1240	else {
1241		warnx("no %sth slot in current directory", argv[1]);
1242		return 1;
1243	}
1244}
1245
1246static struct typemap {
1247	const char *typename;
1248	int     typebits;
1249}       typenamemap[] = {
1250	{ "file", IFREG },
1251	{ "dir", IFDIR },
1252	{ "socket", IFSOCK },
1253	{ "fifo", IFIFO },
1254	{"link", IFLNK},
1255	{"chr", IFCHR},
1256	{"blk", IFBLK},
1257};
1258
1259CMDFUNC(newtype)
1260{
1261	int     type;
1262	uint16_t mode;
1263	struct typemap *tp;
1264
1265	if (!checkactive())
1266		return 1;
1267	mode = iswap16(DIP(curinode, mode));
1268	type = mode & IFMT;
1269	for (tp = typenamemap;
1270	    tp < &typenamemap[sizeof(typenamemap) / sizeof(*typenamemap)];
1271	    tp++) {
1272		if (!strcmp(argv[1], tp->typename)) {
1273			printf("setting type to %s\n", tp->typename);
1274			type = tp->typebits;
1275			break;
1276		}
1277	}
1278	if (tp == &typenamemap[sizeof(typenamemap) / sizeof(*typenamemap)]) {
1279		warnx("type `%s' not known", argv[1]);
1280		warnx("try one of `file', `dir', `socket', `fifo'");
1281		return 1;
1282	}
1283	DIP_SET(curinode, mode, iswap16((mode & ~IFMT) | type));
1284	inodirty();
1285	printactive();
1286	return 0;
1287}
1288
1289CMDFUNC(chmode)
1290{
1291	long    modebits;
1292	char   *cp;
1293	uint16_t mode;
1294
1295	if (!checkactive())
1296		return 1;
1297
1298	modebits = strtol(argv[1], &cp, 8);
1299	if (cp == argv[1] || *cp != '\0') {
1300		warnx("bad modebits `%s'", argv[1]);
1301		return 1;
1302	}
1303	mode = iswap16(DIP(curinode, mode));
1304	DIP_SET(curinode, mode, iswap16((mode & ~07777) | modebits));
1305	inodirty();
1306	printactive();
1307	return 0;
1308}
1309
1310CMDFUNC(chlen)
1311{
1312	off_t    len;
1313	char   *cp;
1314
1315	if (!checkactive())
1316		return 1;
1317
1318	len = strtoull(argv[1], &cp, 0);
1319	if (cp == argv[1] || *cp != '\0' || len < 0) {
1320		warnx("bad length '%s'", argv[1]);
1321		return 1;
1322	}
1323	DIP_SET(curinode, size, iswap64(len));
1324	inodirty();
1325	printactive();
1326	return 0;
1327}
1328
1329CMDFUNC(chaflags)
1330{
1331	u_long  flags;
1332	char   *cp;
1333
1334	if (!checkactive())
1335		return 1;
1336
1337	flags = strtoul(argv[1], &cp, 0);
1338	if (cp == argv[1] || *cp != '\0') {
1339		warnx("bad flags `%s'", argv[1]);
1340		return 1;
1341	}
1342	if (flags > UINT_MAX) {
1343		warnx("flags set beyond 32-bit range of field (0x%lx)",
1344		    flags);
1345		return (1);
1346	}
1347	DIP_SET(curinode, flags, iswap32(flags));
1348	inodirty();
1349	printactive();
1350	return 0;
1351}
1352
1353CMDFUNC(chgen)
1354{
1355	long    gen;
1356	char   *cp;
1357
1358	if (!checkactive())
1359		return 1;
1360
1361	gen = strtol(argv[1], &cp, 0);
1362	if (cp == argv[1] || *cp != '\0') {
1363		warnx("bad gen `%s'", argv[1]);
1364		return 1;
1365	}
1366	if (gen > INT_MAX || gen < INT_MIN) {
1367		warnx("gen set beyond 32-bit range of field (0x%lx)", gen);
1368		return (1);
1369	}
1370	DIP_SET(curinode, gen, iswap32(gen));
1371	inodirty();
1372	printactive();
1373	return 0;
1374}
1375
1376CMDFUNC(chextsize)
1377{
1378	uint32_t extsize;
1379	char *cp;
1380
1381	if (!is_ufs2)
1382		return 1;
1383	if (!checkactive())
1384		return 1;
1385
1386	extsize = strtol(argv[1], &cp, 0);
1387	if (cp == argv[1] || *cp != '\0') {
1388		warnx("bad extsize `%s'", argv[1]);
1389		return 1;
1390	}
1391
1392	curinode->dp2.di_extsize = extsize;
1393	inodirty();
1394	printactive();
1395	return 0;
1396}
1397
1398CMDFUNC(chblocks)
1399{
1400	uint64_t blocks;
1401	char *cp;
1402
1403	if (!checkactive())
1404		return 1;
1405
1406	blocks = strtoll(argv[1], &cp, 0);
1407	if (cp == argv[1] || *cp != '\0') {
1408		warnx("bad blocks `%s'", argv[1]);
1409		return 1;
1410	}
1411
1412	DIP_SET(curinode, blocks, blocks);
1413	inodirty();
1414	printactive();
1415	return 0;
1416}
1417
1418CMDFUNC(chdb)
1419{
1420	unsigned int idx;
1421	daddr_t bno;
1422	char *cp;
1423
1424	if (!checkactive())
1425		return 1;
1426
1427	idx = strtoull(argv[1], &cp, 0);
1428	if (cp == argv[1] || *cp != '\0') {
1429		warnx("bad pointer idx `%s'", argv[1]);
1430		return 1;
1431	}
1432	bno = strtoll(argv[2], &cp, 0);
1433	if (cp == argv[2] || *cp != '\0') {
1434		warnx("bad block number `%s'", argv[2]);
1435		return 1;
1436	}
1437	if (idx >= UFS_NDADDR) {
1438		warnx("pointer index %d is out of range", idx);
1439		return 1;
1440	}
1441
1442	DIP_SET(curinode, db[idx], bno);
1443	inodirty();
1444	printactive();
1445	return 0;
1446}
1447
1448CMDFUNC(chib)
1449{
1450	unsigned int idx;
1451	daddr_t bno;
1452	char *cp;
1453
1454	if (!checkactive())
1455		return 1;
1456
1457	idx = strtoull(argv[1], &cp, 0);
1458	if (cp == argv[1] || *cp != '\0') {
1459		warnx("bad pointer idx `%s'", argv[1]);
1460		return 1;
1461	}
1462	bno = strtoll(argv[2], &cp, 0);
1463	if (cp == argv[2] || *cp != '\0') {
1464		warnx("bad block number `%s'", argv[2]);
1465		return 1;
1466	}
1467	if (idx >= UFS_NIADDR) {
1468		warnx("pointer index %d is out of range", idx);
1469		return 1;
1470	}
1471
1472	DIP_SET(curinode, ib[idx], bno);
1473	inodirty();
1474	printactive();
1475	return 0;
1476}
1477
1478CMDFUNC(chextb)
1479{
1480	unsigned int idx;
1481	daddr_t bno;
1482	char *cp;
1483
1484	if (!checkactive())
1485		return 1;
1486
1487	idx = strtoull(argv[1], &cp, 0);
1488	if (cp == argv[1] || *cp != '\0') {
1489		warnx("bad pointer idx `%s'", argv[1]);
1490		return 1;
1491	}
1492	bno = strtoll(argv[2], &cp, 0);
1493	if (cp == argv[2] || *cp != '\0') {
1494		warnx("bad block number `%s'", argv[2]);
1495		return 1;
1496	}
1497	if (idx >= UFS_NXADDR) {
1498		warnx("pointer index %d is out of range", idx);
1499		return 1;
1500	}
1501
1502	curinode->dp2.di_extb[idx] = bno;
1503	inodirty();
1504	printactive();
1505	return 0;
1506}
1507
1508CMDFUNC(chfreelink)
1509{
1510#if 0
1511	ino_t freelink;
1512	char *cp;
1513
1514	if (!checkactive())
1515		return 1;
1516
1517	freelink = strtoll(argv[1], &cp, 0);
1518	if (cp == argv[1] || *cp != '\0') {
1519		warnx("bad freelink `%s'", argv[1]);
1520		return 1;
1521	}
1522
1523	DIP_SET(curinode, freelink, freelink);
1524	inodirty();
1525	printactive();
1526#endif
1527	return 0;
1528}
1529
1530CMDFUNC(linkcount)
1531{
1532	int     lcnt;
1533	char   *cp;
1534
1535	if (!checkactive())
1536		return 1;
1537
1538	lcnt = strtol(argv[1], &cp, 0);
1539	if (cp == argv[1] || *cp != '\0') {
1540		warnx("bad link count `%s'", argv[1]);
1541		return 1;
1542	}
1543	if (lcnt > USHRT_MAX || lcnt < 0) {
1544		warnx("max link count is %d", USHRT_MAX);
1545		return 1;
1546	}
1547	DIP_SET(curinode, nlink, iswap16(lcnt));
1548	inodirty();
1549	printactive();
1550	return 0;
1551}
1552
1553CMDFUNC(chowner)
1554{
1555	unsigned long uid;
1556	char   *cp;
1557	struct passwd *pwd;
1558
1559	if (!checkactive())
1560		return 1;
1561
1562	uid = strtoul(argv[1], &cp, 0);
1563	if (cp == argv[1] || *cp != '\0') {
1564		/* try looking up name */
1565		if ((pwd = getpwnam(argv[1])) != 0) {
1566			uid = pwd->pw_uid;
1567		} else {
1568			warnx("bad uid `%s'", argv[1]);
1569			return 1;
1570		}
1571	}
1572	if (!is_ufs2 && sblock->fs_old_inodefmt < FS_44INODEFMT)
1573		curinode->dp1.di_ouid = iswap32(uid);
1574	else
1575		DIP_SET(curinode, uid, iswap32(uid));
1576	inodirty();
1577	printactive();
1578	return 0;
1579}
1580
1581CMDFUNC(chgroup)
1582{
1583	unsigned long gid;
1584	char   *cp;
1585	struct group *grp;
1586
1587	if (!checkactive())
1588		return 1;
1589
1590	gid = strtoul(argv[1], &cp, 0);
1591	if (cp == argv[1] || *cp != '\0') {
1592		if ((grp = getgrnam(argv[1])) != 0) {
1593			gid = grp->gr_gid;
1594		} else {
1595			warnx("bad gid `%s'", argv[1]);
1596			return 1;
1597		}
1598	}
1599	if (!is_ufs2 && sblock->fs_old_inodefmt < FS_44INODEFMT)
1600		curinode->dp1.di_ogid = iswap32(gid);
1601	else
1602		DIP_SET(curinode, gid, iswap32(gid));
1603	inodirty();
1604	printactive();
1605	return 0;
1606}
1607
1608static int
1609dotime(char *name, int64_t *rsec, int32_t *rnsec)
1610{
1611	char   *p, *val;
1612	struct tm t;
1613	int64_t sec;
1614	int32_t nsec;
1615	p = strchr(name, '.');
1616	if (p) {
1617		*p = '\0';
1618		nsec = strtoul(++p, &val, 0);
1619		if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
1620			warnx("invalid nanoseconds");
1621			goto badformat;
1622		}
1623	} else
1624		nsec = 0;
1625	if (strlen(name) != 14) {
1626badformat:
1627		warnx("date format: YYYYMMDDHHMMSS[.nsec]");
1628		return 1;
1629	}
1630	for (p = name; *p; p++)
1631		if (*p < '0' || *p > '9')
1632			goto badformat;
1633
1634	p = name;
1635#define VAL() ((*p++) - '0')
1636	t.tm_year = VAL();
1637	t.tm_year = VAL() + t.tm_year * 10;
1638	t.tm_year = VAL() + t.tm_year * 10;
1639	t.tm_year = VAL() + t.tm_year * 10 - 1900;
1640	t.tm_mon = VAL();
1641	t.tm_mon = VAL() + t.tm_mon * 10 - 1;
1642	t.tm_mday = VAL();
1643	t.tm_mday = VAL() + t.tm_mday * 10;
1644	t.tm_hour = VAL();
1645	t.tm_hour = VAL() + t.tm_hour * 10;
1646	t.tm_min = VAL();
1647	t.tm_min = VAL() + t.tm_min * 10;
1648	t.tm_sec = VAL();
1649	t.tm_sec = VAL() + t.tm_sec * 10;
1650	t.tm_isdst = -1;
1651
1652	sec = mktime(&t);
1653	if (sec == -1) {
1654		warnx("date/time out of range");
1655		return 1;
1656	}
1657	*rsec = iswap64(sec);
1658	*rnsec = iswap32(nsec);
1659	return 0;
1660}
1661
1662CMDFUNC(chmtime)
1663{
1664	int64_t rsec;
1665	int32_t nsec;
1666
1667	if (!checkactive())
1668		return 1;
1669	if (dotime(argv[1], &rsec, &nsec))
1670		return 1;
1671	DIP_SET(curinode, mtime, rsec);
1672	DIP_SET(curinode, mtimensec, nsec);
1673	inodirty();
1674	printactive();
1675	return 0;
1676}
1677
1678CMDFUNC(chatime)
1679{
1680	int64_t rsec;
1681	int32_t nsec;
1682
1683	if (!checkactive())
1684		return 1;
1685	if (dotime(argv[1], &rsec, &nsec))
1686		return 1;
1687	DIP_SET(curinode, atime, rsec);
1688	DIP_SET(curinode, atimensec, nsec);
1689	inodirty();
1690	printactive();
1691	return 0;
1692}
1693
1694CMDFUNC(chctime)
1695{
1696	int64_t rsec;
1697	int32_t nsec;
1698
1699	if (!checkactive())
1700		return 1;
1701	if (dotime(argv[1], &rsec, &nsec))
1702		return 1;
1703	DIP_SET(curinode, ctime, rsec);
1704	DIP_SET(curinode, ctimensec, nsec);
1705	inodirty();
1706	printactive();
1707	return 0;
1708}
1709
1710CMDFUNC(chbirthtime)
1711{
1712	int64_t rsec;
1713	int32_t nsec;
1714
1715	if (!is_ufs2) {
1716		warnx("birthtime can only be set in ufs2");
1717		return 1;
1718	}
1719	if (!checkactive())
1720		return 1;
1721
1722	if (dotime(argv[1], &rsec, &nsec))
1723		return 1;
1724	curinode->dp2.di_birthtime = rsec;
1725	curinode->dp2.di_birthnsec = nsec;
1726	inodirty();
1727	printactive();
1728	return 0;
1729}
1730
1731CMDFUNC(iptrs)
1732{
1733	int i;
1734
1735	if (!checkactive())
1736		return 1;
1737	for (i = 0; i < UFS_NDADDR; i++)
1738		printf("di_db %d %ju\n", i, DIP(curinode, db[i]));
1739	for (i = 0; i < UFS_NIADDR; i++)
1740		printf("di_ib %d %ju\n", i, DIP(curinode, ib[i]));
1741	if (is_ufs2)
1742		for (i = 0; i < UFS_NXADDR; i++)
1743			printf("di_extb %d %ju\n", i, curinode->dp2.di_extb[i]);
1744	return 0;
1745}
1746
1747CMDFUNC(saveea)
1748{
1749	struct wrinfo wrinfo;
1750	uint64_t blkno = 0;
1751
1752	if (!is_ufs2) {
1753		warnx("dumping extattrs is only supported for ufs2");
1754		return 1;
1755	}
1756	if (!checkactive())
1757		return 1;
1758
1759	wrinfo.fd = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0644);
1760	if (wrinfo.fd == -1) {
1761		warn("unable to create file %s", argv[1]);
1762		return 0;
1763	}
1764
1765	wrinfo.size = iswap32(curinode->dp2.di_extsize);
1766	wrinfo.written_size = 0;
1767	print_blks64(curinode->dp2.di_extb, UFS_NXADDR, &blkno, &wrinfo);
1768	return 0;
1769}
1770