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