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