]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/ffsinfo/ffsinfo.c
rtld-elf: link udivmoddi4 from compiler_rt
[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/ufsmount.h>
63 #include <ufs/ufs/dinode.h>
64 #include <ufs/ffs/fs.h>
65
66 #include <ctype.h>
67 #include <err.h>
68 #include <errno.h>
69 #include <fcntl.h>
70 #include <libufs.h>
71 #include <paths.h>
72 #include <stdint.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <unistd.h>
77
78 #include "debug.h"
79
80 /* *********************************************************** GLOBALS ***** */
81 #ifdef FS_DEBUG
82 int     _dbg_lvl_ = (DL_INFO); /* DL_TRC */
83 #endif /* FS_DEBUG */
84
85 static struct uufsd disk;
86
87 #define sblock disk.d_fs
88 #define acg    disk.d_cg
89
90 static union {
91         struct fs fs;
92         char pad[SBLOCKSIZE];
93 } fsun;
94
95 #define osblock fsun.fs
96
97 static char     i1blk[MAXBSIZE];
98 static char     i2blk[MAXBSIZE];
99 static char     i3blk[MAXBSIZE];
100
101 static struct csum      *fscs;
102
103 /* ******************************************************** PROTOTYPES ***** */
104 static void     usage(void);
105 static void     dump_whole_ufs1_inode(ino_t, int);
106 static void     dump_whole_ufs2_inode(ino_t, int);
107
108 #define DUMP_WHOLE_INODE(A,B) \
109         ( disk.d_ufs == 1 \
110                 ? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
111
112 /* ************************************************************** main ***** */
113 /*
114  * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
115  * errors is the file system much easier. You can run ffsinfo before and  after
116  * an  fsck(8),  and compare the two ascii dumps easy with diff, and  you  see
117  * directly where the problem is. You can control how much detail you want  to
118  * see  with some command line arguments. You can also easy check  the  status
119  * of  a file system, like is there is enough space for growing  a  file system,
120  * or  how  many active snapshots do we have. It provides much  more  detailed
121  * information  then dumpfs. Snapshots, as they are very new, are  not  really
122  * supported.  They  are just mentioned currently, but it is  planned  to  run
123  * also over active snapshots, to even get that output.
124  */
125 int
126 main(int argc, char **argv)
127 {
128         DBG_FUNC("main")
129         char    *device, *special;
130         int     ch;
131         size_t  len;
132         struct stat     st;
133         struct csum     *dbg_csp;
134         int     dbg_csc;
135         char    dbg_line[80];
136         int     cylno,i;
137         int     cfg_cg, cfg_in, cfg_lv;
138         int     cg_start, cg_stop;
139         ino_t   in;
140         char    *out_file;
141
142         DBG_ENTER;
143
144         cfg_lv = 0xff;
145         cfg_in = -2;
146         cfg_cg = -2;
147         out_file = strdup("-");
148
149         while ((ch = getopt(argc, argv, "g:i:l:o:")) != -1) {
150                 switch (ch) {
151                 case 'g':
152                         cfg_cg = strtol(optarg, NULL, 0);
153                         if (errno == EINVAL || errno == ERANGE)
154                                 err(1, "%s", optarg);
155                         if (cfg_cg < -1)
156                                 usage();
157                         break;
158                 case 'i':
159                         cfg_in = strtol(optarg, NULL, 0);
160                         if (errno == EINVAL || errno == ERANGE)
161                                 err(1, "%s", optarg);
162                         if (cfg_in < 0)
163                                 usage();
164                         break; 
165                 case 'l':
166                         cfg_lv = strtol(optarg, NULL, 0);
167                         if (errno == EINVAL||errno == ERANGE)
168                                 err(1, "%s", optarg);
169                         if (cfg_lv < 0x1 || cfg_lv > 0x3ff)
170                                 usage();
171                         break;
172                 case 'o':
173                         free(out_file);
174                         out_file = strdup(optarg);
175                         if (out_file == NULL)
176                                 errx(1, "strdup failed");
177                         break;
178                 case '?':
179                         /* FALLTHROUGH */
180                 default:
181                         usage();
182                 }
183         }
184         argc -= optind;
185         argv += optind;
186
187         if (argc != 1)
188                 usage();
189         device = *argv;
190
191         /*
192          * Now we try to guess the (raw)device name.
193          */
194         if (0 == strrchr(device, '/') && stat(device, &st) == -1) {
195                 /*-
196                  * No path prefix was given, so try in this order:
197                  *     /dev/r%s
198                  *     /dev/%s
199                  *     /dev/vinum/r%s
200                  *     /dev/vinum/%s.
201                  * 
202                  * FreeBSD now doesn't distinguish between raw and  block
203                  * devices any longer, but it should still work this way.
204                  */
205                 len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
206                 special = (char *)malloc(len);
207                 if (special == NULL)
208                         errx(1, "malloc failed");
209                 snprintf(special, len, "%sr%s", _PATH_DEV, device);
210                 if (stat(special, &st) == -1) {
211                         snprintf(special, len, "%s%s", _PATH_DEV, device);
212                         if (stat(special, &st) == -1) {
213                                 snprintf(special, len, "%svinum/r%s",
214                                     _PATH_DEV, device);
215                                 if (stat(special, &st) == -1)
216                                         /* For now this is the 'last resort' */
217                                         snprintf(special, len, "%svinum/%s",
218                                             _PATH_DEV, device);
219                         }
220                 }
221                 device = special;
222         }
223
224         if (ufs_disk_fillout(&disk, device) == -1)
225                 err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
226
227         DBG_OPEN(out_file);     /* already here we need a superblock */
228
229         if (cfg_lv & 0x001)
230                 DBG_DUMP_FS(&sblock, "primary sblock");
231
232         /* Determine here what cylinder groups to dump */
233         if (cfg_cg==-2) {
234                 cg_start = 0;
235                 cg_stop = sblock.fs_ncg;
236         } else if (cfg_cg == -1) {
237                 cg_start = sblock.fs_ncg - 1;
238                 cg_stop = sblock.fs_ncg;
239         } else if (cfg_cg < sblock.fs_ncg) {
240                 cg_start = cfg_cg;
241                 cg_stop = cfg_cg + 1;
242         } else {
243                 cg_start = sblock.fs_ncg;
244                 cg_stop = sblock.fs_ncg;
245         }
246
247         if (cfg_lv & 0x004) {
248                 fscs = (struct csum *)calloc((size_t)1,
249                     (size_t)sblock.fs_cssize);
250                 if (fscs == NULL)
251                         errx(1, "calloc failed");
252
253                 /* get the cylinder summary into the memory ... */
254                 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
255                         if (bread(&disk, fsbtodb(&sblock,
256                             sblock.fs_csaddr + numfrags(&sblock, i)), 
257                             (void *)(((char *)fscs)+i), 
258                             (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ?
259                             sblock.fs_cssize - i : sblock.fs_bsize)) == -1)
260                                 err(1, "bread: %s", disk.d_error);
261                 }
262
263                 dbg_csp = fscs;
264                 /* ... and dump it */
265                 for (dbg_csc = 0; dbg_csc < sblock.fs_ncg; dbg_csc++) {
266                         snprintf(dbg_line, sizeof(dbg_line),
267                             "%d. csum in fscs", dbg_csc);
268                         DBG_DUMP_CSUM(&sblock,
269                             dbg_line,
270                             dbg_csp++);
271                 }
272         }
273
274         if (cfg_lv & 0xf8) {
275                 /* for each requested cylinder group ... */
276                 for (cylno = cg_start; cylno < cg_stop; cylno++) {
277                         snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
278                         if (cfg_lv & 0x002) {
279                                 /* dump the superblock copies */
280                                 if (bread(&disk, fsbtodb(&sblock,
281                                     cgsblock(&sblock, cylno)), 
282                                     (void *)&osblock, SBLOCKSIZE) == -1)
283                                         err(1, "bread: %s", disk.d_error);
284                                 DBG_DUMP_FS(&osblock, dbg_line);
285                         }
286
287                         /*
288                          * Read the cylinder group and dump whatever was
289                          * requested.
290                          */
291                         if (bread(&disk, fsbtodb(&sblock,
292                             cgtod(&sblock, cylno)), (void *)&acg,
293                             (size_t)sblock.fs_cgsize) == -1)
294                                 err(1, "bread: %s", disk.d_error);
295
296                         if (cfg_lv & 0x008)
297                                 DBG_DUMP_CG(&sblock, dbg_line, &acg);
298                         if (cfg_lv & 0x010)
299                                 DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
300                         if (cfg_lv & 0x020)
301                                 DBG_DUMP_FRMAP(&sblock, dbg_line, &acg);
302                         if (cfg_lv & 0x040) {
303                                 DBG_DUMP_CLMAP(&sblock, dbg_line, &acg);
304                                 DBG_DUMP_CLSUM(&sblock, dbg_line, &acg);
305                         }
306         #ifdef NOT_CURRENTLY
307                         /*
308                          * See the comment in sbin/growfs/debug.c for why this
309                          * is currently disabled, and what needs to be done to
310                          * re-enable it.
311                          */
312                         if (disk.d_ufs == 1 && cfg_lv & 0x080)
313                                 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
314         #endif
315                 }
316         }
317
318         if (cfg_lv & 0x300) {
319                 /* Dump the requested inode(s) */
320                 if (cfg_in != -2)
321                         DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
322                 else {
323                         for (in = cg_start * sblock.fs_ipg;
324                             in < (ino_t)cg_stop * sblock.fs_ipg; 
325                             in++)
326                                 DUMP_WHOLE_INODE(in, cfg_lv);
327                 }
328         }
329
330         DBG_CLOSE;
331         DBG_LEAVE;
332
333         return 0;
334 }
335
336 /* ********************************************** dump_whole_ufs1_inode ***** */
337 /*
338  * Here we dump a list of all blocks allocated by this inode. We follow
339  * all indirect blocks.
340  */
341 void
342 dump_whole_ufs1_inode(ino_t inode, int level)
343 {
344         DBG_FUNC("dump_whole_ufs1_inode")
345         union dinodep dp;
346         int     rb;
347         unsigned int    ind2ctr, ind3ctr;
348         ufs1_daddr_t    *ind2ptr, *ind3ptr;
349         char    comment[80];
350         
351         DBG_ENTER;
352
353         /*
354          * Read the inode from disk/cache.
355          */
356         if (getinode(&disk, &dp, inode) == -1)
357                 err(1, "getinode: %s", disk.d_error);
358
359         if (dp.dp1->di_nlink == 0) {
360                 DBG_LEAVE;
361                 return; /* inode not in use */
362         }
363
364         /*
365          * Dump the main inode structure.
366          */
367         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
368         if (level & 0x100) {
369                 DBG_DUMP_INO(&sblock,
370                     comment,
371                     dp.dp1);
372         }
373
374         if (!(level & 0x200)) {
375                 DBG_LEAVE;
376                 return;
377         }
378
379         /*
380          * Ok, now prepare for dumping all direct and indirect pointers.
381          */
382         rb = howmany(dp.dp1->di_size, sblock.fs_bsize) - UFS_NDADDR;
383         if (rb > 0) {
384                 /*
385                  * Dump single indirect block.
386                  */
387                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[0]),
388                     (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
389                         err(1, "bread: %s", disk.d_error);
390                 }
391                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
392                     (uintmax_t)inode);
393                 DBG_DUMP_IBLK(&sblock,
394                     comment,
395                     i1blk,
396                     (size_t)rb);
397                 rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
398         }
399         if (rb > 0) {
400                 /*
401                  * Dump double indirect blocks.
402                  */
403                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[1]),
404                     (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
405                         err(1, "bread: %s", disk.d_error);
406                 }
407                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
408                     (uintmax_t)inode);
409                 DBG_DUMP_IBLK(&sblock,
410                     comment,
411                     i2blk,
412                     howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
413                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
414                         sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
415                         ind2ptr = &((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
416
417                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
418                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
419                                 err(1, "bread: %s", disk.d_error);
420                         }
421                         snprintf(comment, sizeof(comment),
422                             "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
423                             ind2ctr);
424                         DBG_DUMP_IBLK(&sblock,
425                             comment,
426                             i1blk,
427                             (size_t)rb);
428                         rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
429                 }
430         }
431         if (rb > 0) {
432                 /*
433                  * Dump triple indirect blocks.
434                  */
435                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[2]),
436                     (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
437                         err(1, "bread: %s", disk.d_error);
438                 }
439                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
440                     (uintmax_t)inode);
441 #define SQUARE(a) ((a)*(a))
442                 DBG_DUMP_IBLK(&sblock,
443                     comment,
444                     i3blk,
445                     howmany(rb,
446                       SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
447 #undef SQUARE
448                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
449                         sizeof(ufs1_daddr_t))) && (rb > 0)); ind3ctr++) {
450                         ind3ptr = &((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
451
452                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
453                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
454                                 err(1, "bread: %s", disk.d_error);
455                         }
456                         snprintf(comment, sizeof(comment),
457                             "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
458                             ind3ctr);
459                         DBG_DUMP_IBLK(&sblock,
460                             comment,
461                             i2blk,
462                             howmany(rb,
463                               howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
464                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
465                              sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
466                                 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
467                                     [ind2ctr];
468                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
469                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
470                                     == -1) {
471                                         err(1, "bread: %s", disk.d_error);
472                                 }
473                                 snprintf(comment, sizeof(comment),
474                                     "Inode 0x%08jx: indirect 2->%d->%d",
475                                     (uintmax_t)inode, ind3ctr, ind3ctr);
476                                 DBG_DUMP_IBLK(&sblock,
477                                     comment,
478                                     i1blk,
479                                     (size_t)rb);
480                                 rb -= howmany(sblock.fs_bsize,
481                                     sizeof(ufs1_daddr_t));
482                         }
483                 }
484         }
485
486         DBG_LEAVE;
487         return;
488 }
489
490 /* ********************************************** dump_whole_ufs2_inode ***** */
491 /*
492  * Here we dump a list of all blocks allocated by this inode. We follow
493  * all indirect blocks.
494  */
495 void
496 dump_whole_ufs2_inode(ino_t inode, int level)
497 {
498         DBG_FUNC("dump_whole_ufs2_inode")
499         union dinodep dp;
500         int     rb;
501         unsigned int    ind2ctr, ind3ctr;
502         ufs2_daddr_t    *ind2ptr, *ind3ptr;
503         char    comment[80];
504         
505         DBG_ENTER;
506
507         /*
508          * Read the inode from disk/cache.
509          */
510         if (getinode(&disk, &dp, inode) == -1)
511                 err(1, "getinode: %s", disk.d_error);
512
513         if (dp.dp2->di_nlink == 0) {
514                 DBG_LEAVE;
515                 return; /* inode not in use */
516         }
517
518         /*
519          * Dump the main inode structure.
520          */
521         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
522         if (level & 0x100) {
523                 DBG_DUMP_INO(&sblock, comment, dp.dp2);
524         }
525
526         if (!(level & 0x200)) {
527                 DBG_LEAVE;
528                 return;
529         }
530
531         /*
532          * Ok, now prepare for dumping all direct and indirect pointers.
533          */
534         rb = howmany(dp.dp2->di_size, sblock.fs_bsize) - UFS_NDADDR;
535         if (rb > 0) {
536                 /*
537                  * Dump single indirect block.
538                  */
539                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[0]),
540                     (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
541                         err(1, "bread: %s", disk.d_error);
542                 }
543                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
544                     (uintmax_t)inode);
545                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
546                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
547         }
548         if (rb > 0) {
549                 /*
550                  * Dump double indirect blocks.
551                  */
552                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[1]),
553                     (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
554                         err(1, "bread: %s", disk.d_error);
555                 }
556                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
557                     (uintmax_t)inode);
558                 DBG_DUMP_IBLK(&sblock,
559                         comment,
560                         i2blk,
561                         howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
562                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
563                         sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
564                         ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
565
566                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
567                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
568                                 err(1, "bread: %s", disk.d_error);
569                         }
570                         snprintf(comment, sizeof(comment),
571                                 "Inode 0x%08jx: indirect 1->%d",
572                                 (uintmax_t)inode, ind2ctr);
573                         DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
574                         rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
575                 }
576         }
577         if (rb > 0) {
578                 /*
579                  * Dump triple indirect blocks.
580                  */
581                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[2]),
582                     (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
583                         err(1, "bread: %s", disk.d_error);
584                 }
585                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
586                     (uintmax_t)inode);
587 #define SQUARE(a) ((a)*(a))
588                 DBG_DUMP_IBLK(&sblock,
589                         comment,
590                         i3blk,
591                         howmany(rb,
592                                 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
593 #undef SQUARE
594                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
595                         sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
596                         ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
597
598                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
599                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
600                                 err(1, "bread: %s", disk.d_error);
601                         }
602                         snprintf(comment, sizeof(comment),
603                                 "Inode 0x%08jx: indirect 2->%d",
604                                 (uintmax_t)inode, ind3ctr);
605                         DBG_DUMP_IBLK(&sblock,
606                                 comment,
607                                 i2blk,
608                                 howmany(rb,
609                                         howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
610                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
611                                 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
612                                 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
613                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
614                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
615                                     == -1) {
616                                         err(1, "bread: %s", disk.d_error);
617                                 }
618                                 snprintf(comment, sizeof(comment),
619                                         "Inode 0x%08jx: indirect 2->%d->%d",
620                                         (uintmax_t)inode, ind3ctr, ind3ctr);
621                                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
622                                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
623                         }
624                 }
625         }
626
627         DBG_LEAVE;
628         return;
629 }
630
631 /* ************************************************************* usage ***** */
632 /*
633  * Dump a line of usage.
634  */
635 void
636 usage(void)
637 {
638         DBG_FUNC("usage")       
639
640         DBG_ENTER;
641
642         fprintf(stderr,
643             "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
644             "[-o outfile]\n"
645             "               special | file\n");
646
647         DBG_LEAVE;
648         exit(1);
649 }