1/*	$NetBSD: fsdb.c,v 1.2 1995/10/08 23:18:10 thorpej Exp $	*/
2
3/*-
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 *  Copyright (c) 1995 John T. Kohl
7 *  All rights reserved.
8 *
9 *  Redistribution and use in source and binary forms, with or without
10 *  modification, are permitted provided that the following conditions
11 *  are met:
12 *  1. Redistributions of source code must retain the above copyright
13 *     notice, this list of conditions and the following disclaimer.
14 *  2. Redistributions in binary form must reproduce the above copyright
15 *     notice, this list of conditions and the following disclaimer in the
16 *     documentation and/or other materials provided with the distribution.
17 *  3. The name of the author may not be used to endorse or promote products
18 *     derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#ifndef lint
34static const char rcsid[] =
35  "$FreeBSD: stable/11/sbin/fsdb/fsdb.c 359754 2020-04-09 20:38:36Z kevans $";
36#endif /* not lint */
37
38#include <sys/param.h>
39#include <ctype.h>
40#include <err.h>
41#include <grp.h>
42#include <histedit.h>
43#include <pwd.h>
44#include <stdint.h>
45#include <string.h>
46#include <time.h>
47#include <timeconv.h>
48
49#include <ufs/ufs/dinode.h>
50#include <ufs/ufs/dir.h>
51#include <ufs/ffs/fs.h>
52
53#include "fsdb.h"
54#include "fsck.h"
55
56static void usage(void) __dead2;
57int cmdloop(void);
58static int compare_blk32(uint32_t *wantedblk, uint32_t curblk);
59static int compare_blk64(uint64_t *wantedblk, uint64_t curblk);
60static int founddatablk(uint64_t blk);
61static int find_blks32(uint32_t *buf, int size, uint32_t *blknum);
62static int find_blks64(uint64_t *buf, int size, uint64_t *blknum);
63static int find_indirblks32(uint32_t blk, int ind_level, uint32_t *blknum);
64static int find_indirblks64(uint64_t blk, int ind_level, uint64_t *blknum);
65
66static void
67usage(void)
68{
69	fprintf(stderr, "usage: fsdb [-d] [-f] [-r] fsname\n");
70	exit(1);
71}
72
73/*
74 * We suck in lots of fsck code, and just pick & choose the stuff we want.
75 *
76 * fsreadfd is set up to read from the file system, fswritefd to write to
77 * the file system.
78 */
79int
80main(int argc, char *argv[])
81{
82	int ch, rval;
83	char *fsys = NULL;
84
85	while (-1 != (ch = getopt(argc, argv, "fdr"))) {
86		switch (ch) {
87		case 'f':
88			/* The -f option is left for historical
89			 * reasons and has no meaning.
90			 */
91			break;
92		case 'd':
93			debug++;
94			break;
95		case 'r':
96			nflag++; /* "no" in fsck, readonly for us */
97			break;
98		default:
99			usage();
100		}
101	}
102	argc -= optind;
103	argv += optind;
104	if (argc != 1)
105		usage();
106	else
107		fsys = argv[0];
108
109	sblock_init();
110	if (!setup(fsys))
111		errx(1, "cannot set up file system `%s'", fsys);
112	printf("%s file system `%s'\nLast Mounted on %s\n",
113	       nflag? "Examining": "Editing", fsys, sblock.fs_fsmnt);
114	rval = cmdloop();
115	if (!nflag) {
116		sblock.fs_clean = 0;	/* mark it dirty */
117		sbdirty();
118		ckfini(0);
119		printf("*** FILE SYSTEM MARKED DIRTY\n");
120		printf("*** BE SURE TO RUN FSCK TO CLEAN UP ANY DAMAGE\n");
121		printf("*** IF IT WAS MOUNTED, RE-MOUNT WITH -u -o reload\n");
122	}
123	exit(rval);
124}
125
126#define CMDFUNC(func) int func(int argc, char *argv[])
127#define CMDFUNCSTART(func) int func(int argc, char *argv[])
128
129CMDFUNC(helpfn);
130CMDFUNC(focus);				/* focus on inode */
131CMDFUNC(active);			/* print active inode */
132CMDFUNC(blocks);			/* print blocks for active inode */
133CMDFUNC(focusname);			/* focus by name */
134CMDFUNC(zapi);				/* clear inode */
135CMDFUNC(uplink);			/* incr link */
136CMDFUNC(downlink);			/* decr link */
137CMDFUNC(linkcount);			/* set link count */
138CMDFUNC(quit);				/* quit */
139CMDFUNC(findblk);			/* find block */
140CMDFUNC(ls);				/* list directory */
141CMDFUNC(rm);				/* remove name */
142CMDFUNC(ln);				/* add name */
143CMDFUNC(newtype);			/* change type */
144CMDFUNC(chmode);			/* change mode */
145CMDFUNC(chlen);				/* change length */
146CMDFUNC(chaflags);			/* change flags */
147CMDFUNC(chgen);				/* change generation */
148CMDFUNC(chowner);			/* change owner */
149CMDFUNC(chgroup);			/* Change group */
150CMDFUNC(back);				/* pop back to last ino */
151CMDFUNC(chbtime);			/* Change btime */
152CMDFUNC(chmtime);			/* Change mtime */
153CMDFUNC(chctime);			/* Change ctime */
154CMDFUNC(chatime);			/* Change atime */
155CMDFUNC(chinum);			/* Change inode # of dirent */
156CMDFUNC(chname);			/* Change dirname of dirent */
157CMDFUNC(chsize);			/* Change size */
158
159struct cmdtable cmds[] = {
160	{ "help", "Print out help", 1, 1, FL_RO, helpfn },
161	{ "?", "Print out help", 1, 1, FL_RO, helpfn },
162	{ "inode", "Set active inode to INUM", 2, 2, FL_RO, focus },
163	{ "clri", "Clear inode INUM", 2, 2, FL_WR, zapi },
164	{ "lookup", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
165	{ "cd", "Set active inode by looking up NAME", 2, 2, FL_RO | FL_ST, focusname },
166	{ "back", "Go to previous active inode", 1, 1, FL_RO, back },
167	{ "active", "Print active inode", 1, 1, FL_RO, active },
168	{ "print", "Print active inode", 1, 1, FL_RO, active },
169	{ "blocks", "Print block numbers of active inode", 1, 1, FL_RO, blocks },
170	{ "uplink", "Increment link count", 1, 1, FL_WR, uplink },
171	{ "downlink", "Decrement link count", 1, 1, FL_WR, downlink },
172	{ "linkcount", "Set link count to COUNT", 2, 2, FL_WR, linkcount },
173	{ "findblk", "Find inode owning disk block(s)", 2, 33, FL_RO, findblk},
174	{ "ls", "List current inode as directory", 1, 1, FL_RO, ls },
175	{ "rm", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
176	{ "del", "Remove NAME from current inode directory", 2, 2, FL_WR | FL_ST, rm },
177	{ "ln", "Hardlink INO into current inode directory as NAME", 3, 3, FL_WR | FL_ST, ln },
178	{ "chinum", "Change dir entry number INDEX to INUM", 3, 3, FL_WR, chinum },
179	{ "chname", "Change dir entry number INDEX to NAME", 3, 3, FL_WR | FL_ST, chname },
180	{ "chtype", "Change type of current inode to TYPE", 2, 2, FL_WR, newtype },
181	{ "chmod", "Change mode of current inode to MODE", 2, 2, FL_WR, chmode },
182	{ "chlen", "Change length of current inode to LENGTH", 2, 2, FL_WR, chlen },
183	{ "chown", "Change owner of current inode to OWNER", 2, 2, FL_WR, chowner },
184	{ "chgrp", "Change group of current inode to GROUP", 2, 2, FL_WR, chgroup },
185	{ "chflags", "Change flags of current inode to FLAGS", 2, 2, FL_WR, chaflags },
186	{ "chgen", "Change generation number of current inode to GEN", 2, 2, FL_WR, chgen },
187	{ "chsize", "Change size of current inode to SIZE", 2, 2, FL_WR, chsize },
188	{ "btime", "Change btime of current inode to BTIME", 2, 2, FL_WR, chbtime },
189	{ "mtime", "Change mtime of current inode to MTIME", 2, 2, FL_WR, chmtime },
190	{ "ctime", "Change ctime of current inode to CTIME", 2, 2, FL_WR, chctime },
191	{ "atime", "Change atime of current inode to ATIME", 2, 2, FL_WR, chatime },
192	{ "quit", "Exit", 1, 1, FL_RO, quit },
193	{ "q", "Exit", 1, 1, FL_RO, quit },
194	{ "exit", "Exit", 1, 1, FL_RO, quit },
195	{ NULL, 0, 0, 0, 0, NULL },
196};
197
198int
199helpfn(int argc, char *argv[])
200{
201    struct cmdtable *cmdtp;
202
203    printf("Commands are:\n%-10s %5s %5s   %s\n",
204	   "command", "min args", "max args", "what");
205
206    for (cmdtp = cmds; cmdtp->cmd; cmdtp++)
207	printf("%-10s %5u %5u   %s\n",
208		cmdtp->cmd, cmdtp->minargc-1, cmdtp->maxargc-1, cmdtp->helptxt);
209    return 0;
210}
211
212char *
213prompt(EditLine *el)
214{
215    static char pstring[64];
216    snprintf(pstring, sizeof(pstring), "fsdb (inum: %ju)> ",
217	(uintmax_t)curinum);
218    return pstring;
219}
220
221
222int
223cmdloop(void)
224{
225    char *line;
226    const char *elline;
227    int cmd_argc, rval = 0, known;
228#define scratch known
229    char **cmd_argv;
230    struct cmdtable *cmdp;
231    History *hist;
232    EditLine *elptr;
233    HistEvent he;
234
235    curinode = ginode(ROOTINO);
236    curinum = ROOTINO;
237    printactive(0);
238
239    hist = history_init();
240    history(hist, &he, H_SETSIZE, 100);	/* 100 elt history buffer */
241
242    elptr = el_init("fsdb", stdin, stdout, stderr);
243    el_set(elptr, EL_EDITOR, "emacs");
244    el_set(elptr, EL_PROMPT, prompt);
245    el_set(elptr, EL_HIST, history, hist);
246    el_source(elptr, NULL);
247
248    while ((elline = el_gets(elptr, &scratch)) != NULL && scratch != 0) {
249	if (debug)
250	    printf("command `%s'\n", elline);
251
252	history(hist, &he, H_ENTER, elline);
253
254	line = strdup(elline);
255	cmd_argv = crack(line, &cmd_argc);
256	/*
257	 * el_parse returns -1 to signal that it's not been handled
258	 * internally.
259	 */
260	if (el_parse(elptr, cmd_argc, (const char **)cmd_argv) != -1)
261	    continue;
262	if (cmd_argc) {
263	    known = 0;
264	    for (cmdp = cmds; cmdp->cmd; cmdp++) {
265		if (!strcmp(cmdp->cmd, cmd_argv[0])) {
266		    if ((cmdp->flags & FL_WR) == FL_WR && nflag)
267			warnx("`%s' requires write access", cmd_argv[0]),
268			    rval = 1;
269		    else if (cmd_argc >= cmdp->minargc &&
270			cmd_argc <= cmdp->maxargc)
271			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
272		    else if (cmd_argc >= cmdp->minargc &&
273			(cmdp->flags & FL_ST) == FL_ST) {
274			strcpy(line, elline);
275			cmd_argv = recrack(line, &cmd_argc, cmdp->maxargc);
276			rval = (*cmdp->handler)(cmd_argc, cmd_argv);
277		    } else
278			rval = argcount(cmdp, cmd_argc, cmd_argv);
279		    known = 1;
280		    break;
281		}
282	    }
283	    if (!known)
284		warnx("unknown command `%s'", cmd_argv[0]), rval = 1;
285	} else
286	    rval = 0;
287	free(line);
288	if (rval < 0)
289	    /* user typed "quit" */
290	    return 0;
291	if (rval)
292	    warnx("rval was %d", rval);
293    }
294    el_end(elptr);
295    history_end(hist);
296    return rval;
297}
298
299union dinode *curinode;
300ino_t curinum, ocurrent;
301
302#define GETINUM(ac,inum)    inum = strtoul(argv[ac], &cp, 0); \
303    if (inum < ROOTINO || inum > maxino || cp == argv[ac] || *cp != '\0' ) { \
304	printf("inode %ju out of range; range is [%ju,%ju]\n",		\
305	    (uintmax_t)inum, (uintmax_t)ROOTINO, (uintmax_t)maxino);	\
306	return 1; \
307    }
308
309/*
310 * Focus on given inode number
311 */
312CMDFUNCSTART(focus)
313{
314    ino_t inum;
315    char *cp;
316
317    GETINUM(1,inum);
318    curinode = ginode(inum);
319    ocurrent = curinum;
320    curinum = inum;
321    printactive(0);
322    return 0;
323}
324
325CMDFUNCSTART(back)
326{
327    curinum = ocurrent;
328    curinode = ginode(curinum);
329    printactive(0);
330    return 0;
331}
332
333CMDFUNCSTART(zapi)
334{
335    ino_t inum;
336    union dinode *dp;
337    char *cp;
338
339    GETINUM(1,inum);
340    dp = ginode(inum);
341    clearinode(dp);
342    inodirty(dp);
343    if (curinode)			/* re-set after potential change */
344	curinode = ginode(curinum);
345    return 0;
346}
347
348CMDFUNCSTART(active)
349{
350    printactive(0);
351    return 0;
352}
353
354CMDFUNCSTART(blocks)
355{
356    printactive(1);
357    return 0;
358}
359
360CMDFUNCSTART(quit)
361{
362    return -1;
363}
364
365CMDFUNCSTART(uplink)
366{
367    if (!checkactive())
368	return 1;
369    DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) + 1);
370    printf("inode %ju link count now %d\n",
371	(uintmax_t)curinum, DIP(curinode, di_nlink));
372    inodirty(curinode);
373    return 0;
374}
375
376CMDFUNCSTART(downlink)
377{
378    if (!checkactive())
379	return 1;
380    DIP_SET(curinode, di_nlink, DIP(curinode, di_nlink) - 1);
381    printf("inode %ju link count now %d\n",
382	(uintmax_t)curinum, DIP(curinode, di_nlink));
383    inodirty(curinode);
384    return 0;
385}
386
387const char *typename[] = {
388    "unknown",
389    "fifo",
390    "char special",
391    "unregistered #3",
392    "directory",
393    "unregistered #5",
394    "blk special",
395    "unregistered #7",
396    "regular",
397    "unregistered #9",
398    "symlink",
399    "unregistered #11",
400    "socket",
401    "unregistered #13",
402    "whiteout",
403};
404
405int diroff;
406int slot;
407
408int
409scannames(struct inodesc *idesc)
410{
411	struct direct *dirp = idesc->id_dirp;
412
413	printf("slot %d off %d ino %d reclen %d: %s, `%.*s'\n",
414	       slot++, diroff, dirp->d_ino, dirp->d_reclen,
415	       typename[dirp->d_type], dirp->d_namlen, dirp->d_name);
416	diroff += dirp->d_reclen;
417	return (KEEPON);
418}
419
420CMDFUNCSTART(ls)
421{
422    struct inodesc idesc;
423    checkactivedir();			/* let it go on anyway */
424
425    slot = 0;
426    diroff = 0;
427    idesc.id_number = curinum;
428    idesc.id_func = scannames;
429    idesc.id_type = DATA;
430    idesc.id_fix = IGNORE;
431    ckinode(curinode, &idesc);
432    curinode = ginode(curinum);
433
434    return 0;
435}
436
437static int findblk_numtofind;
438static int wantedblksize;
439
440CMDFUNCSTART(findblk)
441{
442    ino_t inum, inosused;
443    uint32_t *wantedblk32;
444    uint64_t *wantedblk64;
445    struct bufarea *cgbp;
446    struct cg *cgp;
447    int c, i, is_ufs2;
448
449    wantedblksize = (argc - 1);
450    is_ufs2 = sblock.fs_magic == FS_UFS2_MAGIC;
451    ocurrent = curinum;
452
453    if (is_ufs2) {
454	wantedblk64 = calloc(wantedblksize, sizeof(uint64_t));
455	if (wantedblk64 == NULL)
456	    err(1, "malloc");
457	for (i = 1; i < argc; i++)
458	    wantedblk64[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
459    } else {
460	wantedblk32 = calloc(wantedblksize, sizeof(uint32_t));
461	if (wantedblk32 == NULL)
462	    err(1, "malloc");
463	for (i = 1; i < argc; i++)
464	    wantedblk32[i - 1] = dbtofsb(&sblock, strtoull(argv[i], NULL, 0));
465    }
466    findblk_numtofind = wantedblksize;
467    /*
468     * sblock.fs_ncg holds a number of cylinder groups.
469     * Iterate over all cylinder groups.
470     */
471    for (c = 0; c < sblock.fs_ncg; c++) {
472	/*
473	 * sblock.fs_ipg holds a number of inodes per cylinder group.
474	 * Calculate a highest inode number for a given cylinder group.
475	 */
476	inum = c * sblock.fs_ipg;
477	/* Read cylinder group. */
478	cgbp = cgget(c);
479	cgp = cgbp->b_un.b_cg;
480	/*
481	 * Get a highest used inode number for a given cylinder group.
482	 * For UFS1 all inodes initialized at the newfs stage.
483	 */
484	if (is_ufs2)
485	    inosused = cgp->cg_initediblk;
486	else
487	    inosused = sblock.fs_ipg;
488
489	for (; inosused > 0; inum++, inosused--) {
490	    /* Skip magic inodes: 0, WINO, ROOTINO. */
491	    if (inum < ROOTINO)
492		continue;
493	    /*
494	     * Check if the block we are looking for is just an inode block.
495	     *
496	     * ino_to_fsba() - get block containing inode from its number.
497	     * INOPB() - get a number of inodes in one disk block.
498	     */
499	    if (is_ufs2 ?
500		compare_blk64(wantedblk64, ino_to_fsba(&sblock, inum)) :
501		compare_blk32(wantedblk32, ino_to_fsba(&sblock, inum))) {
502		printf("block %llu: inode block (%ju-%ju)\n",
503		    (unsigned long long)fsbtodb(&sblock,
504			ino_to_fsba(&sblock, inum)),
505		    (uintmax_t)(inum / INOPB(&sblock)) * INOPB(&sblock),
506		    (uintmax_t)(inum / INOPB(&sblock) + 1) * INOPB(&sblock));
507		findblk_numtofind--;
508		if (findblk_numtofind == 0)
509		    goto end;
510	    }
511	    /* Get on-disk inode aka dinode. */
512	    curinum = inum;
513	    curinode = ginode(inum);
514	    /* Find IFLNK dinode with allocated data blocks. */
515	    switch (DIP(curinode, di_mode) & IFMT) {
516	    case IFDIR:
517	    case IFREG:
518		if (DIP(curinode, di_blocks) == 0)
519		    continue;
520		break;
521	    case IFLNK:
522		{
523		    uint64_t size = DIP(curinode, di_size);
524		    if (size > 0 && size < sblock.fs_maxsymlinklen &&
525			DIP(curinode, di_blocks) == 0)
526			continue;
527		    else
528			break;
529		}
530	    default:
531		continue;
532	    }
533	    /* Look through direct data blocks. */
534	    if (is_ufs2 ?
535		find_blks64(curinode->dp2.di_db, NDADDR, wantedblk64) :
536		find_blks32(curinode->dp1.di_db, NDADDR, wantedblk32))
537		goto end;
538	    for (i = 0; i < NIADDR; i++) {
539		/*
540		 * Does the block we are looking for belongs to the
541		 * indirect blocks?
542		 */
543		if (is_ufs2 ?
544		    compare_blk64(wantedblk64, curinode->dp2.di_ib[i]) :
545		    compare_blk32(wantedblk32, curinode->dp1.di_ib[i]))
546		    if (founddatablk(is_ufs2 ? curinode->dp2.di_ib[i] :
547			curinode->dp1.di_ib[i]))
548			goto end;
549		/*
550		 * Search through indirect, double and triple indirect
551		 * data blocks.
552		 */
553		if (is_ufs2 ? (curinode->dp2.di_ib[i] != 0) :
554		    (curinode->dp1.di_ib[i] != 0))
555		    if (is_ufs2 ?
556			find_indirblks64(curinode->dp2.di_ib[i], i,
557			    wantedblk64) :
558			find_indirblks32(curinode->dp1.di_ib[i], i,
559			    wantedblk32))
560			goto end;
561	    }
562	}
563    }
564end:
565    curinum = ocurrent;
566    curinode = ginode(curinum);
567    return 0;
568}
569
570static int
571compare_blk32(uint32_t *wantedblk, uint32_t curblk)
572{
573    int i;
574
575    for (i = 0; i < wantedblksize; i++) {
576	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
577	    wantedblk[i] = 0;
578	    return 1;
579	}
580    }
581    return 0;
582}
583
584static int
585compare_blk64(uint64_t *wantedblk, uint64_t curblk)
586{
587    int i;
588
589    for (i = 0; i < wantedblksize; i++) {
590	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
591	    wantedblk[i] = 0;
592	    return 1;
593	}
594    }
595    return 0;
596}
597
598static int
599founddatablk(uint64_t blk)
600{
601
602    printf("%llu: data block of inode %ju\n",
603	(unsigned long long)fsbtodb(&sblock, blk), (uintmax_t)curinum);
604    findblk_numtofind--;
605    if (findblk_numtofind == 0)
606	return 1;
607    return 0;
608}
609
610static int
611find_blks32(uint32_t *buf, int size, uint32_t *wantedblk)
612{
613    int blk;
614    for (blk = 0; blk < size; blk++) {
615	if (buf[blk] == 0)
616	    continue;
617	if (compare_blk32(wantedblk, buf[blk])) {
618	    if (founddatablk(buf[blk]))
619		return 1;
620	}
621    }
622    return 0;
623}
624
625static int
626find_indirblks32(uint32_t blk, int ind_level, uint32_t *wantedblk)
627{
628#define MAXNINDIR      (MAXBSIZE / sizeof(uint32_t))
629    uint32_t idblk[MAXNINDIR];
630    int i;
631
632    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
633    if (ind_level <= 0) {
634	if (find_blks32(idblk, sblock.fs_bsize / sizeof(uint32_t), wantedblk))
635	    return 1;
636    } else {
637	ind_level--;
638	for (i = 0; i < sblock.fs_bsize / sizeof(uint32_t); i++) {
639	    if (compare_blk32(wantedblk, idblk[i])) {
640		if (founddatablk(idblk[i]))
641		    return 1;
642	    }
643	    if (idblk[i] != 0)
644		if (find_indirblks32(idblk[i], ind_level, wantedblk))
645		    return 1;
646	}
647    }
648#undef MAXNINDIR
649    return 0;
650}
651
652static int
653find_blks64(uint64_t *buf, int size, uint64_t *wantedblk)
654{
655    int blk;
656    for (blk = 0; blk < size; blk++) {
657	if (buf[blk] == 0)
658	    continue;
659	if (compare_blk64(wantedblk, buf[blk])) {
660	    if (founddatablk(buf[blk]))
661		return 1;
662	}
663    }
664    return 0;
665}
666
667static int
668find_indirblks64(uint64_t blk, int ind_level, uint64_t *wantedblk)
669{
670#define MAXNINDIR      (MAXBSIZE / sizeof(uint64_t))
671    uint64_t idblk[MAXNINDIR];
672    int i;
673
674    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
675    if (ind_level <= 0) {
676	if (find_blks64(idblk, sblock.fs_bsize / sizeof(uint64_t), wantedblk))
677	    return 1;
678    } else {
679	ind_level--;
680	for (i = 0; i < sblock.fs_bsize / sizeof(uint64_t); i++) {
681	    if (compare_blk64(wantedblk, idblk[i])) {
682		if (founddatablk(idblk[i]))
683		    return 1;
684	    }
685	    if (idblk[i] != 0)
686		if (find_indirblks64(idblk[i], ind_level, wantedblk))
687		    return 1;
688	}
689    }
690#undef MAXNINDIR
691    return 0;
692}
693
694int findino(struct inodesc *idesc); /* from fsck */
695static int dolookup(char *name);
696
697static int
698dolookup(char *name)
699{
700    struct inodesc idesc;
701
702    if (!checkactivedir())
703	    return 0;
704    idesc.id_number = curinum;
705    idesc.id_func = findino;
706    idesc.id_name = name;
707    idesc.id_type = DATA;
708    idesc.id_fix = IGNORE;
709    if (ckinode(curinode, &idesc) & FOUND) {
710	curinum = idesc.id_parent;
711	curinode = ginode(curinum);
712	printactive(0);
713	return 1;
714    } else {
715	warnx("name `%s' not found in current inode directory", name);
716	return 0;
717    }
718}
719
720CMDFUNCSTART(focusname)
721{
722    char *p, *val;
723
724    if (!checkactive())
725	return 1;
726
727    ocurrent = curinum;
728
729    if (argv[1][0] == '/') {
730	curinum = ROOTINO;
731	curinode = ginode(ROOTINO);
732    } else {
733	if (!checkactivedir())
734	    return 1;
735    }
736    for (p = argv[1]; p != NULL;) {
737	while ((val = strsep(&p, "/")) != NULL && *val == '\0');
738	if (val) {
739	    printf("component `%s': ", val);
740	    fflush(stdout);
741	    if (!dolookup(val)) {
742		curinode = ginode(curinum);
743		return(1);
744	    }
745	}
746    }
747    return 0;
748}
749
750CMDFUNCSTART(ln)
751{
752    ino_t inum;
753    int rval;
754    char *cp;
755
756    GETINUM(1,inum);
757
758    if (!checkactivedir())
759	return 1;
760    rval = makeentry(curinum, inum, argv[2]);
761    if (rval)
762	    printf("Ino %ju entered as `%s'\n", (uintmax_t)inum, argv[2]);
763    else
764	printf("could not enter name? weird.\n");
765    curinode = ginode(curinum);
766    return rval;
767}
768
769CMDFUNCSTART(rm)
770{
771    int rval;
772
773    if (!checkactivedir())
774	return 1;
775    rval = changeino(curinum, argv[1], 0);
776    if (rval & ALTERED) {
777	printf("Name `%s' removed\n", argv[1]);
778	return 0;
779    } else {
780	printf("could not remove name ('%s')? weird.\n", argv[1]);
781	return 1;
782    }
783}
784
785long slotcount, desired;
786
787int
788chinumfunc(struct inodesc *idesc)
789{
790	struct direct *dirp = idesc->id_dirp;
791
792	if (slotcount++ == desired) {
793	    dirp->d_ino = idesc->id_parent;
794	    return STOP|ALTERED|FOUND;
795	}
796	return KEEPON;
797}
798
799CMDFUNCSTART(chinum)
800{
801    char *cp;
802    ino_t inum;
803    struct inodesc idesc;
804
805    slotcount = 0;
806    if (!checkactivedir())
807	return 1;
808    GETINUM(2,inum);
809
810    desired = strtol(argv[1], &cp, 0);
811    if (cp == argv[1] || *cp != '\0' || desired < 0) {
812	printf("invalid slot number `%s'\n", argv[1]);
813	return 1;
814    }
815
816    idesc.id_number = curinum;
817    idesc.id_func = chinumfunc;
818    idesc.id_fix = IGNORE;
819    idesc.id_type = DATA;
820    idesc.id_parent = inum;		/* XXX convenient hiding place */
821
822    if (ckinode(curinode, &idesc) & FOUND)
823	return 0;
824    else {
825	warnx("no %sth slot in current directory", argv[1]);
826	return 1;
827    }
828}
829
830int
831chnamefunc(struct inodesc *idesc)
832{
833	struct direct *dirp = idesc->id_dirp;
834	struct direct testdir;
835
836	if (slotcount++ == desired) {
837	    /* will name fit? */
838	    testdir.d_namlen = strlen(idesc->id_name);
839	    if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
840		dirp->d_namlen = testdir.d_namlen;
841		strcpy(dirp->d_name, idesc->id_name);
842		return STOP|ALTERED|FOUND;
843	    } else
844		return STOP|FOUND;	/* won't fit, so give up */
845	}
846	return KEEPON;
847}
848
849CMDFUNCSTART(chname)
850{
851    int rval;
852    char *cp;
853    struct inodesc idesc;
854
855    slotcount = 0;
856    if (!checkactivedir())
857	return 1;
858
859    desired = strtoul(argv[1], &cp, 0);
860    if (cp == argv[1] || *cp != '\0') {
861	printf("invalid slot number `%s'\n", argv[1]);
862	return 1;
863    }
864
865    idesc.id_number = curinum;
866    idesc.id_func = chnamefunc;
867    idesc.id_fix = IGNORE;
868    idesc.id_type = DATA;
869    idesc.id_name = argv[2];
870
871    rval = ckinode(curinode, &idesc);
872    if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
873	return 0;
874    else if (rval & FOUND) {
875	warnx("new name `%s' does not fit in slot %s\n", argv[2], argv[1]);
876	return 1;
877    } else {
878	warnx("no %sth slot in current directory", argv[1]);
879	return 1;
880    }
881}
882
883struct typemap {
884    const char *typename;
885    int typebits;
886} typenamemap[]  = {
887    {"file", IFREG},
888    {"dir", IFDIR},
889    {"socket", IFSOCK},
890    {"fifo", IFIFO},
891};
892
893CMDFUNCSTART(newtype)
894{
895    int type;
896    struct typemap *tp;
897
898    if (!checkactive())
899	return 1;
900    type = DIP(curinode, di_mode) & IFMT;
901    for (tp = typenamemap;
902	 tp < &typenamemap[nitems(typenamemap)];
903	 tp++) {
904	if (!strcmp(argv[1], tp->typename)) {
905	    printf("setting type to %s\n", tp->typename);
906	    type = tp->typebits;
907	    break;
908	}
909    }
910    if (tp == &typenamemap[nitems(typenamemap)]) {
911	warnx("type `%s' not known", argv[1]);
912	warnx("try one of `file', `dir', `socket', `fifo'");
913	return 1;
914    }
915    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
916    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
917    inodirty(curinode);
918    printactive(0);
919    return 0;
920}
921
922CMDFUNCSTART(chlen)
923{
924    int rval = 1;
925    long len;
926    char *cp;
927
928    if (!checkactive())
929	return 1;
930
931    len = strtol(argv[1], &cp, 0);
932    if (cp == argv[1] || *cp != '\0' || len < 0) {
933	warnx("bad length `%s'", argv[1]);
934	return 1;
935    }
936
937    DIP_SET(curinode, di_size, len);
938    inodirty(curinode);
939    printactive(0);
940    return rval;
941}
942
943CMDFUNCSTART(chmode)
944{
945    int rval = 1;
946    long modebits;
947    char *cp;
948
949    if (!checkactive())
950	return 1;
951
952    modebits = strtol(argv[1], &cp, 8);
953    if (cp == argv[1] || *cp != '\0' || (modebits & ~07777)) {
954	warnx("bad modebits `%s'", argv[1]);
955	return 1;
956    }
957
958    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
959    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
960    inodirty(curinode);
961    printactive(0);
962    return rval;
963}
964
965CMDFUNCSTART(chaflags)
966{
967    int rval = 1;
968    u_long flags;
969    char *cp;
970
971    if (!checkactive())
972	return 1;
973
974    flags = strtoul(argv[1], &cp, 0);
975    if (cp == argv[1] || *cp != '\0' ) {
976	warnx("bad flags `%s'", argv[1]);
977	return 1;
978    }
979
980    if (flags > UINT_MAX) {
981	warnx("flags set beyond 32-bit range of field (%lx)\n", flags);
982	return(1);
983    }
984    DIP_SET(curinode, di_flags, flags);
985    inodirty(curinode);
986    printactive(0);
987    return rval;
988}
989
990CMDFUNCSTART(chgen)
991{
992    int rval = 1;
993    long gen;
994    char *cp;
995
996    if (!checkactive())
997	return 1;
998
999    gen = strtol(argv[1], &cp, 0);
1000    if (cp == argv[1] || *cp != '\0' ) {
1001	warnx("bad gen `%s'", argv[1]);
1002	return 1;
1003    }
1004
1005    if (gen > INT_MAX || gen < INT_MIN) {
1006	warnx("gen set beyond 32-bit range of field (%lx)\n", gen);
1007	return(1);
1008    }
1009    DIP_SET(curinode, di_gen, gen);
1010    inodirty(curinode);
1011    printactive(0);
1012    return rval;
1013}
1014
1015CMDFUNCSTART(chsize)
1016{
1017    int rval = 1;
1018    off_t size;
1019    char *cp;
1020
1021    if (!checkactive())
1022	return 1;
1023
1024    size = strtoll(argv[1], &cp, 0);
1025    if (cp == argv[1] || *cp != '\0') {
1026	warnx("bad size `%s'", argv[1]);
1027	return 1;
1028    }
1029
1030    if (size < 0) {
1031	warnx("size set to negative (%jd)\n", (intmax_t)size);
1032	return(1);
1033    }
1034    DIP_SET(curinode, di_size, size);
1035    inodirty(curinode);
1036    printactive(0);
1037    return rval;
1038}
1039
1040CMDFUNCSTART(linkcount)
1041{
1042    int rval = 1;
1043    int lcnt;
1044    char *cp;
1045
1046    if (!checkactive())
1047	return 1;
1048
1049    lcnt = strtol(argv[1], &cp, 0);
1050    if (cp == argv[1] || *cp != '\0' ) {
1051	warnx("bad link count `%s'", argv[1]);
1052	return 1;
1053    }
1054    if (lcnt > USHRT_MAX || lcnt < 0) {
1055	warnx("max link count is %d\n", USHRT_MAX);
1056	return 1;
1057    }
1058
1059    DIP_SET(curinode, di_nlink, lcnt);
1060    inodirty(curinode);
1061    printactive(0);
1062    return rval;
1063}
1064
1065CMDFUNCSTART(chowner)
1066{
1067    int rval = 1;
1068    unsigned long uid;
1069    char *cp;
1070    struct passwd *pwd;
1071
1072    if (!checkactive())
1073	return 1;
1074
1075    uid = strtoul(argv[1], &cp, 0);
1076    if (cp == argv[1] || *cp != '\0' ) {
1077	/* try looking up name */
1078	if ((pwd = getpwnam(argv[1]))) {
1079	    uid = pwd->pw_uid;
1080	} else {
1081	    warnx("bad uid `%s'", argv[1]);
1082	    return 1;
1083	}
1084    }
1085
1086    DIP_SET(curinode, di_uid, uid);
1087    inodirty(curinode);
1088    printactive(0);
1089    return rval;
1090}
1091
1092CMDFUNCSTART(chgroup)
1093{
1094    int rval = 1;
1095    unsigned long gid;
1096    char *cp;
1097    struct group *grp;
1098
1099    if (!checkactive())
1100	return 1;
1101
1102    gid = strtoul(argv[1], &cp, 0);
1103    if (cp == argv[1] || *cp != '\0' ) {
1104	if ((grp = getgrnam(argv[1]))) {
1105	    gid = grp->gr_gid;
1106	} else {
1107	    warnx("bad gid `%s'", argv[1]);
1108	    return 1;
1109	}
1110    }
1111
1112    DIP_SET(curinode, di_gid, gid);
1113    inodirty(curinode);
1114    printactive(0);
1115    return rval;
1116}
1117
1118int
1119dotime(char *name, time_t *secp, int32_t *nsecp)
1120{
1121    char *p, *val;
1122    struct tm t;
1123    int32_t nsec;
1124    p = strchr(name, '.');
1125    if (p) {
1126	*p = '\0';
1127	nsec = strtoul(++p, &val, 0);
1128	if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
1129		warnx("invalid nanoseconds");
1130		goto badformat;
1131	}
1132    } else
1133	nsec = 0;
1134    if (strlen(name) != 14) {
1135badformat:
1136	warnx("date format: YYYYMMDDHHMMSS[.nsec]");
1137	return 1;
1138    }
1139    *nsecp = nsec;
1140
1141    for (p = name; *p; p++)
1142	if (*p < '0' || *p > '9')
1143	    goto badformat;
1144
1145    p = name;
1146#define VAL() ((*p++) - '0')
1147    t.tm_year = VAL();
1148    t.tm_year = VAL() + t.tm_year * 10;
1149    t.tm_year = VAL() + t.tm_year * 10;
1150    t.tm_year = VAL() + t.tm_year * 10 - 1900;
1151    t.tm_mon = VAL();
1152    t.tm_mon = VAL() + t.tm_mon * 10 - 1;
1153    t.tm_mday = VAL();
1154    t.tm_mday = VAL() + t.tm_mday * 10;
1155    t.tm_hour = VAL();
1156    t.tm_hour = VAL() + t.tm_hour * 10;
1157    t.tm_min = VAL();
1158    t.tm_min = VAL() + t.tm_min * 10;
1159    t.tm_sec = VAL();
1160    t.tm_sec = VAL() + t.tm_sec * 10;
1161    t.tm_isdst = -1;
1162
1163    *secp = mktime(&t);
1164    if (*secp == -1) {
1165	warnx("date/time out of range");
1166	return 1;
1167    }
1168    return 0;
1169}
1170
1171CMDFUNCSTART(chbtime)
1172{
1173    time_t secs;
1174    int32_t nsecs;
1175
1176    if (dotime(argv[1], &secs, &nsecs))
1177	return 1;
1178    if (sblock.fs_magic == FS_UFS1_MAGIC)
1179	return 1;
1180    curinode->dp2.di_birthtime = _time_to_time64(secs);
1181    curinode->dp2.di_birthnsec = nsecs;
1182    inodirty(curinode);
1183    printactive(0);
1184    return 0;
1185}
1186
1187CMDFUNCSTART(chmtime)
1188{
1189    time_t secs;
1190    int32_t nsecs;
1191
1192    if (dotime(argv[1], &secs, &nsecs))
1193	return 1;
1194    if (sblock.fs_magic == FS_UFS1_MAGIC)
1195	curinode->dp1.di_mtime = _time_to_time32(secs);
1196    else
1197	curinode->dp2.di_mtime = _time_to_time64(secs);
1198    DIP_SET(curinode, di_mtimensec, nsecs);
1199    inodirty(curinode);
1200    printactive(0);
1201    return 0;
1202}
1203
1204CMDFUNCSTART(chatime)
1205{
1206    time_t secs;
1207    int32_t nsecs;
1208
1209    if (dotime(argv[1], &secs, &nsecs))
1210	return 1;
1211    if (sblock.fs_magic == FS_UFS1_MAGIC)
1212	curinode->dp1.di_atime = _time_to_time32(secs);
1213    else
1214	curinode->dp2.di_atime = _time_to_time64(secs);
1215    DIP_SET(curinode, di_atimensec, nsecs);
1216    inodirty(curinode);
1217    printactive(0);
1218    return 0;
1219}
1220
1221CMDFUNCSTART(chctime)
1222{
1223    time_t secs;
1224    int32_t nsecs;
1225
1226    if (dotime(argv[1], &secs, &nsecs))
1227	return 1;
1228    if (sblock.fs_magic == FS_UFS1_MAGIC)
1229	curinode->dp1.di_ctime = _time_to_time32(secs);
1230    else
1231	curinode->dp2.di_ctime = _time_to_time64(secs);
1232    DIP_SET(curinode, di_ctimensec, nsecs);
1233    inodirty(curinode);
1234    printactive(0);
1235    return 0;
1236}
1237