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