]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/ffsinfo/ffsinfo.c
Merge llvm-project release/16.x llvmorg-16.0.1-0-gcd89023f7979
[FreeBSD/FreeBSD.git] / sbin / ffsinfo / ffsinfo.c
1 /*-
2  * SPDX-License-Identifier: BSD-4-Clause
3  *
4  * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
5  * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
6  * All rights reserved.
7  * 
8  * This code is derived from software contributed to Berkeley by
9  * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
10  * 
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
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.
27  * 
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
38  * SUCH DAMAGE.
39  *
40  * $TSHeader: src/sbin/ffsinfo/ffsinfo.c,v 1.4 2000/12/12 19:30:55 tomsoft Exp $
41  *
42  */
43
44 #ifndef lint
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";
49 #endif /* not lint */
50
51 #ifndef lint
52 static const char rcsid[] =
53   "$FreeBSD$";
54 #endif /* not lint */
55
56 /* ********************************************************** INCLUDES ***** */
57 #include <sys/param.h>
58 #include <sys/disklabel.h>
59 #include <sys/mount.h>
60 #include <sys/stat.h>
61
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>
67
68 #include <ctype.h>
69 #include <err.h>
70 #include <errno.h>
71 #include <fcntl.h>
72 #include <libufs.h>
73 #include <paths.h>
74 #include <stdint.h>
75 #include <stdio.h>
76 #include <stdlib.h>
77 #include <string.h>
78 #include <unistd.h>
79
80 #include "debug.h"
81
82 /* *********************************************************** GLOBALS ***** */
83 #ifdef FS_DEBUG
84 int     _dbg_lvl_ = (DL_INFO); /* DL_TRC */
85 #endif /* FS_DEBUG */
86
87 static struct uufsd disk;
88
89 #define sblock disk.d_fs
90 #define acg    disk.d_cg
91
92 static union {
93         struct fs fs;
94         char pad[SBLOCKSIZE];
95 } fsun;
96
97 #define osblock fsun.fs
98
99 static char     i1blk[MAXBSIZE];
100 static char     i2blk[MAXBSIZE];
101 static char     i3blk[MAXBSIZE];
102
103 static struct csum      *fscs;
104
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);
109
110 #define DUMP_WHOLE_INODE(A,B) \
111         ( disk.d_ufs == 1 \
112                 ? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
113
114 /* ************************************************************** main ***** */
115 /*
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.
126  */
127 int
128 main(int argc, char **argv)
129 {
130         DBG_FUNC("main")
131         char    *device, *special;
132         int     ch;
133         size_t  len;
134         struct stat     st;
135         struct csum     *dbg_csp;
136         int     dbg_csc;
137         char    dbg_line[80];
138         int     cylno,i;
139         int     cfg_cg, cfg_in, cfg_lv;
140         int     cg_start, cg_stop;
141         ino_t   in;
142         char    *out_file;
143
144         DBG_ENTER;
145
146         cfg_lv = 0xff;
147         cfg_in = -2;
148         cfg_cg = -2;
149         out_file = strdup("-");
150
151         while ((ch = getopt(argc, argv, "g:i:l:o:")) != -1) {
152                 switch (ch) {
153                 case 'g':
154                         cfg_cg = strtol(optarg, NULL, 0);
155                         if (errno == EINVAL || errno == ERANGE)
156                                 err(1, "%s", optarg);
157                         if (cfg_cg < -1)
158                                 usage();
159                         break;
160                 case 'i':
161                         cfg_in = strtol(optarg, NULL, 0);
162                         if (errno == EINVAL || errno == ERANGE)
163                                 err(1, "%s", optarg);
164                         if (cfg_in < 0)
165                                 usage();
166                         break; 
167                 case 'l':
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)
172                                 usage();
173                         break;
174                 case 'o':
175                         free(out_file);
176                         out_file = strdup(optarg);
177                         if (out_file == NULL)
178                                 errx(1, "strdup failed");
179                         break;
180                 case '?':
181                         /* FALLTHROUGH */
182                 default:
183                         usage();
184                 }
185         }
186         argc -= optind;
187         argv += optind;
188
189         if (argc != 1)
190                 usage();
191         device = *argv;
192
193         /*
194          * Now we try to guess the (raw)device name.
195          */
196         if (0 == strrchr(device, '/') && stat(device, &st) == -1) {
197                 /*-
198                  * No path prefix was given, so try in this order:
199                  *     /dev/r%s
200                  *     /dev/%s
201                  *     /dev/vinum/r%s
202                  *     /dev/vinum/%s.
203                  * 
204                  * FreeBSD now doesn't distinguish between raw and  block
205                  * devices any longer, but it should still work this way.
206                  */
207                 len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
208                 special = (char *)malloc(len);
209                 if (special == NULL)
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",
216                                     _PATH_DEV, device);
217                                 if (stat(special, &st) == -1)
218                                         /* For now this is the 'last resort' */
219                                         snprintf(special, len, "%svinum/%s",
220                                             _PATH_DEV, device);
221                         }
222                 }
223                 device = special;
224         }
225
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);
229
230         DBG_OPEN(out_file);     /* already here we need a superblock */
231
232         if (cfg_lv & 0x001)
233                 DBG_DUMP_FS(&sblock, "primary sblock");
234
235         /* Determine here what cylinder groups to dump */
236         if (cfg_cg==-2) {
237                 cg_start = 0;
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) {
243                 cg_start = cfg_cg;
244                 cg_stop = cfg_cg + 1;
245         } else {
246                 cg_start = sblock.fs_ncg;
247                 cg_stop = sblock.fs_ncg;
248         }
249
250         if (cfg_lv & 0x004) {
251                 fscs = (struct csum *)calloc((size_t)1,
252                     (size_t)sblock.fs_cssize);
253                 if (fscs == NULL)
254                         errx(1, "calloc failed");
255
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);
264                 }
265
266                 dbg_csp = fscs;
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,
272                             dbg_line,
273                             dbg_csp++);
274                 }
275         }
276
277         if (cfg_lv & 0xf8) {
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);
288                         }
289
290                         /*
291                          * Read the cylinder group and dump whatever was
292                          * requested.
293                          */
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);
298
299                         if (cfg_lv & 0x008)
300                                 DBG_DUMP_CG(&sblock, dbg_line, &acg);
301                         if (cfg_lv & 0x010)
302                                 DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
303                         if (cfg_lv & 0x020)
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);
308                         }
309         #ifdef NOT_CURRENTLY
310                         /*
311                          * See the comment in sbin/growfs/debug.c for why this
312                          * is currently disabled, and what needs to be done to
313                          * re-enable it.
314                          */
315                         if (disk.d_ufs == 1 && cfg_lv & 0x080)
316                                 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
317         #endif
318                 }
319         }
320
321         if (cfg_lv & 0x300) {
322                 /* Dump the requested inode(s) */
323                 if (cfg_in != -2)
324                         DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
325                 else {
326                         for (in = cg_start * sblock.fs_ipg;
327                             in < (ino_t)cg_stop * sblock.fs_ipg; 
328                             in++)
329                                 DUMP_WHOLE_INODE(in, cfg_lv);
330                 }
331         }
332
333         DBG_CLOSE;
334         DBG_LEAVE;
335
336         return 0;
337 }
338
339 /* ********************************************** dump_whole_ufs1_inode ***** */
340 /*
341  * Here we dump a list of all blocks allocated by this inode. We follow
342  * all indirect blocks.
343  */
344 void
345 dump_whole_ufs1_inode(ino_t inode, int level)
346 {
347         DBG_FUNC("dump_whole_ufs1_inode")
348         union dinodep dp;
349         int     rb;
350         unsigned int    ind2ctr, ind3ctr;
351         ufs1_daddr_t    *ind2ptr, *ind3ptr;
352         char    comment[80];
353         
354         DBG_ENTER;
355
356         /*
357          * Read the inode from disk/cache.
358          */
359         if (getinode(&disk, &dp, inode) == -1)
360                 err(1, "getinode: %s", disk.d_error);
361
362         if (dp.dp1->di_nlink == 0) {
363                 DBG_LEAVE;
364                 return; /* inode not in use */
365         }
366
367         /*
368          * Dump the main inode structure.
369          */
370         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
371         if (level & 0x100) {
372                 DBG_DUMP_INO(&sblock,
373                     comment,
374                     dp.dp1);
375         }
376
377         if (!(level & 0x200)) {
378                 DBG_LEAVE;
379                 return;
380         }
381
382         /*
383          * Ok, now prepare for dumping all direct and indirect pointers.
384          */
385         rb = howmany(dp.dp1->di_size, sblock.fs_bsize) - UFS_NDADDR;
386         if (rb > 0) {
387                 /*
388                  * Dump single indirect block.
389                  */
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);
393                 }
394                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
395                     (uintmax_t)inode);
396                 DBG_DUMP_IBLK(&sblock,
397                     comment,
398                     i1blk,
399                     (size_t)rb);
400                 rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
401         }
402         if (rb > 0) {
403                 /*
404                  * Dump double indirect blocks.
405                  */
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);
409                 }
410                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
411                     (uintmax_t)inode);
412                 DBG_DUMP_IBLK(&sblock,
413                     comment,
414                     i2blk,
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];
419
420                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
421                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
422                                 err(1, "bread: %s", disk.d_error);
423                         }
424                         snprintf(comment, sizeof(comment),
425                             "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
426                             ind2ctr);
427                         DBG_DUMP_IBLK(&sblock,
428                             comment,
429                             i1blk,
430                             (size_t)rb);
431                         rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
432                 }
433         }
434         if (rb > 0) {
435                 /*
436                  * Dump triple indirect blocks.
437                  */
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);
441                 }
442                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
443                     (uintmax_t)inode);
444 #define SQUARE(a) ((a)*(a))
445                 DBG_DUMP_IBLK(&sblock,
446                     comment,
447                     i3blk,
448                     howmany(rb,
449                       SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
450 #undef SQUARE
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];
454
455                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
456                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
457                                 err(1, "bread: %s", disk.d_error);
458                         }
459                         snprintf(comment, sizeof(comment),
460                             "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
461                             ind3ctr);
462                         DBG_DUMP_IBLK(&sblock,
463                             comment,
464                             i2blk,
465                             howmany(rb,
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)
470                                     [ind2ctr];
471                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
472                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
473                                     == -1) {
474                                         err(1, "bread: %s", disk.d_error);
475                                 }
476                                 snprintf(comment, sizeof(comment),
477                                     "Inode 0x%08jx: indirect 2->%d->%d",
478                                     (uintmax_t)inode, ind3ctr, ind3ctr);
479                                 DBG_DUMP_IBLK(&sblock,
480                                     comment,
481                                     i1blk,
482                                     (size_t)rb);
483                                 rb -= howmany(sblock.fs_bsize,
484                                     sizeof(ufs1_daddr_t));
485                         }
486                 }
487         }
488
489         DBG_LEAVE;
490         return;
491 }
492
493 /* ********************************************** dump_whole_ufs2_inode ***** */
494 /*
495  * Here we dump a list of all blocks allocated by this inode. We follow
496  * all indirect blocks.
497  */
498 void
499 dump_whole_ufs2_inode(ino_t inode, int level)
500 {
501         DBG_FUNC("dump_whole_ufs2_inode")
502         union dinodep dp;
503         int     rb;
504         unsigned int    ind2ctr, ind3ctr;
505         ufs2_daddr_t    *ind2ptr, *ind3ptr;
506         char    comment[80];
507         
508         DBG_ENTER;
509
510         /*
511          * Read the inode from disk/cache.
512          */
513         if (getinode(&disk, &dp, inode) == -1)
514                 err(1, "getinode: %s", disk.d_error);
515
516         if (dp.dp2->di_nlink == 0) {
517                 DBG_LEAVE;
518                 return; /* inode not in use */
519         }
520
521         /*
522          * Dump the main inode structure.
523          */
524         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
525         if (level & 0x100) {
526                 DBG_DUMP_INO(&sblock, comment, dp.dp2);
527         }
528
529         if (!(level & 0x200)) {
530                 DBG_LEAVE;
531                 return;
532         }
533
534         /*
535          * Ok, now prepare for dumping all direct and indirect pointers.
536          */
537         rb = howmany(dp.dp2->di_size, sblock.fs_bsize) - UFS_NDADDR;
538         if (rb > 0) {
539                 /*
540                  * Dump single indirect block.
541                  */
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);
545                 }
546                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
547                     (uintmax_t)inode);
548                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
549                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
550         }
551         if (rb > 0) {
552                 /*
553                  * Dump double indirect blocks.
554                  */
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);
558                 }
559                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
560                     (uintmax_t)inode);
561                 DBG_DUMP_IBLK(&sblock,
562                         comment,
563                         i2blk,
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];
568
569                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
570                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
571                                 err(1, "bread: %s", disk.d_error);
572                         }
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));
578                 }
579         }
580         if (rb > 0) {
581                 /*
582                  * Dump triple indirect blocks.
583                  */
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);
587                 }
588                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
589                     (uintmax_t)inode);
590 #define SQUARE(a) ((a)*(a))
591                 DBG_DUMP_IBLK(&sblock,
592                         comment,
593                         i3blk,
594                         howmany(rb,
595                                 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
596 #undef SQUARE
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];
600
601                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
602                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
603                                 err(1, "bread: %s", disk.d_error);
604                         }
605                         snprintf(comment, sizeof(comment),
606                                 "Inode 0x%08jx: indirect 2->%d",
607                                 (uintmax_t)inode, ind3ctr);
608                         DBG_DUMP_IBLK(&sblock,
609                                 comment,
610                                 i2blk,
611                                 howmany(rb,
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)
618                                     == -1) {
619                                         err(1, "bread: %s", disk.d_error);
620                                 }
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));
626                         }
627                 }
628         }
629
630         DBG_LEAVE;
631         return;
632 }
633
634 /* ************************************************************* usage ***** */
635 /*
636  * Dump a line of usage.
637  */
638 void
639 usage(void)
640 {
641         DBG_FUNC("usage")       
642
643         DBG_ENTER;
644
645         fprintf(stderr,
646             "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
647             "[-o outfile]\n"
648             "               special | file\n");
649
650         DBG_LEAVE;
651         exit(1);
652 }