Home | History | Annotate | Line # | Download | only in fsdb
      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 
     65 struct bufarea bufhead;
     66 struct bufarea sblk;
     67 struct bufarea asblk;
     68 struct bufarea cgblk;
     69 struct bufarea appleufsblk;
     70 struct bufarea *pdirbp;
     71 struct bufarea *pbp;
     72 struct fs *sblock;
     73 struct fs *altsblock;
     74 struct cg *cgrp;
     75 struct fs *sblocksave;
     76 struct dups *duplist;
     77 struct dups *muldup;
     78 struct zlncnt *zlnhead;
     79 struct inoinfo **inphead, **inpsort;
     80 long numdirs, dirhash, listmax, inplast;
     81 struct uquot_hash *uquot_user_hash;
     82 struct uquot_hash *uquot_group_hash;
     83 uint8_t q2h_hash_shift;
     84 uint16_t q2h_hash_mask;
     85 struct inostatlist *inostathead;
     86 long	dev_bsize;
     87 long	secsize;
     88 char	nflag;
     89 char	yflag;
     90 int	Uflag;
     91 int	bflag;
     92 int	debug;
     93 int	zflag;
     94 int	cvtlevel;
     95 int	eaflag;
     96 int	doinglevel1;
     97 int	doinglevel2;
     98 int	doing2ea;
     99 int	doing2noea;
    100 int	newinofmt;
    101 char	usedsoftdep;
    102 int	preen;
    103 int	forceimage;
    104 int	is_ufs2;
    105 int	is_ufs2ea;
    106 int	markclean;
    107 char	havesb;
    108 char	skipclean;
    109 int	fsmodified;
    110 int	fsreadfd;
    111 int	fswritefd;
    112 int	rerun;
    113 char	resolved;
    114 int	endian;
    115 int	doswap;
    116 int	needswap;
    117 int	do_blkswap;
    118 int	do_dirswap;
    119 int	isappleufs;
    120 daddr_t maxfsblock;
    121 char	*blockmap;
    122 ino_t	maxino;
    123 int	dirblksiz;
    124 daddr_t n_blks;
    125 ino_t n_files;
    126 long countdirs;
    127 int	got_siginfo;
    128 struct	ufs1_dinode ufs1_zino;
    129 struct	ufs2_dinode ufs2_zino;
    130 
    131 /* Used to keep state for "saveblks" command.  */
    132 struct wrinfo {
    133 	off_t size;
    134 	off_t written_size;
    135 	int fd;
    136 };
    137 
    138 __dead static void usage(void);
    139 static int cmdloop(void);
    140 static char *prompt(EditLine *);
    141 static int scannames(struct inodesc *);
    142 static int dolookup(char *);
    143 static int chinumfunc(struct inodesc *);
    144 static int chnamefunc(struct inodesc *);
    145 static int chreclenfunc(struct inodesc *);
    146 static int dotime(char *, int64_t *, int32_t *);
    147 static void print_blks32(int32_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp);
    148 static void print_blks64(int64_t *buf, int size, uint64_t *blknum, struct wrinfo *wrp);
    149 static void print_indirblks32(uint32_t blk, int ind_level,
    150     uint64_t *blknum, struct wrinfo *wrp);
    151 static void print_indirblks64(uint64_t blk, int ind_level,
    152     uint64_t *blknum, struct wrinfo *wrp);
    153 static int compare_blk32(uint32_t *, uint32_t);
    154 static int compare_blk64(uint64_t *, uint64_t);
    155 static int founddatablk(uint64_t);
    156 static int find_blks32(uint32_t *buf, int size, uint32_t *blknum);
    157 static int find_blks64(uint64_t *buf, int size, uint64_t *blknum);
    158 static int find_indirblks32(uint32_t blk, int ind_level,
    159 						uint32_t *blknum);
    160 static int find_indirblks64(uint64_t blk, int ind_level,
    161 						uint64_t *blknum);
    162 
    163 union dinode *curinode;
    164 ino_t   curinum;
    165 
    166 static void
    167 usage(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  */
    177 int
    178 main(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 
    238 CMDFUNC(helpfn);
    239 CMDFUNC(focus);			/* focus on inode */
    240 CMDFUNC(active);		/* print active inode */
    241 CMDFUNC(focusname);		/* focus by name */
    242 CMDFUNC(zapi);			/* clear inode */
    243 CMDFUNC(uplink);		/* incr link */
    244 CMDFUNC(downlink);		/* decr link */
    245 CMDFUNC(linkcount);		/* set link count */
    246 CMDFUNC(quit);			/* quit */
    247 CMDFUNC(ls);			/* list directory */
    248 CMDFUNC(blks);			/* list blocks */
    249 CMDFUNC(findblk);		/* find block */
    250 CMDFUNC(rm);			/* remove name */
    251 CMDFUNC(ln);			/* add name */
    252 CMDFUNC(newtype);		/* change type */
    253 CMDFUNC(chmode);		/* change mode */
    254 CMDFUNC(chlen);			/* change length */
    255 CMDFUNC(chaflags);		/* change flags */
    256 CMDFUNC(chgen);			/* change generation */
    257 CMDFUNC(chowner);		/* change owner */
    258 CMDFUNC(chgroup);		/* Change group */
    259 CMDFUNC(back);			/* pop back to last ino */
    260 CMDFUNC(chmtime);		/* Change mtime */
    261 CMDFUNC(chctime);		/* Change ctime */
    262 CMDFUNC(chatime);		/* Change atime */
    263 CMDFUNC(chbirthtime);		/* Change birthtime */
    264 CMDFUNC(chinum);		/* Change inode # of dirent */
    265 CMDFUNC(chname);		/* Change dirname of dirent */
    266 CMDFUNC(chreclen);		/* Change reclen of dirent */
    267 CMDFUNC(chextsize);		/* Change extsize */
    268 CMDFUNC(chblocks);		/* Change blocks */
    269 CMDFUNC(chdb);			/* Change direct block pointer */
    270 CMDFUNC(chib);			/* Change indirect block pointer */
    271 CMDFUNC(chextb);		/* Change extattr block pointer */
    272 CMDFUNC(chfreelink);		/* Change freelink pointer */
    273 CMDFUNC(iptrs);			/* print raw block pointers for active inode */
    274 CMDFUNC(saveea);		/* Save extattrs */
    275 
    276 static 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 
    326 static int
    327 helpfn(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 
    340 static char *
    341 prompt(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 
    350 static int
    351 cmdloop(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 
    422 static 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  */
    435 CMDFUNC(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 
    448 CMDFUNC(back)
    449 {
    450 	curinum = ocurrent;
    451 	curinode = ginode(curinum);
    452 	printactive();
    453 	return 0;
    454 }
    455 
    456 CMDFUNC(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 
    471 CMDFUNC(active)
    472 {
    473 	printactive();
    474 	return 0;
    475 }
    476 
    477 CMDFUNC(quit)
    478 {
    479 	return -1;
    480 }
    481 
    482 CMDFUNC(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 
    497 CMDFUNC(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 
    512 static 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 
    530 static int diroff;
    531 static int slot;
    532 
    533 static int
    534 scannames(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 
    546 CMDFUNC(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 
    563 CMDFUNC(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 
    615 static int findblk_numtofind;
    616 static int wantedblksize;
    617 CMDFUNC(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 	}
    728 end:
    729 	if (wantedblk32)
    730 		free(wantedblk32);
    731 	if (wantedblk64)
    732 		free(wantedblk64);
    733 	curinum = ocurrent;
    734 	curinode = ginode(curinum);
    735 	return 0;
    736 }
    737 
    738 static int
    739 compare_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 
    751 static int
    752 compare_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 
    764 static int
    765 founddatablk(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 
    776 static int
    777 find_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 
    791 static int
    792 find_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 
    822 static int
    823 find_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 
    837 static int
    838 find_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 
    867 static int
    868 writefileblk(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 
    893 static void
    894 print_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 
    921 static void
    922 print_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 
    953 static void
    954 print_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 
    982 static void
    983 print_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 
   1011 static int
   1012 dolookup(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 
   1034 CMDFUNC(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 
   1064 CMDFUNC(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 
   1084 CMDFUNC(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 
   1100 static long slotcount, desired;
   1101 
   1102 static int
   1103 chinumfunc(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 
   1114 CMDFUNC(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 
   1144 static int
   1145 chnamefunc(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 
   1164 CMDFUNC(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 
   1199 static int
   1200 chreclenfunc(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 
   1211 CMDFUNC(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 
   1246 static 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 
   1259 CMDFUNC(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 
   1289 CMDFUNC(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 
   1310 CMDFUNC(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 
   1329 CMDFUNC(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 
   1353 CMDFUNC(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 
   1376 CMDFUNC(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 
   1398 CMDFUNC(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 
   1418 CMDFUNC(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 
   1448 CMDFUNC(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 
   1478 CMDFUNC(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 
   1508 CMDFUNC(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 
   1530 CMDFUNC(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 
   1553 CMDFUNC(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 
   1581 CMDFUNC(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 
   1608 static int
   1609 dotime(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) {
   1626 badformat:
   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 
   1662 CMDFUNC(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 
   1678 CMDFUNC(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 
   1694 CMDFUNC(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 
   1710 CMDFUNC(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 
   1731 CMDFUNC(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 
   1747 CMDFUNC(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