xref: /openbsd-src/sbin/fsdb/fsdb.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: fsdb.c,v 1.31 2016/09/09 15:37:14 tb Exp $	*/
2 /*	$NetBSD: fsdb.c,v 1.7 1997/01/11 06:50:53 lukem Exp $	*/
3 
4 /*-
5  * Copyright (c) 1996 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by John T. Kohl.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/mount.h>
36 #include <ctype.h>
37 #include <err.h>
38 #include <fcntl.h>
39 #include <grp.h>
40 #include <histedit.h>
41 #include <limits.h>
42 #include <pwd.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 #include <ufs/ufs/dinode.h>
49 #include <ufs/ufs/dir.h>
50 #include <ufs/ffs/fs.h>
51 
52 #include "fsdb.h"
53 #include "fsck.h"
54 #include "extern.h"
55 
56 extern char *__progname;	/* from crt0.o */
57 
58 int main(int, char *[]);
59 static void usage(void);
60 static int cmdloop(void);
61 static int helpfn(int, char *[]);
62 static char *prompt(EditLine *);
63 static int scannames(struct inodesc *);
64 static int dolookup(char *);
65 static int chinumfunc(struct inodesc *);
66 static int chnamefunc(struct inodesc *);
67 static int dotime(char *, time_t *, int32_t *);
68 
69 int returntosingle = 0;
70 union dinode *curinode;
71 ino_t curinum;
72 
73 static void
74 usage(void)
75 {
76 	fprintf(stderr, "usage: %s [-d] -f fsname\n", __progname);
77 	exit(1);
78 }
79 
80 /*
81  * We suck in lots of fsck code, and just pick & choose the stuff we want.
82  *
83  * fsreadfd is set up to read from the file system, fswritefd to write to
84  * the file system.
85  */
86 int
87 main(int argc, char *argv[])
88 {
89 	int ch, rval;
90 	char *fsys = NULL;
91 
92 	while (-1 != (ch = getopt(argc, argv, "f:d"))) {
93 		switch (ch) {
94 		case 'f':
95 			fsys = optarg;
96 			break;
97 		case 'd':
98 			debug++;
99 			break;
100 		default:
101 			usage();
102 		}
103 	}
104 	if (fsys == NULL)
105 		usage();
106 	if (!setup(fsys, 1))
107 		errx(1, "cannot set up file system `%s'", fsys);
108 	printf("Editing file system `%s'\nLast Mounted on %s\n", fsys,
109 	    sblock.fs_fsmnt);
110 	rval = cmdloop();
111 	sblock.fs_clean = 0;		/* mark it dirty */
112 	sbdirty();
113 	ckfini(0);
114 	printf("*** FILE SYSTEM MARKED DIRTY\n");
115 	printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
116 	printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
117 	exit(rval);
118 }
119 
120 #define CMDFUNC(func) static int func(int argc, char *argv[])
121 #define CMDFUNCSTART(func) static int func(int argc, char *argv[])
122 
123 CMDFUNC(helpfn);
124 CMDFUNC(focus);				/* focus on inode */
125 CMDFUNC(active);			/* print active inode */
126 CMDFUNC(focusname);			/* focus by name */
127 CMDFUNC(zapi);				/* clear inode */
128 CMDFUNC(uplink);			/* incr link */
129 CMDFUNC(downlink);			/* decr link */
130 CMDFUNC(linkcount);			/* set link count */
131 CMDFUNC(quit);				/* quit */
132 CMDFUNC(ls);				/* list directory */
133 CMDFUNC(rm);				/* remove name */
134 CMDFUNC(ln);				/* add name */
135 CMDFUNC(newtype);			/* change type */
136 CMDFUNC(chmode);			/* change mode */
137 CMDFUNC(chlen);				/* change length */
138 CMDFUNC(chaflags);			/* change flags */
139 CMDFUNC(chgen);				/* change generation */
140 CMDFUNC(chowner);			/* change owner */
141 CMDFUNC(chgroup);			/* Change group */
142 CMDFUNC(back);				/* pop back to last ino */
143 CMDFUNC(chmtime);			/* Change mtime */
144 CMDFUNC(chctime);			/* Change ctime */
145 CMDFUNC(chatime);			/* Change atime */
146 CMDFUNC(chinum);			/* Change inode # of dirent */
147 CMDFUNC(chname);			/* Change dirname of dirent */
148 
149 static struct cmdtable cmds[] = {
150 	{ "help", "Print out help", 1, 1, helpfn },
151 	{ "?", "Print out help", 1, 1, helpfn },
152 	{ "inode", "Set active inode to INUM", 2, 2, focus },
153 	{ "clri", "Clear inode INUM", 2, 2, zapi },
154 	{ "lookup", "Set active inode by looking up NAME", 2, 2, focusname },
155 	{ "cd", "Set active inode by looking up NAME", 2, 2, focusname },
156 	{ "back", "Go to previous active inode", 1, 1, back },
157 	{ "active", "Print active inode", 1, 1, active },
158 	{ "print", "Print active inode", 1, 1, active },
159 	{ "uplink", "Increment link count", 1, 1, uplink },
160 	{ "downlink", "Decrement link count", 1, 1, downlink },
161 	{ "linkcount", "Set link count to COUNT", 2, 2, linkcount },
162 	{ "ls", "List current inode as directory", 1, 1, ls },
163 	{ "rm", "Remove NAME from current inode directory", 2, 2, rm },
164 	{ "del", "Remove NAME from current inode directory", 2, 2, rm },
165 	{ "ln", "Hardlink INO into current inode directory as NAME", 3, 3, ln },
166 	{ "chinum", "Change dir entry number INDEX to INUM", 3, 3, chinum },
167 	{ "chname", "Change dir entry number INDEX to NAME", 3, 3, chname },
168 	{ "chtype", "Change type of current inode to TYPE", 2, 2, newtype },
169 	{ "chmod", "Change mode of current inode to MODE", 2, 2, chmode },
170 	{ "chown", "Change owner of current inode to OWNER", 2, 2, chowner },
171 	{ "chlen", "Change length of current inode to LENGTH", 2, 2, chlen },
172 	{ "chgrp", "Change group of current inode to GROUP", 2, 2, chgroup },
173 	{ "chflags", "Change flags of current inode to FLAGS", 2, 2, chaflags },
174 	{ "chgen", "Change generation number of current inode to GEN", 2, 2, chgen },
175 	{ "mtime", "Change mtime of current inode to MTIME", 2, 2, chmtime },
176 	{ "ctime", "Change ctime of current inode to CTIME", 2, 2, chctime },
177 	{ "atime", "Change atime of current inode to ATIME", 2, 2, chatime },
178 	{ "quit", "Exit", 1, 1, quit },
179 	{ "q", "Exit", 1, 1, quit },
180 	{ "exit", "Exit", 1, 1, quit },
181 	{ NULL, 0, 0, 0 },
182 };
183 
184 static int
185 helpfn(int argc, char *argv[])
186 {
187 	struct cmdtable *cmdtp;
188 
189 	printf("Commands are:\n%-10s %5s %5s   %s\n",
190 	    "command", "min argc", "max argc", "what");
191 
192 	for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
193 		printf("%-10s %5u %5u   %s\n",
194 		    cmdtp->cmd, cmdtp->minargc, cmdtp->maxargc, cmdtp->helptxt);
195 	return 0;
196 }
197 
198 static char *
199 prompt(EditLine *el)
200 {
201 	static char pstring[64];
202 
203 	snprintf(pstring, sizeof(pstring), "fsdb (inum: %llu)> ",
204 	    (unsigned long long)curinum);
205 	return pstring;
206 }
207 
208 
209 static int
210 cmdloop(void)
211 {
212 	char *line = NULL;
213 	const char *elline;
214 	int cmd_argc, rval = 0, known;
215 #define scratch known
216 	char **cmd_argv;
217 	struct cmdtable *cmdp;
218 	History *hist;
219 	EditLine *elptr;
220 	HistEvent hev;
221 
222 	curinode = ginode(ROOTINO);
223 	curinum = ROOTINO;
224 	printactive();
225 
226 	hist = history_init();
227 	history(hist, &hev, H_SETSIZE, 100);	/* 100 elt history buffer */
228 
229 	elptr = el_init(__progname, stdin, stdout, stderr);
230 	el_set(elptr, EL_EDITOR, "emacs");
231 	el_set(elptr, EL_PROMPT, prompt);
232 	el_set(elptr, EL_HIST, history, hist);
233 	el_source(elptr, NULL);
234 
235 	while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
236 		if (debug)
237 			printf("command `%s'\n", line);
238 
239 		history(hist, &hev, H_ENTER, elline);
240 
241 		line = strdup(elline);
242 		if (line == NULL)
243 			errx(1, "out of memory");
244 		cmd_argv = crack(line, &cmd_argc);
245 		if (cmd_argc) {
246 			/*
247 			 * el_parse returns -1 to signal that it's not been handled
248 			 * internally.
249 			 */
250 			if (el_parse(elptr, cmd_argc, (const char **)cmd_argv) != -1)
251 				continue;
252 			known = 0;
253 			for (cmdp = cmds; cmdp->cmd; cmdp++) {
254 				if (!strcmp(cmdp->cmd, cmd_argv[0])) {
255 					if (cmd_argc >= cmdp->minargc &&
256 					    cmd_argc <= cmdp->maxargc)
257 						rval = (*cmdp->handler)(cmd_argc,
258 						    cmd_argv);
259 					else
260 						rval = argcount(cmdp,
261 						    cmd_argc, cmd_argv);
262 					known = 1;
263 					break;
264 				}
265 			}
266 			if (!known) {
267 				warnx("unknown command `%s'", cmd_argv[0]);
268 				rval = 1;
269 			}
270 		} else
271 			rval = 0;
272 		free(line);
273 		if (rval < 0)
274 			return rval;
275 		if (rval)
276 			warnx("rval was %d", rval);
277 	}
278 	el_end(elptr);
279 	history_end(hist);
280 	return rval;
281 }
282 
283 static ino_t ocurrent;
284 
285 #define GETINUM(ac,inum)    inum = strtoull(argv[ac], &cp, 0); \
286 	if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
287 		printf("inode %llu out of range; range is [%llu,%llu]\n", \
288 		    (unsigned long long)inum, (unsigned long long)ROOTINO, \
289 		    (unsigned long long)maxino); \
290 		return 1; \
291 	}
292 
293 /*
294  * Focus on given inode number
295  */
296 CMDFUNCSTART(focus)
297 {
298 	ino_t inum;
299 	char *cp;
300 
301 	GETINUM(1,inum);
302 	curinode = ginode(inum);
303 	ocurrent = curinum;
304 	curinum = inum;
305 	printactive();
306 	return 0;
307 }
308 
309 CMDFUNCSTART(back)
310 {
311 	curinum = ocurrent;
312 	curinode = ginode(curinum);
313 	printactive();
314 	return 0;
315 }
316 
317 CMDFUNCSTART(zapi)
318 {
319 	ino_t inum;
320 	union dinode *dp;
321 	char *cp;
322 
323 	GETINUM(1,inum);
324 	dp = ginode(inum);
325 	clearinode(dp);
326 	inodirty();
327 	if (curinode)			/* re-set after potential change */
328 		curinode = ginode(curinum);
329 	return 0;
330 }
331 
332 CMDFUNCSTART(active)
333 {
334 	printactive();
335 	return 0;
336 }
337 
338 
339 CMDFUNCSTART(quit)
340 {
341 	return -1;
342 }
343 
344 CMDFUNCSTART(uplink)
345 {
346 	if (!checkactive())
347 		return 1;
348 	DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) + 1);
349 	printf("inode %llu link count now %d\n",
350 	    (unsigned long long)curinum, DIP(curinode, di_nlink));
351 	inodirty();
352 	return 0;
353 }
354 
355 CMDFUNCSTART(downlink)
356 {
357 	if (!checkactive())
358 		return 1;
359 	DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) - 1);
360 	printf("inode %llu link count now %d\n",
361 	    (unsigned long long)curinum, DIP(curinode, di_nlink));
362 	inodirty();
363 	return 0;
364 }
365 
366 static const char *typename[] = {
367 	"unknown",
368 	"fifo",
369 	"char special",
370 	"unregistered #3",
371 	"directory",
372 	"unregistered #5",
373 	"blk special",
374 	"unregistered #7",
375 	"regular",
376 	"unregistered #9",
377 	"symlink",
378 	"unregistered #11",
379 	"socket",
380 	"unregistered #13",
381 	"whiteout",
382 };
383 
384 static int slot;
385 
386 static int
387 scannames(struct inodesc *idesc)
388 {
389 	struct direct *dirp = idesc->id_dirp;
390 
391 	printf("slot %d ino %llu reclen %d: %s, `%.*s'\n",
392 	    slot++, (unsigned long long)dirp->d_ino, dirp->d_reclen,
393 	    typename[dirp->d_type], dirp->d_namlen, dirp->d_name);
394 	return (KEEPON);
395 }
396 
397 CMDFUNCSTART(ls)
398 {
399 	struct inodesc idesc;
400 	checkactivedir();			/* let it go on anyway */
401 
402 	slot = 0;
403 	idesc.id_number = curinum;
404 	idesc.id_func = scannames;
405 	idesc.id_type = DATA;
406 	idesc.id_fix = IGNORE;
407 	ckinode(curinode, &idesc);
408 	curinode = ginode(curinum);
409 
410 	return 0;
411 }
412 
413 static int
414 dolookup(char *name)
415 {
416 	struct inodesc idesc;
417 
418 	if (!checkactivedir())
419 		return 0;
420 	idesc.id_number = curinum;
421 	idesc.id_func = findino;
422 	idesc.id_name = name;
423 	idesc.id_type = DATA;
424 	idesc.id_fix = IGNORE;
425 	if (ckinode(curinode, &idesc) & FOUND) {
426 		curinum = idesc.id_parent;
427 		curinode = ginode(curinum);
428 		printactive();
429 		return 1;
430 	} else {
431 		warnx("name `%s' not found in current inode directory", name);
432 		return 0;
433 	}
434 }
435 
436 CMDFUNCSTART(focusname)
437 {
438 	char *p, *val;
439 
440 	if (!checkactive())
441 		return 1;
442 
443 	ocurrent = curinum;
444 
445 	if (argv[1][0] == '/') {
446 		curinum = ROOTINO;
447 		curinode = ginode(ROOTINO);
448 	} else {
449 		if (!checkactivedir())
450 		    return 1;
451 	}
452 	for (p = argv[1]; p != NULL;) {
453 		while ((val = strsep(&p, "/")) != NULL && *val == '\0')
454 			continue;
455 		if (val) {
456 			printf("component `%s': ", val);
457 			fflush(stdout);
458 			if (!dolookup(val)) {
459 				curinode = ginode(curinum);
460 				return(1);
461 			}
462 		}
463 	}
464 	return 0;
465 }
466 
467 CMDFUNCSTART(ln)
468 {
469 	ino_t inum;
470 	int rval;
471 	char *cp;
472 
473 	GETINUM(1,inum);
474 
475 	if (!checkactivedir())
476 		return 1;
477 	rval = makeentry(curinum, inum, argv[2]);
478 	if (rval)
479 		printf("Ino %llu entered as `%s'\n",
480 		    (unsigned long long)inum, argv[2]);
481 	else
482 		printf("could not enter name? weird.\n");
483 	curinode = ginode(curinum);
484 	return rval;
485 }
486 
487 CMDFUNCSTART(rm)
488 {
489 	int rval;
490 
491 	if (!checkactivedir())
492 		return 1;
493 	rval = changeino(curinum, argv[1], 0);
494 	if (rval & ALTERED) {
495 		printf("Name `%s' removed\n", argv[1]);
496 		return 0;
497 	} else {
498 		printf("could not remove name? weird.\n");
499 		return 1;
500 	}
501 }
502 
503 static long slotcount, desired;
504 
505 static int
506 chinumfunc(struct inodesc *idesc)
507 {
508 	struct direct *dirp = idesc->id_dirp;
509 
510 	if (slotcount++ == desired) {
511 	    dirp->d_ino = idesc->id_parent;
512 	    return STOP|ALTERED|FOUND;
513 	}
514 	return KEEPON;
515 }
516 
517 CMDFUNCSTART(chinum)
518 {
519 	char *cp;
520 	ino_t inum;
521 	struct inodesc idesc;
522 
523 	slotcount = 0;
524 	if (!checkactivedir())
525 		return 1;
526 	GETINUM(2,inum);
527 
528 	desired = strtol(argv[1], &cp, 0);
529 	if (cp == argv[1] || *cp != '\0' || desired < 0) {
530 		printf("invalid slot number `%s'\n", argv[1]);
531 		return 1;
532 	}
533 
534 	idesc.id_number = curinum;
535 	idesc.id_func = chinumfunc;
536 	idesc.id_fix = IGNORE;
537 	idesc.id_type = DATA;
538 	idesc.id_parent = inum;		/* XXX convenient hiding place */
539 
540 	if (ckinode(curinode, &idesc) & FOUND)
541 		return 0;
542 	else {
543 		warnx("no %sth slot in current directory", argv[1]);
544 		return 1;
545 	}
546 }
547 
548 static int
549 chnamefunc(struct inodesc *idesc)
550 {
551 	struct direct *dirp = idesc->id_dirp;
552 	struct direct testdir;
553 
554 	if (slotcount++ == desired) {
555 		/* will name fit? */
556 		testdir.d_namlen = strlen(idesc->id_name);
557 		if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
558 			dirp->d_namlen = testdir.d_namlen;
559 			strlcpy(dirp->d_name, idesc->id_name, sizeof dirp->d_name);
560 			return STOP|ALTERED|FOUND;
561 		} else
562 			return STOP|FOUND;	/* won't fit, so give up */
563 	}
564 	return KEEPON;
565 }
566 
567 CMDFUNCSTART(chname)
568 {
569 	int rval;
570 	char *cp;
571 	struct inodesc idesc;
572 
573 	slotcount = 0;
574 	if (!checkactivedir())
575 		return 1;
576 
577 	desired = strtoul(argv[1], &cp, 0);
578 	if (cp == argv[1] || *cp != '\0') {
579 		printf("invalid slot number `%s'\n", argv[1]);
580 		return 1;
581 	}
582 
583 	idesc.id_number = curinum;
584 	idesc.id_func = chnamefunc;
585 	idesc.id_fix = IGNORE;
586 	idesc.id_type = DATA;
587 	idesc.id_name = argv[2];
588 
589 	rval = ckinode(curinode, &idesc);
590 	if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
591 		return 0;
592 	else if (rval & FOUND) {
593 		warnx("new name `%s' does not fit in slot %s", argv[2], argv[1]);
594 		return 1;
595 	} else {
596 		warnx("no %sth slot in current directory", argv[1]);
597 		return 1;
598 	}
599 }
600 
601 static struct typemap {
602 	const char *typename;
603 	int typebits;
604 } typenamemap[]  = {
605 	{"file", IFREG},
606 	{"dir", IFDIR},
607 	{"socket", IFSOCK},
608 	{"fifo", IFIFO},
609 };
610 
611 CMDFUNCSTART(newtype)
612 {
613 	int type;
614 	struct typemap *tp;
615 
616 	if (!checkactive())
617 		return 1;
618 	type = DIP(curinode, di_mode) & IFMT;
619 	for (tp = typenamemap;
620 	    tp < &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)];
621 	    tp++) {
622 		if (!strcmp(argv[1], tp->typename)) {
623 			printf("setting type to %s\n", tp->typename);
624 			type = tp->typebits;
625 			break;
626 		}
627 	}
628 	if (tp == &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)]) {
629 		warnx("type `%s' not known", argv[1]);
630 		warnx("try one of `file', `dir', `socket', `fifo'");
631 		return 1;
632 	}
633 	DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
634 	DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
635 	inodirty();
636 	printactive();
637 	return 0;
638 }
639 
640 CMDFUNCSTART(chmode)
641 {
642 	int rval = 1;
643 	long modebits;
644 	char *cp;
645 
646 	if (!checkactive())
647 		return 1;
648 
649 	modebits = strtol(argv[1], &cp, 8);
650 	if (cp == argv[1] || *cp != '\0' ) {
651 		warnx("bad modebits `%s'", argv[1]);
652 		return 1;
653 	}
654 
655 	DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
656 	DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
657 	inodirty();
658 	printactive();
659 	return rval;
660 }
661 
662 CMDFUNCSTART(chlen)
663 {
664 	int rval = 1;
665 	long len;
666 	char *cp;
667 
668 	if (!checkactive())
669 		return 1;
670 
671 	len = strtol(argv[1], &cp, 0);
672 	if (cp == argv[1] || *cp != '\0' || len < 0) {
673 		warnx("bad length '%s'", argv[1]);
674 		return 1;
675 	}
676 
677 	DIP_SET(curinode, di_size, len);
678 	inodirty();
679 	printactive();
680 	return rval;
681 }
682 
683 CMDFUNCSTART(chaflags)
684 {
685 	int rval = 1;
686 	u_long flags;
687 	char *cp;
688 
689 	if (!checkactive())
690 		return 1;
691 
692 	flags = strtoul(argv[1], &cp, 0);
693 	if (cp == argv[1] || *cp != '\0' ) {
694 		warnx("bad flags `%s'", argv[1]);
695 		return 1;
696 	}
697 
698 	if (flags > UINT_MAX) {
699 		warnx("flags set beyond 32-bit range of field (%lx)", flags);
700 		return(1);
701 	}
702 	DIP_SET(curinode, di_flags, flags);
703 	inodirty();
704 	printactive();
705 	return rval;
706 }
707 
708 CMDFUNCSTART(chgen)
709 {
710 	int rval = 1;
711 	long gen;
712 	char *cp;
713 
714 	if (!checkactive())
715 		return 1;
716 
717 	gen = strtol(argv[1], &cp, 0);
718 	if (cp == argv[1] || *cp != '\0' ) {
719 		warnx("bad gen `%s'", argv[1]);
720 		return 1;
721 	}
722 
723 	if (gen > INT_MAX || gen < INT_MIN) {
724 		warnx("gen set beyond 32-bit range of field (%lx)", gen);
725 		return(1);
726 	}
727 	DIP_SET(curinode, di_gen, gen);
728 	inodirty();
729 	printactive();
730 	return rval;
731 }
732 
733 CMDFUNCSTART(linkcount)
734 {
735 	int rval = 1;
736 	int lcnt;
737 	char *cp;
738 
739 	if (!checkactive())
740 		return 1;
741 
742 	lcnt = strtol(argv[1], &cp, 0);
743 	if (cp == argv[1] || *cp != '\0' ) {
744 		warnx("bad link count `%s'", argv[1]);
745 		return 1;
746 	}
747 	if (lcnt > USHRT_MAX || lcnt < 0) {
748 		warnx("max link count is %d", USHRT_MAX);
749 		return 1;
750 	}
751 
752 	DIP_SET(curinode, di_nlink, lcnt);
753 	inodirty();
754 	printactive();
755 	return rval;
756 }
757 
758 CMDFUNCSTART(chowner)
759 {
760 	int rval = 1;
761 	uid_t uid;
762 	char *cp;
763 	struct passwd *pwd;
764 
765 	if (!checkactive())
766 		return 1;
767 
768 	uid = strtoul(argv[1], &cp, 0);
769 	if (cp == argv[1] || *cp != '\0' ) {
770 		/* try looking up name */
771 		if ((pwd = getpwnam(argv[1]))) {
772 			uid = pwd->pw_uid;
773 		} else {
774 			warnx("bad uid `%s'", argv[1]);
775 			return 1;
776 		}
777 	}
778 
779 	DIP_SET(curinode, di_uid, uid);
780 	inodirty();
781 	printactive();
782 	return rval;
783 }
784 
785 CMDFUNCSTART(chgroup)
786 {
787 	int rval = 1;
788 	gid_t gid;
789 	char *cp;
790 	struct group *grp;
791 
792 	if (!checkactive())
793 		return 1;
794 
795 	gid = strtoul(argv[1], &cp, 0);
796 	if (cp == argv[1] || *cp != '\0' ) {
797 		if ((grp = getgrnam(argv[1]))) {
798 			gid = grp->gr_gid;
799 		} else {
800 			warnx("bad gid `%s'", argv[1]);
801 			return 1;
802 		}
803 	}
804 
805 	DIP_SET(curinode, di_gid, gid);
806 	inodirty();
807 	printactive();
808 	return rval;
809 }
810 
811 static int
812 dotime(char *name, time_t *rsec, int32_t *rnsec)
813 {
814 	char *p, *val;
815 	struct tm t;
816 	time_t sec;
817 	int32_t nsec;
818 
819 	p = strchr(name, '.');
820 	if (p) {
821 		*p = '\0';
822 		nsec = strtoul(++p, &val, 0);
823 		if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
824 			warnx("invalid nanoseconds");
825 			goto badformat;
826 		}
827 	} else
828 		nsec = 0;
829 
830 	if (strlen(name) != 14) {
831 badformat:
832 		warnx("date format: YYYYMMDDHHMMSS[.nsec]");
833 		return 1;
834 	}
835 
836 	for (p = name; *p; p++)
837 		if (*p < '0' || *p > '9')
838 			    goto badformat;
839 
840 	p = name;
841 #define VAL() ((*p++) - '0')
842 	bzero(&t, sizeof t);
843 	t.tm_year = VAL();
844 	t.tm_year = VAL() + t.tm_year * 10;
845 	t.tm_year = VAL() + t.tm_year * 10;
846 	t.tm_year = VAL() + t.tm_year * 10 - 1900;
847 	t.tm_mon = VAL();
848 	t.tm_mon = VAL() + t.tm_mon * 10 - 1;
849 	t.tm_mday = VAL();
850 	t.tm_mday = VAL() + t.tm_mday * 10;
851 	t.tm_hour = VAL();
852 	t.tm_hour = VAL() + t.tm_hour * 10;
853 	t.tm_min = VAL();
854 	t.tm_min = VAL() + t.tm_min * 10;
855 	t.tm_sec = VAL();
856 	t.tm_sec = VAL() + t.tm_sec * 10;
857 	t.tm_isdst = -1;
858 
859 	sec = mktime(&t);
860 	if (sec == -1) {
861 		warnx("date/time out of range");
862 		return 1;
863 	}
864 	*rsec = sec;
865 	*rnsec = nsec;
866 	return 0;
867 }
868 
869 CMDFUNCSTART(chmtime)
870 {
871 	time_t rsec;
872 	int32_t nsec;
873 
874 	if (dotime(argv[1], &rsec, &nsec))
875 		return 1;
876 	DIP_SET(curinode, di_mtime, rsec);
877 	DIP_SET(curinode, di_mtimensec, nsec);
878 	inodirty();
879 	printactive();
880 	return 0;
881 }
882 
883 CMDFUNCSTART(chatime)
884 {
885 	time_t rsec;
886 	int32_t nsec;
887 
888 	if (dotime(argv[1], &rsec, &nsec))
889 		return 1;
890 	DIP_SET(curinode, di_atime, rsec);
891 	DIP_SET(curinode, di_atimensec, nsec);
892 	inodirty();
893 	printactive();
894 	return 0;
895 }
896 
897 CMDFUNCSTART(chctime)
898 {
899 	time_t rsec;
900 	int32_t nsec;
901 
902 	if (dotime(argv[1], &rsec, &nsec))
903 		return 1;
904 	DIP_SET(curinode, di_ctime, rsec);
905 	DIP_SET(curinode, di_ctimensec, nsec);
906 	inodirty();
907 	printactive();
908 	return 0;
909 }
910