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$";
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(UFS_ROOTINO);
236    curinum = UFS_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); \
303if (inum < UFS_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)UFS_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 = cglookup(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, UFS_WINO, UFS_ROOTINO. */
491	    if (inum < UFS_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, UFS_NDADDR, wantedblk64) :
536		find_blks32(curinode->dp1.di_db, UFS_NDADDR, wantedblk32))
537		goto end;
538	    for (i = 0; i < UFS_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    if (is_ufs2)
568	free(wantedblk64);
569    else
570	free(wantedblk32);
571    return 0;
572}
573
574static int
575compare_blk32(uint32_t *wantedblk, uint32_t curblk)
576{
577    int i;
578
579    for (i = 0; i < wantedblksize; i++) {
580	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
581	    wantedblk[i] = 0;
582	    return 1;
583	}
584    }
585    return 0;
586}
587
588static int
589compare_blk64(uint64_t *wantedblk, uint64_t curblk)
590{
591    int i;
592
593    for (i = 0; i < wantedblksize; i++) {
594	if (wantedblk[i] != 0 && wantedblk[i] == curblk) {
595	    wantedblk[i] = 0;
596	    return 1;
597	}
598    }
599    return 0;
600}
601
602static int
603founddatablk(uint64_t blk)
604{
605
606    printf("%llu: data block of inode %ju\n",
607	(unsigned long long)fsbtodb(&sblock, blk), (uintmax_t)curinum);
608    findblk_numtofind--;
609    if (findblk_numtofind == 0)
610	return 1;
611    return 0;
612}
613
614static int
615find_blks32(uint32_t *buf, int size, uint32_t *wantedblk)
616{
617    int blk;
618    for (blk = 0; blk < size; blk++) {
619	if (buf[blk] == 0)
620	    continue;
621	if (compare_blk32(wantedblk, buf[blk])) {
622	    if (founddatablk(buf[blk]))
623		return 1;
624	}
625    }
626    return 0;
627}
628
629static int
630find_indirblks32(uint32_t blk, int ind_level, uint32_t *wantedblk)
631{
632#define MAXNINDIR      (MAXBSIZE / sizeof(uint32_t))
633    uint32_t idblk[MAXNINDIR];
634    int i;
635
636    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
637    if (ind_level <= 0) {
638	if (find_blks32(idblk, sblock.fs_bsize / sizeof(uint32_t), wantedblk))
639	    return 1;
640    } else {
641	ind_level--;
642	for (i = 0; i < sblock.fs_bsize / sizeof(uint32_t); i++) {
643	    if (compare_blk32(wantedblk, idblk[i])) {
644		if (founddatablk(idblk[i]))
645		    return 1;
646	    }
647	    if (idblk[i] != 0)
648		if (find_indirblks32(idblk[i], ind_level, wantedblk))
649		    return 1;
650	}
651    }
652#undef MAXNINDIR
653    return 0;
654}
655
656static int
657find_blks64(uint64_t *buf, int size, uint64_t *wantedblk)
658{
659    int blk;
660    for (blk = 0; blk < size; blk++) {
661	if (buf[blk] == 0)
662	    continue;
663	if (compare_blk64(wantedblk, buf[blk])) {
664	    if (founddatablk(buf[blk]))
665		return 1;
666	}
667    }
668    return 0;
669}
670
671static int
672find_indirblks64(uint64_t blk, int ind_level, uint64_t *wantedblk)
673{
674#define MAXNINDIR      (MAXBSIZE / sizeof(uint64_t))
675    uint64_t idblk[MAXNINDIR];
676    int i;
677
678    blread(fsreadfd, (char *)idblk, fsbtodb(&sblock, blk), (int)sblock.fs_bsize);
679    if (ind_level <= 0) {
680	if (find_blks64(idblk, sblock.fs_bsize / sizeof(uint64_t), wantedblk))
681	    return 1;
682    } else {
683	ind_level--;
684	for (i = 0; i < sblock.fs_bsize / sizeof(uint64_t); i++) {
685	    if (compare_blk64(wantedblk, idblk[i])) {
686		if (founddatablk(idblk[i]))
687		    return 1;
688	    }
689	    if (idblk[i] != 0)
690		if (find_indirblks64(idblk[i], ind_level, wantedblk))
691		    return 1;
692	}
693    }
694#undef MAXNINDIR
695    return 0;
696}
697
698int findino(struct inodesc *idesc); /* from fsck */
699static int dolookup(char *name);
700
701static int
702dolookup(char *name)
703{
704    struct inodesc idesc;
705
706    if (!checkactivedir())
707	    return 0;
708    idesc.id_number = curinum;
709    idesc.id_func = findino;
710    idesc.id_name = name;
711    idesc.id_type = DATA;
712    idesc.id_fix = IGNORE;
713    if (ckinode(curinode, &idesc) & FOUND) {
714	curinum = idesc.id_parent;
715	curinode = ginode(curinum);
716	printactive(0);
717	return 1;
718    } else {
719	warnx("name `%s' not found in current inode directory", name);
720	return 0;
721    }
722}
723
724CMDFUNCSTART(focusname)
725{
726    char *p, *val;
727
728    if (!checkactive())
729	return 1;
730
731    ocurrent = curinum;
732
733    if (argv[1][0] == '/') {
734	curinum = UFS_ROOTINO;
735	curinode = ginode(UFS_ROOTINO);
736    } else {
737	if (!checkactivedir())
738	    return 1;
739    }
740    for (p = argv[1]; p != NULL;) {
741	while ((val = strsep(&p, "/")) != NULL && *val == '\0');
742	if (val) {
743	    printf("component `%s': ", val);
744	    fflush(stdout);
745	    if (!dolookup(val)) {
746		curinode = ginode(curinum);
747		return(1);
748	    }
749	}
750    }
751    return 0;
752}
753
754CMDFUNCSTART(ln)
755{
756    ino_t inum;
757    int rval;
758    char *cp;
759
760    GETINUM(1,inum);
761
762    if (!checkactivedir())
763	return 1;
764    rval = makeentry(curinum, inum, argv[2]);
765    if (rval)
766	    printf("Ino %ju entered as `%s'\n", (uintmax_t)inum, argv[2]);
767    else
768	printf("could not enter name? weird.\n");
769    curinode = ginode(curinum);
770    return rval;
771}
772
773CMDFUNCSTART(rm)
774{
775    int rval;
776
777    if (!checkactivedir())
778	return 1;
779    rval = changeino(curinum, argv[1], 0);
780    if (rval & ALTERED) {
781	printf("Name `%s' removed\n", argv[1]);
782	return 0;
783    } else {
784	printf("could not remove name ('%s')? weird.\n", argv[1]);
785	return 1;
786    }
787}
788
789long slotcount, desired;
790
791int
792chinumfunc(struct inodesc *idesc)
793{
794	struct direct *dirp = idesc->id_dirp;
795
796	if (slotcount++ == desired) {
797	    dirp->d_ino = idesc->id_parent;
798	    return STOP|ALTERED|FOUND;
799	}
800	return KEEPON;
801}
802
803CMDFUNCSTART(chinum)
804{
805    char *cp;
806    ino_t inum;
807    struct inodesc idesc;
808
809    slotcount = 0;
810    if (!checkactivedir())
811	return 1;
812    GETINUM(2,inum);
813
814    desired = strtol(argv[1], &cp, 0);
815    if (cp == argv[1] || *cp != '\0' || desired < 0) {
816	printf("invalid slot number `%s'\n", argv[1]);
817	return 1;
818    }
819
820    idesc.id_number = curinum;
821    idesc.id_func = chinumfunc;
822    idesc.id_fix = IGNORE;
823    idesc.id_type = DATA;
824    idesc.id_parent = inum;		/* XXX convenient hiding place */
825
826    if (ckinode(curinode, &idesc) & FOUND)
827	return 0;
828    else {
829	warnx("no %sth slot in current directory", argv[1]);
830	return 1;
831    }
832}
833
834int
835chnamefunc(struct inodesc *idesc)
836{
837	struct direct *dirp = idesc->id_dirp;
838	struct direct testdir;
839
840	if (slotcount++ == desired) {
841	    /* will name fit? */
842	    testdir.d_namlen = strlen(idesc->id_name);
843	    if (DIRSIZ(NEWDIRFMT, &testdir) <= dirp->d_reclen) {
844		dirp->d_namlen = testdir.d_namlen;
845		strcpy(dirp->d_name, idesc->id_name);
846		return STOP|ALTERED|FOUND;
847	    } else
848		return STOP|FOUND;	/* won't fit, so give up */
849	}
850	return KEEPON;
851}
852
853CMDFUNCSTART(chname)
854{
855    int rval;
856    char *cp;
857    struct inodesc idesc;
858
859    slotcount = 0;
860    if (!checkactivedir())
861	return 1;
862
863    desired = strtoul(argv[1], &cp, 0);
864    if (cp == argv[1] || *cp != '\0') {
865	printf("invalid slot number `%s'\n", argv[1]);
866	return 1;
867    }
868
869    idesc.id_number = curinum;
870    idesc.id_func = chnamefunc;
871    idesc.id_fix = IGNORE;
872    idesc.id_type = DATA;
873    idesc.id_name = argv[2];
874
875    rval = ckinode(curinode, &idesc);
876    if ((rval & (FOUND|ALTERED)) == (FOUND|ALTERED))
877	return 0;
878    else if (rval & FOUND) {
879	warnx("new name `%s' does not fit in slot %s\n", argv[2], argv[1]);
880	return 1;
881    } else {
882	warnx("no %sth slot in current directory", argv[1]);
883	return 1;
884    }
885}
886
887struct typemap {
888    const char *typename;
889    int typebits;
890} typenamemap[]  = {
891    {"file", IFREG},
892    {"dir", IFDIR},
893    {"socket", IFSOCK},
894    {"fifo", IFIFO},
895};
896
897CMDFUNCSTART(newtype)
898{
899    int type;
900    struct typemap *tp;
901
902    if (!checkactive())
903	return 1;
904    type = DIP(curinode, di_mode) & IFMT;
905    for (tp = typenamemap;
906	 tp < &typenamemap[nitems(typenamemap)];
907	 tp++) {
908	if (!strcmp(argv[1], tp->typename)) {
909	    printf("setting type to %s\n", tp->typename);
910	    type = tp->typebits;
911	    break;
912	}
913    }
914    if (tp == &typenamemap[nitems(typenamemap)]) {
915	warnx("type `%s' not known", argv[1]);
916	warnx("try one of `file', `dir', `socket', `fifo'");
917	return 1;
918    }
919    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~IFMT);
920    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | type);
921    inodirty(curinode);
922    printactive(0);
923    return 0;
924}
925
926CMDFUNCSTART(chlen)
927{
928    int rval = 1;
929    long len;
930    char *cp;
931
932    if (!checkactive())
933	return 1;
934
935    len = strtol(argv[1], &cp, 0);
936    if (cp == argv[1] || *cp != '\0' || len < 0) {
937	warnx("bad length `%s'", argv[1]);
938	return 1;
939    }
940
941    DIP_SET(curinode, di_size, len);
942    inodirty(curinode);
943    printactive(0);
944    return rval;
945}
946
947CMDFUNCSTART(chmode)
948{
949    int rval = 1;
950    long modebits;
951    char *cp;
952
953    if (!checkactive())
954	return 1;
955
956    modebits = strtol(argv[1], &cp, 8);
957    if (cp == argv[1] || *cp != '\0' || (modebits & ~07777)) {
958	warnx("bad modebits `%s'", argv[1]);
959	return 1;
960    }
961
962    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) & ~07777);
963    DIP_SET(curinode, di_mode, DIP(curinode, di_mode) | modebits);
964    inodirty(curinode);
965    printactive(0);
966    return rval;
967}
968
969CMDFUNCSTART(chaflags)
970{
971    int rval = 1;
972    u_long flags;
973    char *cp;
974
975    if (!checkactive())
976	return 1;
977
978    flags = strtoul(argv[1], &cp, 0);
979    if (cp == argv[1] || *cp != '\0' ) {
980	warnx("bad flags `%s'", argv[1]);
981	return 1;
982    }
983
984    if (flags > UINT_MAX) {
985	warnx("flags set beyond 32-bit range of field (%lx)\n", flags);
986	return(1);
987    }
988    DIP_SET(curinode, di_flags, flags);
989    inodirty(curinode);
990    printactive(0);
991    return rval;
992}
993
994CMDFUNCSTART(chgen)
995{
996    int rval = 1;
997    long gen;
998    char *cp;
999
1000    if (!checkactive())
1001	return 1;
1002
1003    gen = strtol(argv[1], &cp, 0);
1004    if (cp == argv[1] || *cp != '\0' ) {
1005	warnx("bad gen `%s'", argv[1]);
1006	return 1;
1007    }
1008
1009    if (gen > INT_MAX || gen < INT_MIN) {
1010	warnx("gen set beyond 32-bit range of field (%lx)\n", gen);
1011	return(1);
1012    }
1013    DIP_SET(curinode, di_gen, gen);
1014    inodirty(curinode);
1015    printactive(0);
1016    return rval;
1017}
1018
1019CMDFUNCSTART(chsize)
1020{
1021    int rval = 1;
1022    off_t size;
1023    char *cp;
1024
1025    if (!checkactive())
1026	return 1;
1027
1028    size = strtoll(argv[1], &cp, 0);
1029    if (cp == argv[1] || *cp != '\0') {
1030	warnx("bad size `%s'", argv[1]);
1031	return 1;
1032    }
1033
1034    if (size < 0) {
1035	warnx("size set to negative (%jd)\n", (intmax_t)size);
1036	return(1);
1037    }
1038    DIP_SET(curinode, di_size, size);
1039    inodirty(curinode);
1040    printactive(0);
1041    return rval;
1042}
1043
1044CMDFUNCSTART(linkcount)
1045{
1046    int rval = 1;
1047    int lcnt;
1048    char *cp;
1049
1050    if (!checkactive())
1051	return 1;
1052
1053    lcnt = strtol(argv[1], &cp, 0);
1054    if (cp == argv[1] || *cp != '\0' ) {
1055	warnx("bad link count `%s'", argv[1]);
1056	return 1;
1057    }
1058    if (lcnt > USHRT_MAX || lcnt < 0) {
1059	warnx("max link count is %d\n", USHRT_MAX);
1060	return 1;
1061    }
1062
1063    DIP_SET(curinode, di_nlink, lcnt);
1064    inodirty(curinode);
1065    printactive(0);
1066    return rval;
1067}
1068
1069CMDFUNCSTART(chowner)
1070{
1071    int rval = 1;
1072    unsigned long uid;
1073    char *cp;
1074    struct passwd *pwd;
1075
1076    if (!checkactive())
1077	return 1;
1078
1079    uid = strtoul(argv[1], &cp, 0);
1080    if (cp == argv[1] || *cp != '\0' ) {
1081	/* try looking up name */
1082	if ((pwd = getpwnam(argv[1]))) {
1083	    uid = pwd->pw_uid;
1084	} else {
1085	    warnx("bad uid `%s'", argv[1]);
1086	    return 1;
1087	}
1088    }
1089
1090    DIP_SET(curinode, di_uid, uid);
1091    inodirty(curinode);
1092    printactive(0);
1093    return rval;
1094}
1095
1096CMDFUNCSTART(chgroup)
1097{
1098    int rval = 1;
1099    unsigned long gid;
1100    char *cp;
1101    struct group *grp;
1102
1103    if (!checkactive())
1104	return 1;
1105
1106    gid = strtoul(argv[1], &cp, 0);
1107    if (cp == argv[1] || *cp != '\0' ) {
1108	if ((grp = getgrnam(argv[1]))) {
1109	    gid = grp->gr_gid;
1110	} else {
1111	    warnx("bad gid `%s'", argv[1]);
1112	    return 1;
1113	}
1114    }
1115
1116    DIP_SET(curinode, di_gid, gid);
1117    inodirty(curinode);
1118    printactive(0);
1119    return rval;
1120}
1121
1122int
1123dotime(char *name, time_t *secp, int32_t *nsecp)
1124{
1125    char *p, *val;
1126    struct tm t;
1127    int32_t nsec;
1128    p = strchr(name, '.');
1129    if (p) {
1130	*p = '\0';
1131	nsec = strtoul(++p, &val, 0);
1132	if (val == p || *val != '\0' || nsec >= 1000000000 || nsec < 0) {
1133		warnx("invalid nanoseconds");
1134		goto badformat;
1135	}
1136    } else
1137	nsec = 0;
1138    if (strlen(name) != 14) {
1139badformat:
1140	warnx("date format: YYYYMMDDHHMMSS[.nsec]");
1141	return 1;
1142    }
1143    *nsecp = nsec;
1144
1145    for (p = name; *p; p++)
1146	if (*p < '0' || *p > '9')
1147	    goto badformat;
1148
1149    p = name;
1150#define VAL() ((*p++) - '0')
1151    t.tm_year = VAL();
1152    t.tm_year = VAL() + t.tm_year * 10;
1153    t.tm_year = VAL() + t.tm_year * 10;
1154    t.tm_year = VAL() + t.tm_year * 10 - 1900;
1155    t.tm_mon = VAL();
1156    t.tm_mon = VAL() + t.tm_mon * 10 - 1;
1157    t.tm_mday = VAL();
1158    t.tm_mday = VAL() + t.tm_mday * 10;
1159    t.tm_hour = VAL();
1160    t.tm_hour = VAL() + t.tm_hour * 10;
1161    t.tm_min = VAL();
1162    t.tm_min = VAL() + t.tm_min * 10;
1163    t.tm_sec = VAL();
1164    t.tm_sec = VAL() + t.tm_sec * 10;
1165    t.tm_isdst = -1;
1166
1167    *secp = mktime(&t);
1168    if (*secp == -1) {
1169	warnx("date/time out of range");
1170	return 1;
1171    }
1172    return 0;
1173}
1174
1175CMDFUNCSTART(chbtime)
1176{
1177    time_t secs;
1178    int32_t nsecs;
1179
1180    if (dotime(argv[1], &secs, &nsecs))
1181	return 1;
1182    if (sblock.fs_magic == FS_UFS1_MAGIC)
1183	return 1;
1184    curinode->dp2.di_birthtime = _time_to_time64(secs);
1185    curinode->dp2.di_birthnsec = nsecs;
1186    inodirty(curinode);
1187    printactive(0);
1188    return 0;
1189}
1190
1191CMDFUNCSTART(chmtime)
1192{
1193    time_t secs;
1194    int32_t nsecs;
1195
1196    if (dotime(argv[1], &secs, &nsecs))
1197	return 1;
1198    if (sblock.fs_magic == FS_UFS1_MAGIC)
1199	curinode->dp1.di_mtime = _time_to_time32(secs);
1200    else
1201	curinode->dp2.di_mtime = _time_to_time64(secs);
1202    DIP_SET(curinode, di_mtimensec, nsecs);
1203    inodirty(curinode);
1204    printactive(0);
1205    return 0;
1206}
1207
1208CMDFUNCSTART(chatime)
1209{
1210    time_t secs;
1211    int32_t nsecs;
1212
1213    if (dotime(argv[1], &secs, &nsecs))
1214	return 1;
1215    if (sblock.fs_magic == FS_UFS1_MAGIC)
1216	curinode->dp1.di_atime = _time_to_time32(secs);
1217    else
1218	curinode->dp2.di_atime = _time_to_time64(secs);
1219    DIP_SET(curinode, di_atimensec, nsecs);
1220    inodirty(curinode);
1221    printactive(0);
1222    return 0;
1223}
1224
1225CMDFUNCSTART(chctime)
1226{
1227    time_t secs;
1228    int32_t nsecs;
1229
1230    if (dotime(argv[1], &secs, &nsecs))
1231	return 1;
1232    if (sblock.fs_magic == FS_UFS1_MAGIC)
1233	curinode->dp1.di_ctime = _time_to_time32(secs);
1234    else
1235	curinode->dp2.di_ctime = _time_to_time64(secs);
1236    DIP_SET(curinode, di_ctimensec, nsecs);
1237    inodirty(curinode);
1238    printactive(0);
1239    return 0;
1240}
1241