xref: /freebsd/sbin/fsdb/fsdb.c (revision 06064893b3c62c648518be78604fac29fc0d9d61)
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, focusname },
155 	{ "cd", "Set active inode by looking up NAME", 2, 2, FL_RO, 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, rm },
165 	{ "del", "Remove NAME from current inode directory", 2, 2, FL_WR, rm },
166 	{ "ln", "Hardlink INO into current inode directory as NAME", 3, 3, FL_WR, 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, 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 argc", "max argc", "what");
192 
193     for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
194 	printf("%-10s %5u %5u   %s\n",
195 	       cmdtp->cmd, cmdtp->minargc, cmdtp->maxargc, 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_EVENT, 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, 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 			strcpy(line, elline);
260 			cmd_argv = recrack(line, &cmd_argc, cmdp->maxargc);
261 			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
262 		    } else
263 			rval = argcount(cmdp, cmd_argc, cmd_argv);
264 		    known = 1;
265 		    break;
266 		}
267 	    }
268 	    if (!known)
269 		warnx("unknown command `%s'", cmd_argv[0]), rval = 1;
270 	} else
271 	    rval = 0;
272 	free(line);
273 	if (rval < 0)
274 	    /* user typed "quit" */
275 	    return 0;
276 	if (rval)
277 	    warnx("rval was %d", rval);
278     }
279     el_end(elptr);
280     history_end(hist);
281     return rval;
282 }
283 
284 union dinode *curinode;
285 ino_t curinum, ocurrent;
286 
287 #define GETINUM(ac,inum)    inum = strtoul(argv[ac], &cp, 0); \
288     if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
289 	printf("inode %d out of range; range is [%d,%d]\n", \
290 	       inum, ROOTINO, maxino); \
291 	return 1; \
292     }
293 
294 /*
295  * Focus on given inode number
296  */
297 CMDFUNCSTART(focus)
298 {
299     ino_t inum;
300     char *cp;
301 
302     GETINUM(1,inum);
303     curinode = ginode(inum);
304     ocurrent = curinum;
305     curinum = inum;
306     printactive(0);
307     return 0;
308 }
309 
310 CMDFUNCSTART(back)
311 {
312     curinum = ocurrent;
313     curinode = ginode(curinum);
314     printactive(0);
315     return 0;
316 }
317 
318 CMDFUNCSTART(zapi)
319 {
320     ino_t inum;
321     union dinode *dp;
322     char *cp;
323 
324     GETINUM(1,inum);
325     dp = ginode(inum);
326     clearinode(dp);
327     inodirty();
328     if (curinode)			/* re-set after potential change */
329 	curinode = ginode(curinum);
330     return 0;
331 }
332 
333 CMDFUNCSTART(active)
334 {
335     printactive(0);
336     return 0;
337 }
338 
339 CMDFUNCSTART(blocks)
340 {
341     printactive(1);
342     return 0;
343 }
344 
345 CMDFUNCSTART(quit)
346 {
347     return -1;
348 }
349 
350 CMDFUNCSTART(uplink)
351 {
352     if (!checkactive())
353 	return 1;
354     DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) + 1);
355     printf("inode %d link count now %d\n", curinum, DIP(curinode, di_nlink));
356     inodirty();
357     return 0;
358 }
359 
360 CMDFUNCSTART(downlink)
361 {
362     if (!checkactive())
363 	return 1;
364     DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) - 1);
365     printf("inode %d link count now %d\n", curinum, DIP(curinode, di_nlink));
366     inodirty();
367     return 0;
368 }
369 
370 const char *typename[] = {
371     "unknown",
372     "fifo",
373     "char special",
374     "unregistered #3",
375     "directory",
376     "unregistered #5",
377     "blk special",
378     "unregistered #7",
379     "regular",
380     "unregistered #9",
381     "symlink",
382     "unregistered #11",
383     "socket",
384     "unregistered #13",
385     "whiteout",
386 };
387 
388 int slot;
389 
390 int
391 scannames(struct inodesc *idesc)
392 {
393 	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(struct inodesc *idesc); /* from fsck */
418 static int dolookup(char *name);
419 
420 static int
421 dolookup(char *name)
422 {
423     struct inodesc idesc;
424 
425     if (!checkactivedir())
426 	    return 0;
427     idesc.id_number = curinum;
428     idesc.id_func = findino;
429     idesc.id_name = name;
430     idesc.id_type = DATA;
431     idesc.id_fix = IGNORE;
432     if (ckinode(curinode, &idesc) & FOUND) {
433 	curinum = idesc.id_parent;
434 	curinode = ginode(curinum);
435 	printactive(0);
436 	return 1;
437     } else {
438 	warnx("name `%s' not found in current inode directory", name);
439 	return 0;
440     }
441 }
442 
443 CMDFUNCSTART(focusname)
444 {
445     char *p, *val;
446 
447     if (!checkactive())
448 	return 1;
449 
450     ocurrent = curinum;
451 
452     if (argv[1][0] == '/') {
453 	curinum = ROOTINO;
454 	curinode = ginode(ROOTINO);
455     } else {
456 	if (!checkactivedir())
457 	    return 1;
458     }
459     for (p = argv[1]; p != NULL;) {
460 	while ((val = strsep(&p, "/")) != NULL && *val == '\0');
461 	if (val) {
462 	    printf("component `%s': ", val);
463 	    fflush(stdout);
464 	    if (!dolookup(val)) {
465 		curinode = ginode(curinum);
466 		return(1);
467 	    }
468 	}
469     }
470     return 0;
471 }
472 
473 CMDFUNCSTART(ln)
474 {
475     ino_t inum;
476     int rval;
477     char *cp;
478 
479     GETINUM(1,inum);
480 
481     if (!checkactivedir())
482 	return 1;
483     rval = makeentry(curinum, inum, argv[2]);
484     if (rval)
485 	printf("Ino %d entered as `%s'\n", inum, argv[2]);
486     else
487 	printf("could not enter name? weird.\n");
488     curinode = ginode(curinum);
489     return rval;
490 }
491 
492 CMDFUNCSTART(rm)
493 {
494     int rval;
495 
496     if (!checkactivedir())
497 	return 1;
498     rval = changeino(curinum, argv[1], 0);
499     if (rval & ALTERED) {
500 	printf("Name `%s' removed\n", argv[1]);
501 	return 0;
502     } else {
503 	printf("could not remove name ('%s')? weird.\n", argv[1]);
504 	return 1;
505     }
506 }
507 
508 long slotcount, desired;
509 
510 int
511 chinumfunc(struct inodesc *idesc)
512 {
513 	struct direct *dirp = idesc->id_dirp;
514 
515 	if (slotcount++ == desired) {
516 	    dirp->d_ino = idesc->id_parent;
517 	    return STOP|ALTERED|FOUND;
518 	}
519 	return KEEPON;
520 }
521 
522 CMDFUNCSTART(chinum)
523 {
524     char *cp;
525     ino_t inum;
526     struct inodesc idesc;
527 
528     slotcount = 0;
529     if (!checkactivedir())
530 	return 1;
531     GETINUM(2,inum);
532 
533     desired = strtol(argv[1], &cp, 0);
534     if (cp == argv[1] || *cp != '\0' || desired < 0) {
535 	printf("invalid slot number `%s'\n", argv[1]);
536 	return 1;
537     }
538 
539     idesc.id_number = curinum;
540     idesc.id_func = chinumfunc;
541     idesc.id_fix = IGNORE;
542     idesc.id_type = DATA;
543     idesc.id_parent = inum;		/* XXX convenient hiding place */
544 
545     if (ckinode(curinode, &idesc) & FOUND)
546 	return 0;
547     else {
548 	warnx("no %sth slot in current directory", argv[1]);
549 	return 1;
550     }
551 }
552 
553 int
554 chnamefunc(struct inodesc *idesc)
555 {
556 	struct direct *dirp = idesc->id_dirp;
557 	struct direct testdir;
558 
559 	if (slotcount++ == desired) {
560 	    /* will name fit? */
561 	    testdir.d_namlen = strlen(idesc->id_name);
562 	    if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
563 		dirp->d_namlen = testdir.d_namlen;
564 		strcpy(dirp->d_name, idesc->id_name);
565 		return STOP|ALTERED|FOUND;
566 	    } else
567 		return STOP|FOUND;	/* won't fit, so give up */
568 	}
569 	return KEEPON;
570 }
571 
572 CMDFUNCSTART(chname)
573 {
574     int rval;
575     char *cp;
576     struct inodesc idesc;
577 
578     slotcount = 0;
579     if (!checkactivedir())
580 	return 1;
581 
582     desired = strtoul(argv[1], &cp, 0);
583     if (cp == argv[1] || *cp != '\0') {
584 	printf("invalid slot number `%s'\n", argv[1]);
585 	return 1;
586     }
587 
588     idesc.id_number = curinum;
589     idesc.id_func = chnamefunc;
590     idesc.id_fix = IGNORE;
591     idesc.id_type = DATA;
592     idesc.id_name = argv[2];
593 
594     rval = ckinode(curinode, &idesc);
595     if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
596 	return 0;
597     else if (rval & FOUND) {
598 	warnx("new name `%s' does not fit in slot %s\n", argv[2], argv[1]);
599 	return 1;
600     } else {
601 	warnx("no %sth slot in current directory", argv[1]);
602 	return 1;
603     }
604 }
605 
606 struct typemap {
607     const char *typename;
608     int typebits;
609 } typenamemap[]  = {
610     {"file", IFREG},
611     {"dir", IFDIR},
612     {"socket", IFSOCK},
613     {"fifo", IFIFO},
614 };
615 
616 CMDFUNCSTART(newtype)
617 {
618     int type;
619     struct typemap *tp;
620 
621     if (!checkactive())
622 	return 1;
623     type = DIP(curinode, di_mode) & IFMT;
624     for (tp = typenamemap;
625 	 tp < &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)];
626 	 tp++) {
627 	if (!strcmp(argv[1], tp->typename)) {
628 	    printf("setting type to %s\n", tp->typename);
629 	    type = tp->typebits;
630 	    break;
631 	}
632     }
633     if (tp == &typenamemap[sizeof(typenamemap)/sizeof(*typenamemap)]) {
634 	warnx("type `%s' not known", argv[1]);
635 	warnx("try one of `file', `dir', `socket', `fifo'");
636 	return 1;
637     }
638     DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
639     DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
640     inodirty();
641     printactive(0);
642     return 0;
643 }
644 
645 CMDFUNCSTART(chlen)
646 {
647     int rval = 1;
648     long len;
649     char *cp;
650 
651     if (!checkactive())
652 	return 1;
653 
654     len = strtol(argv[1], &cp, 0);
655     if (cp == argv[1] || *cp != '\0' || len < 0) {
656 	warnx("bad length `%s'", argv[1]);
657 	return 1;
658     }
659 
660     DIP_SET(curinode, di_size, len);
661     inodirty();
662     printactive(0);
663     return rval;
664 }
665 
666 CMDFUNCSTART(chmode)
667 {
668     int rval = 1;
669     long modebits;
670     char *cp;
671 
672     if (!checkactive())
673 	return 1;
674 
675     modebits = strtol(argv[1], &cp, 8);
676     if (cp == argv[1] || *cp != '\0' || (modebits & ~07777)) {
677 	warnx("bad modebits `%s'", argv[1]);
678 	return 1;
679     }
680 
681     DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
682     DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
683     inodirty();
684     printactive(0);
685     return rval;
686 }
687 
688 CMDFUNCSTART(chaflags)
689 {
690     int rval = 1;
691     u_long flags;
692     char *cp;
693 
694     if (!checkactive())
695 	return 1;
696 
697     flags = strtoul(argv[1], &cp, 0);
698     if (cp == argv[1] || *cp != '\0' ) {
699 	warnx("bad flags `%s'", argv[1]);
700 	return 1;
701     }
702 
703     if (flags > UINT_MAX) {
704 	warnx("flags set beyond 32-bit range of field (%lx)\n", flags);
705 	return(1);
706     }
707     DIP_SET(curinode, di_flags, flags);
708     inodirty();
709     printactive(0);
710     return rval;
711 }
712 
713 CMDFUNCSTART(chgen)
714 {
715     int rval = 1;
716     long gen;
717     char *cp;
718 
719     if (!checkactive())
720 	return 1;
721 
722     gen = strtol(argv[1], &cp, 0);
723     if (cp == argv[1] || *cp != '\0' ) {
724 	warnx("bad gen `%s'", argv[1]);
725 	return 1;
726     }
727 
728     if (gen > INT_MAX || gen < INT_MIN) {
729 	warnx("gen set beyond 32-bit range of field (%lx)\n", gen);
730 	return(1);
731     }
732     DIP_SET(curinode, di_gen, gen);
733     inodirty();
734     printactive(0);
735     return rval;
736 }
737 
738 CMDFUNCSTART(linkcount)
739 {
740     int rval = 1;
741     int lcnt;
742     char *cp;
743 
744     if (!checkactive())
745 	return 1;
746 
747     lcnt = strtol(argv[1], &cp, 0);
748     if (cp == argv[1] || *cp != '\0' ) {
749 	warnx("bad link count `%s'", argv[1]);
750 	return 1;
751     }
752     if (lcnt > USHRT_MAX || lcnt < 0) {
753 	warnx("max link count is %d\n", USHRT_MAX);
754 	return 1;
755     }
756 
757     DIP_SET(curinode, di_nlink, lcnt);
758     inodirty();
759     printactive(0);
760     return rval;
761 }
762 
763 CMDFUNCSTART(chowner)
764 {
765     int rval = 1;
766     unsigned long uid;
767     char *cp;
768     struct passwd *pwd;
769 
770     if (!checkactive())
771 	return 1;
772 
773     uid = strtoul(argv[1], &cp, 0);
774     if (cp == argv[1] || *cp != '\0' ) {
775 	/* try looking up name */
776 	if ((pwd = getpwnam(argv[1]))) {
777 	    uid = pwd->pw_uid;
778 	} else {
779 	    warnx("bad uid `%s'", argv[1]);
780 	    return 1;
781 	}
782     }
783 
784     DIP_SET(curinode, di_uid, uid);
785     inodirty();
786     printactive(0);
787     return rval;
788 }
789 
790 CMDFUNCSTART(chgroup)
791 {
792     int rval = 1;
793     unsigned long gid;
794     char *cp;
795     struct group *grp;
796 
797     if (!checkactive())
798 	return 1;
799 
800     gid = strtoul(argv[1], &cp, 0);
801     if (cp == argv[1] || *cp != '\0' ) {
802 	if ((grp = getgrnam(argv[1]))) {
803 	    gid = grp->gr_gid;
804 	} else {
805 	    warnx("bad gid `%s'", argv[1]);
806 	    return 1;
807 	}
808     }
809 
810     DIP_SET(curinode, di_gid, gid);
811     inodirty();
812     printactive(0);
813     return rval;
814 }
815 
816 int
817 dotime(char *name, time_t *secp, int32_t *nsecp)
818 {
819     char *p, *val;
820     struct tm t;
821     int32_t nsec;
822     p = strchr(name, '.');
823     if (p) {
824 	*p = '\0';
825 	nsec = strtoul(++p, &val, 0);
826 	if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
827 		warnx("invalid nanoseconds");
828 		goto badformat;
829 	}
830     } else
831 	nsec = 0;
832     if (strlen(name) != 14) {
833 badformat:
834 	warnx("date format: YYYYMMDDHHMMSS[.nsec]");
835 	return 1;
836     }
837     *nsecp = nsec;
838 
839     for (p = name; *p; p++)
840 	if (*p < '0' || *p > '9')
841 	    goto badformat;
842 
843     p = name;
844 #define VAL() ((*p++) - '0')
845     t.tm_year = VAL();
846     t.tm_year = VAL() + t.tm_year * 10;
847     t.tm_year = VAL() + t.tm_year * 10;
848     t.tm_year = VAL() + t.tm_year * 10 - 1900;
849     t.tm_mon = VAL();
850     t.tm_mon = VAL() + t.tm_mon * 10 - 1;
851     t.tm_mday = VAL();
852     t.tm_mday = VAL() + t.tm_mday * 10;
853     t.tm_hour = VAL();
854     t.tm_hour = VAL() + t.tm_hour * 10;
855     t.tm_min = VAL();
856     t.tm_min = VAL() + t.tm_min * 10;
857     t.tm_sec = VAL();
858     t.tm_sec = VAL() + t.tm_sec * 10;
859     t.tm_isdst = -1;
860 
861     *secp = mktime(&t);
862     if (*secp == -1) {
863 	warnx("date/time out of range");
864 	return 1;
865     }
866     return 0;
867 }
868 
869 CMDFUNCSTART(chmtime)
870 {
871     time_t secs;
872     int32_t nsecs;
873 
874     if (dotime(argv[1], &secs, &nsecs))
875 	return 1;
876     if (sblock.fs_magic == FS_UFS1_MAGIC)
877 	curinode->dp1.di_mtime = _time_to_time32(secs);
878     else
879 	curinode->dp2.di_mtime = _time_to_time64(secs);
880     DIP_SET(curinode, di_mtimensec, nsecs);
881     inodirty();
882     printactive(0);
883     return 0;
884 }
885 
886 CMDFUNCSTART(chatime)
887 {
888     time_t secs;
889     int32_t nsecs;
890 
891     if (dotime(argv[1], &secs, &nsecs))
892 	return 1;
893     if (sblock.fs_magic == FS_UFS1_MAGIC)
894 	curinode->dp1.di_atime = _time_to_time32(secs);
895     else
896 	curinode->dp2.di_atime = _time_to_time64(secs);
897     DIP_SET(curinode, di_atimensec, nsecs);
898     inodirty();
899     printactive(0);
900     return 0;
901 }
902 
903 CMDFUNCSTART(chctime)
904 {
905     time_t secs;
906     int32_t nsecs;
907 
908     if (dotime(argv[1], &secs, &nsecs))
909 	return 1;
910     if (sblock.fs_magic == FS_UFS1_MAGIC)
911 	curinode->dp1.di_ctime = _time_to_time32(secs);
912     else
913 	curinode->dp2.di_ctime = _time_to_time64(secs);
914     DIP_SET(curinode, di_ctimensec, nsecs);
915     inodirty();
916     printactive(0);
917     return 0;
918 }
919