xref: /openbsd/sbin/fsdb/fsdb.c (revision 133306f0)
1 /*	$OpenBSD: fsdb.c,v 1.7 1999/08/06 20:41:06 deraadt 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  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *        This product includes software developed by the NetBSD
22  *        Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #ifndef lint
41 static char rcsid[] = "$NetBSD: fsdb.c,v 1.4 1996/03/21 17:56:15 jtc Exp $";
42 #endif /* not lint */
43 
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <sys/param.h>
47 #include <sys/time.h>
48 #include <sys/mount.h>
49 #include <ctype.h>
50 #include <err.h>
51 #include <fcntl.h>
52 #include <grp.h>
53 #include <histedit.h>
54 #include <limits.h>
55 #include <pwd.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 
61 #include <ufs/ufs/dinode.h>
62 #include <ufs/ufs/dir.h>
63 #include <ufs/ffs/fs.h>
64 
65 #include "fsdb.h"
66 #include "fsck.h"
67 #include "extern.h"
68 
69 extern char *__progname;	/* from crt0.o */
70 
71 int main __P((int, char *[]));
72 static void usage __P((void));
73 static int cmdloop __P((void));
74 static int helpfn __P((int, char *[]));
75 static char *prompt __P((EditLine *));
76 static int scannames __P((struct inodesc *));
77 static int dolookup __P((char *));
78 static int chinumfunc __P((struct inodesc *));
79 static int chnamefunc __P((struct inodesc *));
80 static int dotime __P((char *, int32_t *, int32_t *));
81 
82 int returntosingle = 0;
83 struct dinode *curinode;
84 ino_t curinum;
85 
86 static void
87 usage()
88 {
89 	fprintf(stderr, "usage: %s [-d] -f <fsname>\n", __progname);
90 	exit(1);
91 }
92 
93 /*
94  * We suck in lots of fsck code, and just pick & choose the stuff we want.
95  *
96  * fsreadfd is set up to read from the file system, fswritefd to write to
97  * the file system.
98  */
99 int
100 main(argc, argv)
101 	int argc;
102 	char *argv[];
103 {
104 	int ch, rval;
105 	char *fsys = NULL;
106 
107 	while (-1 != (ch = getopt(argc, argv, "f:d"))) {
108 		switch (ch) {
109 		case 'f':
110 			fsys = optarg;
111 			break;
112 		case 'd':
113 			debug++;
114 			break;
115 		default:
116 			usage();
117 		}
118 	}
119 	if (fsys == NULL)
120 		usage();
121 	if (!setup(fsys))
122 		errx(1, "cannot set up file system `%s'", fsys);
123 	printf("Editing file system `%s'\nLast Mounted on %s\n", fsys,
124 	       sblock.fs_fsmnt);
125 	rval = cmdloop();
126 	sblock.fs_clean = 0;		/* mark it dirty */
127 	sbdirty();
128 	ckfini(0);
129 	printf("*** FILE SYSTEM MARKED DIRTY\n");
130 	printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
131 	printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
132 	exit(rval);
133 }
134 
135 #define CMDFUNC(func) static int func __P((int argc, char *argv[]))
136 #define CMDFUNCSTART(func) static int func(argc, argv)		\
137 				int argc;		\
138 				char *argv[];
139 
140 CMDFUNC(helpfn);
141 CMDFUNC(focus);				/* focus on inode */
142 CMDFUNC(active);			/* print active inode */
143 CMDFUNC(focusname);			/* focus by name */
144 CMDFUNC(zapi);				/* clear inode */
145 CMDFUNC(uplink);			/* incr link */
146 CMDFUNC(downlink);			/* decr link */
147 CMDFUNC(linkcount);			/* set link count */
148 CMDFUNC(quit);				/* quit */
149 CMDFUNC(ls);				/* list directory */
150 CMDFUNC(rm);				/* remove name */
151 CMDFUNC(ln);				/* add name */
152 CMDFUNC(newtype);			/* change type */
153 CMDFUNC(chmode);			/* change mode */
154 CMDFUNC(chlen);				/* change length */
155 CMDFUNC(chaflags);			/* change flags */
156 CMDFUNC(chgen);				/* change generation */
157 CMDFUNC(chowner);			/* change owner */
158 CMDFUNC(chgroup);			/* Change group */
159 CMDFUNC(back);				/* pop back to last ino */
160 CMDFUNC(chmtime);			/* Change mtime */
161 CMDFUNC(chctime);			/* Change ctime */
162 CMDFUNC(chatime);			/* Change atime */
163 CMDFUNC(chinum);			/* Change inode # of dirent */
164 CMDFUNC(chname);			/* Change dirname of dirent */
165 
166 static struct cmdtable cmds[] = {
167 	{ "help", "Print out help", 1, 1, helpfn },
168 	{ "?", "Print out help", 1, 1, helpfn },
169 	{ "inode", "Set active inode to INUM", 2, 2, focus },
170 	{ "clri", "Clear inode INUM", 2, 2, zapi },
171 	{ "lookup", "Set active inode by looking up NAME", 2, 2, focusname },
172 	{ "cd", "Set active inode by looking up NAME", 2, 2, focusname },
173 	{ "back", "Go to previous active inode", 1, 1, back },
174 	{ "active", "Print active inode", 1, 1, active },
175 	{ "print", "Print active inode", 1, 1, active },
176 	{ "uplink", "Increment link count", 1, 1, uplink },
177 	{ "downlink", "Decrement link count", 1, 1, downlink },
178 	{ "linkcount", "Set link count to COUNT", 2, 2, linkcount },
179 	{ "ls", "List current inode as directory", 1, 1, ls },
180 	{ "rm", "Remove NAME from current inode directory", 2, 2, rm },
181 	{ "del", "Remove NAME from current inode directory", 2, 2, rm },
182 	{ "ln", "Hardlink INO into current inode directory as NAME", 3, 3, ln },
183 	{ "chinum", "Change dir entry number INDEX to INUM", 3, 3, chinum },
184 	{ "chname", "Change dir entry number INDEX to NAME", 3, 3, chname },
185 	{ "chtype", "Change type of current inode to TYPE", 2, 2, newtype },
186 	{ "chmod", "Change mode of current inode to MODE", 2, 2, chmode },
187 	{ "chown", "Change owner of current inode to OWNER", 2, 2, chowner },
188 	{ "chlen", "Change length of current inode to LENGTH", 2, 2, chlen },
189 	{ "chgrp", "Change group of current inode to GROUP", 2, 2, chgroup },
190 	{ "chflags", "Change flags of current inode to FLAGS", 2, 2, chaflags },
191 	{ "chgen", "Change generation number of current inode to GEN", 2, 2, chgen },
192 	{ "mtime", "Change mtime of current inode to MTIME", 2, 2, chmtime },
193 	{ "ctime", "Change ctime of current inode to CTIME", 2, 2, chctime },
194 	{ "atime", "Change atime of current inode to ATIME", 2, 2, chatime },
195 	{ "quit", "Exit", 1, 1, quit },
196 	{ "q", "Exit", 1, 1, quit },
197 	{ "exit", "Exit", 1, 1, quit },
198 	{ NULL, 0, 0, 0 },
199 };
200 
201 static int
202 helpfn(argc, argv)
203 	int argc;
204 	char *argv[];
205 {
206     register struct cmdtable *cmdtp;
207 
208     printf("Commands are:\n%-10s %5s %5s   %s\n",
209 	   "command", "min argc", "max argc", "what");
210 
211     for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
212 	printf("%-10s %5u %5u   %s\n",
213 	       cmdtp->cmd, cmdtp->minargc, cmdtp->maxargc, cmdtp->helptxt);
214     return 0;
215 }
216 
217 static char *
218 prompt(el)
219 	EditLine *el;
220 {
221     static char pstring[64];
222     snprintf(pstring, sizeof(pstring), "fsdb (inum: %d)> ", curinum);
223     return pstring;
224 }
225 
226 
227 static int
228 cmdloop()
229 {
230     char *line = NULL;
231     const char *elline;
232     int cmd_argc, rval = 0, known;
233 #define scratch known
234     char **cmd_argv;
235     struct cmdtable *cmdp;
236     History *hist;
237     EditLine *elptr;
238 
239     curinode = ginode(ROOTINO);
240     curinum = ROOTINO;
241     printactive();
242 
243     hist = history_init();
244     history(hist, H_EVENT, 100);	/* 100 elt history buffer */
245 
246     elptr = el_init(__progname, stdin, stdout);
247     el_set(elptr, EL_EDITOR, "emacs");
248     el_set(elptr, EL_PROMPT, prompt);
249     el_set(elptr, EL_HIST, history, hist);
250     el_source(elptr, NULL);
251 
252     while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
253 	if (debug)
254 	    printf("command `%s'\n", line);
255 
256 	history(hist, H_ENTER, elline);
257 
258 	line = strdup(elline);
259 	cmd_argv = crack(line, &cmd_argc);
260 	if (cmd_argc) {
261 	    /*
262 	     * el_parse returns -1 to signal that it's not been handled
263 	     * internally.
264 	     */
265 	    if (el_parse(elptr, cmd_argc, cmd_argv) != -1)
266 		continue;
267 	    known = 0;
268 	    for (cmdp = cmds; cmdp->cmd; cmdp++) {
269 		if (!strcmp(cmdp->cmd, cmd_argv[0])) {
270 		    if (cmd_argc >= cmdp->minargc &&
271 			cmd_argc <= cmdp->maxargc)
272 			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
273 		    else
274 			rval = argcount(cmdp, cmd_argc, cmd_argv);
275 		    known = 1;
276 		    break;
277 		}
278 	    }
279 	    if (!known)
280 		warnx("unknown command `%s'", cmd_argv[0]), rval = 1;
281 	} else
282 	    rval = 0;
283 	free(line);
284 	if (rval < 0)
285 	    return rval;
286 	if (rval)
287 	    warnx("rval was %d", rval);
288     }
289     el_end(elptr);
290     history_end(hist);
291     return rval;
292 }
293 
294 static ino_t ocurrent;
295 
296 #define GETINUM(ac,inum)    inum = strtoul(argv[ac], &cp, 0); \
297     if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
298 	printf("inode %d out of range; range is [%d,%d]\n", \
299 	       inum, ROOTINO, maxino); \
300 	return 1; \
301     }
302 
303 /*
304  * Focus on given inode number
305  */
306 CMDFUNCSTART(focus)
307 {
308     ino_t inum;
309     char *cp;
310 
311     GETINUM(1,inum);
312     curinode = ginode(inum);
313     ocurrent = curinum;
314     curinum = inum;
315     printactive();
316     return 0;
317 }
318 
319 CMDFUNCSTART(back)
320 {
321     curinum = ocurrent;
322     curinode = ginode(curinum);
323     printactive();
324     return 0;
325 }
326 
327 CMDFUNCSTART(zapi)
328 {
329     ino_t inum;
330     struct dinode *dp;
331     char *cp;
332 
333     GETINUM(1,inum);
334     dp = ginode(inum);
335     clearinode(dp);
336     inodirty();
337     if (curinode)			/* re-set after potential change */
338 	curinode = ginode(curinum);
339     return 0;
340 }
341 
342 CMDFUNCSTART(active)
343 {
344     printactive();
345     return 0;
346 }
347 
348 
349 CMDFUNCSTART(quit)
350 {
351     return -1;
352 }
353 
354 CMDFUNCSTART(uplink)
355 {
356     if (!checkactive())
357 	return 1;
358     printf("inode %d link count now %d\n", curinum, ++curinode->di_nlink);
359     inodirty();
360     return 0;
361 }
362 
363 CMDFUNCSTART(downlink)
364 {
365     if (!checkactive())
366 	return 1;
367     printf("inode %d link count now %d\n", curinum, --curinode->di_nlink);
368     inodirty();
369     return 0;
370 }
371 
372 static const char *typename[] = {
373     "unknown",
374     "fifo",
375     "char special",
376     "unregistered #3",
377     "directory",
378     "unregistered #5",
379     "blk special",
380     "unregistered #7",
381     "regular",
382     "unregistered #9",
383     "symlink",
384     "unregistered #11",
385     "socket",
386     "unregistered #13",
387     "whiteout",
388 };
389 
390 static int slot;
391 
392 static int
393 scannames(idesc)
394 	struct inodesc *idesc;
395 {
396 	register struct direct *dirp = idesc->id_dirp;
397 
398 	printf("slot %d ino %d reclen %d: %s, `%.*s'\n",
399 	       slot++, dirp->d_ino, dirp->d_reclen, typename[dirp->d_type],
400 	       dirp->d_namlen, dirp->d_name);
401 	return (KEEPON);
402 }
403 
404 CMDFUNCSTART(ls)
405 {
406     struct inodesc idesc;
407     checkactivedir();			/* let it go on anyway */
408 
409     slot = 0;
410     idesc.id_number = curinum;
411     idesc.id_func = scannames;
412     idesc.id_type = DATA;
413     idesc.id_fix = IGNORE;
414     ckinode(curinode, &idesc);
415     curinode = ginode(curinum);
416 
417     return 0;
418 }
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 static long slotcount, desired;
510 
511 static 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 static 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 static 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(chmode)
649 {
650     int rval = 1;
651     long modebits;
652     char *cp;
653 
654     if (!checkactive())
655 	return 1;
656 
657     modebits = strtol(argv[1], &cp, 8);
658     if (cp == argv[1] || *cp != '\0' ) {
659 	warnx("bad modebits `%s'", argv[1]);
660 	return 1;
661     }
662 
663     curinode->di_mode &= ~07777;
664     curinode->di_mode |= modebits;
665     inodirty();
666     printactive();
667     return rval;
668 }
669 
670 CMDFUNCSTART(chlen)
671 {
672     int rval = 1;
673     long len;
674     char *cp;
675 
676     if (!checkactive())
677 	return 1;
678 
679     len = strtol(argv[1], &cp, 0);
680     if (cp == argv[1] || *cp != '\0' || len < 0) {
681 	warnx("bad length '%s'", argv[1]);
682 	return 1;
683     }
684 
685     curinode->di_size = len;
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     uid_t 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     gid_t 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 static int
820 dotime(name, rsec, rnsec)
821 	char *name;
822 	int32_t *rsec, *rnsec;
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     *rsec = sec;
872     *rnsec = nsec;
873     return 0;
874 }
875 
876 CMDFUNCSTART(chmtime)
877 {
878     if (dotime(argv[1], &curinode->di_mtime, &curinode->di_mtimensec))
879 	return 1;
880     inodirty();
881     printactive();
882     return 0;
883 }
884 
885 CMDFUNCSTART(chatime)
886 {
887     if (dotime(argv[1], &curinode->di_atime, &curinode->di_atimensec))
888 	return 1;
889     inodirty();
890     printactive();
891     return 0;
892 }
893 
894 CMDFUNCSTART(chctime)
895 {
896     if (dotime(argv[1], &curinode->di_ctime, &curinode->di_ctimensec))
897 	return 1;
898     inodirty();
899     printactive();
900     return 0;
901 }
902