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