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