2 * SPDX-License-Identifier: BSD-4-Clause
4 * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
5 * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
8 * This code is derived from software contributed to Berkeley by
9 * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgment:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors, as well as Christoph
23 * Herrmann and Thomas-Henning von Kamptz.
24 * 4. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * $TSHeader: src/sbin/ffsinfo/ffsinfo.c,v 1.4 2000/12/12 19:30:55 tomsoft Exp $
45 static const char copyright[] =
46 "@(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz\n\
47 Copyright (c) 1980, 1989, 1993 The Regents of the University of California.\n\
48 All rights reserved.\n";
52 static const char rcsid[] =
56 /* ********************************************************** INCLUDES ***** */
57 #include <sys/param.h>
58 #include <sys/disklabel.h>
59 #include <sys/mount.h>
62 #include <ufs/ufs/extattr.h>
63 #include <ufs/ufs/quota.h>
64 #include <ufs/ufs/ufsmount.h>
65 #include <ufs/ufs/dinode.h>
66 #include <ufs/ffs/fs.h>
82 /* *********************************************************** GLOBALS ***** */
84 int _dbg_lvl_ = (DL_INFO); /* DL_TRC */
87 static struct uufsd disk;
89 #define sblock disk.d_fs
97 #define osblock fsun.fs
99 static char i1blk[MAXBSIZE];
100 static char i2blk[MAXBSIZE];
101 static char i3blk[MAXBSIZE];
103 static struct csum *fscs;
105 /* ******************************************************** PROTOTYPES ***** */
106 static void usage(void);
107 static void dump_whole_ufs1_inode(ino_t, int);
108 static void dump_whole_ufs2_inode(ino_t, int);
110 #define DUMP_WHOLE_INODE(A,B) \
112 ? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
114 /* ************************************************************** main ***** */
116 * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
117 * errors is the file system much easier. You can run ffsinfo before and after
118 * an fsck(8), and compare the two ascii dumps easy with diff, and you see
119 * directly where the problem is. You can control how much detail you want to
120 * see with some command line arguments. You can also easy check the status
121 * of a file system, like is there is enough space for growing a file system,
122 * or how many active snapshots do we have. It provides much more detailed
123 * information then dumpfs. Snapshots, as they are very new, are not really
124 * supported. They are just mentioned currently, but it is planned to run
125 * also over active snapshots, to even get that output.
128 main(int argc, char **argv)
131 char *device, *special;
135 struct csum *dbg_csp;
139 int cfg_cg, cfg_in, cfg_lv;
140 int cg_start, cg_stop;
149 out_file = strdup("-");
151 while ((ch = getopt(argc, argv, "g:i:l:o:")) != -1) {
154 cfg_cg = strtol(optarg, NULL, 0);
155 if (errno == EINVAL || errno == ERANGE)
156 err(1, "%s", optarg);
161 cfg_in = strtol(optarg, NULL, 0);
162 if (errno == EINVAL || errno == ERANGE)
163 err(1, "%s", optarg);
168 cfg_lv = strtol(optarg, NULL, 0);
169 if (errno == EINVAL||errno == ERANGE)
170 err(1, "%s", optarg);
171 if (cfg_lv < 0x1 || cfg_lv > 0x3ff)
176 out_file = strdup(optarg);
177 if (out_file == NULL)
178 errx(1, "strdup failed");
194 * Now we try to guess the (raw)device name.
196 if (0 == strrchr(device, '/') && stat(device, &st) == -1) {
198 * No path prefix was given, so try in this order:
204 * FreeBSD now doesn't distinguish between raw and block
205 * devices any longer, but it should still work this way.
207 len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
208 special = (char *)malloc(len);
210 errx(1, "malloc failed");
211 snprintf(special, len, "%sr%s", _PATH_DEV, device);
212 if (stat(special, &st) == -1) {
213 snprintf(special, len, "%s%s", _PATH_DEV, device);
214 if (stat(special, &st) == -1) {
215 snprintf(special, len, "%svinum/r%s",
217 if (stat(special, &st) == -1)
218 /* For now this is the 'last resort' */
219 snprintf(special, len, "%svinum/%s",
226 if (ufs_disk_fillout_blank(&disk, device) == -1 ||
227 sbfind(&disk, 0) == -1)
228 err(1, "superblock fetch(%s) failed: %s", device, disk.d_error);
230 DBG_OPEN(out_file); /* already here we need a superblock */
233 DBG_DUMP_FS(&sblock, "primary sblock");
235 /* Determine here what cylinder groups to dump */
238 cg_stop = sblock.fs_ncg;
239 } else if (cfg_cg == -1) {
240 cg_start = sblock.fs_ncg - 1;
241 cg_stop = sblock.fs_ncg;
242 } else if (cfg_cg < sblock.fs_ncg) {
244 cg_stop = cfg_cg + 1;
246 cg_start = sblock.fs_ncg;
247 cg_stop = sblock.fs_ncg;
250 if (cfg_lv & 0x004) {
251 fscs = (struct csum *)calloc((size_t)1,
252 (size_t)sblock.fs_cssize);
254 errx(1, "calloc failed");
256 /* get the cylinder summary into the memory ... */
257 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
258 if (bread(&disk, fsbtodb(&sblock,
259 sblock.fs_csaddr + numfrags(&sblock, i)),
260 (void *)(((char *)fscs)+i),
261 (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ?
262 sblock.fs_cssize - i : sblock.fs_bsize)) == -1)
263 err(1, "bread: %s", disk.d_error);
267 /* ... and dump it */
268 for (dbg_csc = 0; dbg_csc < sblock.fs_ncg; dbg_csc++) {
269 snprintf(dbg_line, sizeof(dbg_line),
270 "%d. csum in fscs", dbg_csc);
271 DBG_DUMP_CSUM(&sblock,
278 /* for each requested cylinder group ... */
279 for (cylno = cg_start; cylno < cg_stop; cylno++) {
280 snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
281 if (cfg_lv & 0x002) {
282 /* dump the superblock copies */
283 if (bread(&disk, fsbtodb(&sblock,
284 cgsblock(&sblock, cylno)),
285 (void *)&osblock, SBLOCKSIZE) == -1)
286 err(1, "bread: %s", disk.d_error);
287 DBG_DUMP_FS(&osblock, dbg_line);
291 * Read the cylinder group and dump whatever was
294 if (bread(&disk, fsbtodb(&sblock,
295 cgtod(&sblock, cylno)), (void *)&acg,
296 (size_t)sblock.fs_cgsize) == -1)
297 err(1, "bread: %s", disk.d_error);
300 DBG_DUMP_CG(&sblock, dbg_line, &acg);
302 DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
304 DBG_DUMP_FRMAP(&sblock, dbg_line, &acg);
305 if (cfg_lv & 0x040) {
306 DBG_DUMP_CLMAP(&sblock, dbg_line, &acg);
307 DBG_DUMP_CLSUM(&sblock, dbg_line, &acg);
311 * See the comment in sbin/growfs/debug.c for why this
312 * is currently disabled, and what needs to be done to
315 if (disk.d_ufs == 1 && cfg_lv & 0x080)
316 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
321 if (cfg_lv & 0x300) {
322 /* Dump the requested inode(s) */
324 DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
326 for (in = cg_start * sblock.fs_ipg;
327 in < (ino_t)cg_stop * sblock.fs_ipg;
329 DUMP_WHOLE_INODE(in, cfg_lv);
339 /* ********************************************** dump_whole_ufs1_inode ***** */
341 * Here we dump a list of all blocks allocated by this inode. We follow
342 * all indirect blocks.
345 dump_whole_ufs1_inode(ino_t inode, int level)
347 DBG_FUNC("dump_whole_ufs1_inode")
350 unsigned int ind2ctr, ind3ctr;
351 ufs1_daddr_t *ind2ptr, *ind3ptr;
357 * Read the inode from disk/cache.
359 if (getinode(&disk, &dp, inode) == -1)
360 err(1, "getinode: %s", disk.d_error);
362 if (dp.dp1->di_nlink == 0) {
364 return; /* inode not in use */
368 * Dump the main inode structure.
370 snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
372 DBG_DUMP_INO(&sblock,
377 if (!(level & 0x200)) {
383 * Ok, now prepare for dumping all direct and indirect pointers.
385 rb = howmany(dp.dp1->di_size, sblock.fs_bsize) - UFS_NDADDR;
388 * Dump single indirect block.
390 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[0]),
391 (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
392 err(1, "bread: %s", disk.d_error);
394 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
396 DBG_DUMP_IBLK(&sblock,
400 rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
404 * Dump double indirect blocks.
406 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[1]),
407 (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
408 err(1, "bread: %s", disk.d_error);
410 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
412 DBG_DUMP_IBLK(&sblock,
415 howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
416 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
417 sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
418 ind2ptr = &((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
420 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
421 (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
422 err(1, "bread: %s", disk.d_error);
424 snprintf(comment, sizeof(comment),
425 "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
427 DBG_DUMP_IBLK(&sblock,
431 rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
436 * Dump triple indirect blocks.
438 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[2]),
439 (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
440 err(1, "bread: %s", disk.d_error);
442 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
444 #define SQUARE(a) ((a)*(a))
445 DBG_DUMP_IBLK(&sblock,
449 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
451 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
452 sizeof(ufs1_daddr_t))) && (rb > 0)); ind3ctr++) {
453 ind3ptr = &((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
455 if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
456 (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
457 err(1, "bread: %s", disk.d_error);
459 snprintf(comment, sizeof(comment),
460 "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
462 DBG_DUMP_IBLK(&sblock,
466 howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
467 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
468 sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
469 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
471 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
472 (void *)&i1blk, (size_t)sblock.fs_bsize)
474 err(1, "bread: %s", disk.d_error);
476 snprintf(comment, sizeof(comment),
477 "Inode 0x%08jx: indirect 2->%d->%d",
478 (uintmax_t)inode, ind3ctr, ind3ctr);
479 DBG_DUMP_IBLK(&sblock,
483 rb -= howmany(sblock.fs_bsize,
484 sizeof(ufs1_daddr_t));
493 /* ********************************************** dump_whole_ufs2_inode ***** */
495 * Here we dump a list of all blocks allocated by this inode. We follow
496 * all indirect blocks.
499 dump_whole_ufs2_inode(ino_t inode, int level)
501 DBG_FUNC("dump_whole_ufs2_inode")
504 unsigned int ind2ctr, ind3ctr;
505 ufs2_daddr_t *ind2ptr, *ind3ptr;
511 * Read the inode from disk/cache.
513 if (getinode(&disk, &dp, inode) == -1)
514 err(1, "getinode: %s", disk.d_error);
516 if (dp.dp2->di_nlink == 0) {
518 return; /* inode not in use */
522 * Dump the main inode structure.
524 snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
526 DBG_DUMP_INO(&sblock, comment, dp.dp2);
529 if (!(level & 0x200)) {
535 * Ok, now prepare for dumping all direct and indirect pointers.
537 rb = howmany(dp.dp2->di_size, sblock.fs_bsize) - UFS_NDADDR;
540 * Dump single indirect block.
542 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[0]),
543 (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
544 err(1, "bread: %s", disk.d_error);
546 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
548 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
549 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
553 * Dump double indirect blocks.
555 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[1]),
556 (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
557 err(1, "bread: %s", disk.d_error);
559 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
561 DBG_DUMP_IBLK(&sblock,
564 howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
565 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
566 sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
567 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
569 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
570 (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
571 err(1, "bread: %s", disk.d_error);
573 snprintf(comment, sizeof(comment),
574 "Inode 0x%08jx: indirect 1->%d",
575 (uintmax_t)inode, ind2ctr);
576 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
577 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
582 * Dump triple indirect blocks.
584 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[2]),
585 (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
586 err(1, "bread: %s", disk.d_error);
588 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
590 #define SQUARE(a) ((a)*(a))
591 DBG_DUMP_IBLK(&sblock,
595 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
597 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
598 sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
599 ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
601 if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
602 (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
603 err(1, "bread: %s", disk.d_error);
605 snprintf(comment, sizeof(comment),
606 "Inode 0x%08jx: indirect 2->%d",
607 (uintmax_t)inode, ind3ctr);
608 DBG_DUMP_IBLK(&sblock,
612 howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
613 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
614 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
615 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
616 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
617 (void *)&i1blk, (size_t)sblock.fs_bsize)
619 err(1, "bread: %s", disk.d_error);
621 snprintf(comment, sizeof(comment),
622 "Inode 0x%08jx: indirect 2->%d->%d",
623 (uintmax_t)inode, ind3ctr, ind3ctr);
624 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
625 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
634 /* ************************************************************* usage ***** */
636 * Dump a line of usage.
646 "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
648 " special | file\n");