]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/ffsinfo/ffsinfo.c
Merge llvm-project main llvmorg-14-init-10223-g401b76fdf2b3
[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(&disk, device) == -1)
227                 err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
228
229         DBG_OPEN(out_file);     /* already here we need a superblock */
230
231         if (cfg_lv & 0x001)
232                 DBG_DUMP_FS(&sblock, "primary sblock");
233
234         /* Determine here what cylinder groups to dump */
235         if (cfg_cg==-2) {
236                 cg_start = 0;
237                 cg_stop = sblock.fs_ncg;
238         } else if (cfg_cg == -1) {
239                 cg_start = sblock.fs_ncg - 1;
240                 cg_stop = sblock.fs_ncg;
241         } else if (cfg_cg < sblock.fs_ncg) {
242                 cg_start = cfg_cg;
243                 cg_stop = cfg_cg + 1;
244         } else {
245                 cg_start = sblock.fs_ncg;
246                 cg_stop = sblock.fs_ncg;
247         }
248
249         if (cfg_lv & 0x004) {
250                 fscs = (struct csum *)calloc((size_t)1,
251                     (size_t)sblock.fs_cssize);
252                 if (fscs == NULL)
253                         errx(1, "calloc failed");
254
255                 /* get the cylinder summary into the memory ... */
256                 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
257                         if (bread(&disk, fsbtodb(&sblock,
258                             sblock.fs_csaddr + numfrags(&sblock, i)), 
259                             (void *)(((char *)fscs)+i), 
260                             (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ?
261                             sblock.fs_cssize - i : sblock.fs_bsize)) == -1)
262                                 err(1, "bread: %s", disk.d_error);
263                 }
264
265                 dbg_csp = fscs;
266                 /* ... and dump it */
267                 for (dbg_csc = 0; dbg_csc < sblock.fs_ncg; dbg_csc++) {
268                         snprintf(dbg_line, sizeof(dbg_line),
269                             "%d. csum in fscs", dbg_csc);
270                         DBG_DUMP_CSUM(&sblock,
271                             dbg_line,
272                             dbg_csp++);
273                 }
274         }
275
276         if (cfg_lv & 0xf8) {
277                 /* for each requested cylinder group ... */
278                 for (cylno = cg_start; cylno < cg_stop; cylno++) {
279                         snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
280                         if (cfg_lv & 0x002) {
281                                 /* dump the superblock copies */
282                                 if (bread(&disk, fsbtodb(&sblock,
283                                     cgsblock(&sblock, cylno)), 
284                                     (void *)&osblock, SBLOCKSIZE) == -1)
285                                         err(1, "bread: %s", disk.d_error);
286                                 DBG_DUMP_FS(&osblock, dbg_line);
287                         }
288
289                         /*
290                          * Read the cylinder group and dump whatever was
291                          * requested.
292                          */
293                         if (bread(&disk, fsbtodb(&sblock,
294                             cgtod(&sblock, cylno)), (void *)&acg,
295                             (size_t)sblock.fs_cgsize) == -1)
296                                 err(1, "bread: %s", disk.d_error);
297
298                         if (cfg_lv & 0x008)
299                                 DBG_DUMP_CG(&sblock, dbg_line, &acg);
300                         if (cfg_lv & 0x010)
301                                 DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
302                         if (cfg_lv & 0x020)
303                                 DBG_DUMP_FRMAP(&sblock, dbg_line, &acg);
304                         if (cfg_lv & 0x040) {
305                                 DBG_DUMP_CLMAP(&sblock, dbg_line, &acg);
306                                 DBG_DUMP_CLSUM(&sblock, dbg_line, &acg);
307                         }
308         #ifdef NOT_CURRENTLY
309                         /*
310                          * See the comment in sbin/growfs/debug.c for why this
311                          * is currently disabled, and what needs to be done to
312                          * re-enable it.
313                          */
314                         if (disk.d_ufs == 1 && cfg_lv & 0x080)
315                                 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
316         #endif
317                 }
318         }
319
320         if (cfg_lv & 0x300) {
321                 /* Dump the requested inode(s) */
322                 if (cfg_in != -2)
323                         DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
324                 else {
325                         for (in = cg_start * sblock.fs_ipg;
326                             in < (ino_t)cg_stop * sblock.fs_ipg; 
327                             in++)
328                                 DUMP_WHOLE_INODE(in, cfg_lv);
329                 }
330         }
331
332         DBG_CLOSE;
333         DBG_LEAVE;
334
335         return 0;
336 }
337
338 /* ********************************************** dump_whole_ufs1_inode ***** */
339 /*
340  * Here we dump a list of all blocks allocated by this inode. We follow
341  * all indirect blocks.
342  */
343 void
344 dump_whole_ufs1_inode(ino_t inode, int level)
345 {
346         DBG_FUNC("dump_whole_ufs1_inode")
347         union dinodep dp;
348         int     rb;
349         unsigned int    ind2ctr, ind3ctr;
350         ufs1_daddr_t    *ind2ptr, *ind3ptr;
351         char    comment[80];
352         
353         DBG_ENTER;
354
355         /*
356          * Read the inode from disk/cache.
357          */
358         if (getinode(&disk, &dp, inode) == -1)
359                 err(1, "getinode: %s", disk.d_error);
360
361         if (dp.dp1->di_nlink == 0) {
362                 DBG_LEAVE;
363                 return; /* inode not in use */
364         }
365
366         /*
367          * Dump the main inode structure.
368          */
369         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
370         if (level & 0x100) {
371                 DBG_DUMP_INO(&sblock,
372                     comment,
373                     dp.dp1);
374         }
375
376         if (!(level & 0x200)) {
377                 DBG_LEAVE;
378                 return;
379         }
380
381         /*
382          * Ok, now prepare for dumping all direct and indirect pointers.
383          */
384         rb = howmany(dp.dp1->di_size, sblock.fs_bsize) - UFS_NDADDR;
385         if (rb > 0) {
386                 /*
387                  * Dump single indirect block.
388                  */
389                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[0]),
390                     (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
391                         err(1, "bread: %s", disk.d_error);
392                 }
393                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
394                     (uintmax_t)inode);
395                 DBG_DUMP_IBLK(&sblock,
396                     comment,
397                     i1blk,
398                     (size_t)rb);
399                 rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
400         }
401         if (rb > 0) {
402                 /*
403                  * Dump double indirect blocks.
404                  */
405                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[1]),
406                     (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
407                         err(1, "bread: %s", disk.d_error);
408                 }
409                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
410                     (uintmax_t)inode);
411                 DBG_DUMP_IBLK(&sblock,
412                     comment,
413                     i2blk,
414                     howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
415                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
416                         sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
417                         ind2ptr = &((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
418
419                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
420                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
421                                 err(1, "bread: %s", disk.d_error);
422                         }
423                         snprintf(comment, sizeof(comment),
424                             "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
425                             ind2ctr);
426                         DBG_DUMP_IBLK(&sblock,
427                             comment,
428                             i1blk,
429                             (size_t)rb);
430                         rb -= howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
431                 }
432         }
433         if (rb > 0) {
434                 /*
435                  * Dump triple indirect blocks.
436                  */
437                 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[2]),
438                     (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
439                         err(1, "bread: %s", disk.d_error);
440                 }
441                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
442                     (uintmax_t)inode);
443 #define SQUARE(a) ((a)*(a))
444                 DBG_DUMP_IBLK(&sblock,
445                     comment,
446                     i3blk,
447                     howmany(rb,
448                       SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
449 #undef SQUARE
450                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
451                         sizeof(ufs1_daddr_t))) && (rb > 0)); ind3ctr++) {
452                         ind3ptr = &((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
453
454                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
455                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
456                                 err(1, "bread: %s", disk.d_error);
457                         }
458                         snprintf(comment, sizeof(comment),
459                             "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
460                             ind3ctr);
461                         DBG_DUMP_IBLK(&sblock,
462                             comment,
463                             i2blk,
464                             howmany(rb,
465                               howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
466                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
467                              sizeof(ufs1_daddr_t))) && (rb > 0)); ind2ctr++) {
468                                 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
469                                     [ind2ctr];
470                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
471                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
472                                     == -1) {
473                                         err(1, "bread: %s", disk.d_error);
474                                 }
475                                 snprintf(comment, sizeof(comment),
476                                     "Inode 0x%08jx: indirect 2->%d->%d",
477                                     (uintmax_t)inode, ind3ctr, ind3ctr);
478                                 DBG_DUMP_IBLK(&sblock,
479                                     comment,
480                                     i1blk,
481                                     (size_t)rb);
482                                 rb -= howmany(sblock.fs_bsize,
483                                     sizeof(ufs1_daddr_t));
484                         }
485                 }
486         }
487
488         DBG_LEAVE;
489         return;
490 }
491
492 /* ********************************************** dump_whole_ufs2_inode ***** */
493 /*
494  * Here we dump a list of all blocks allocated by this inode. We follow
495  * all indirect blocks.
496  */
497 void
498 dump_whole_ufs2_inode(ino_t inode, int level)
499 {
500         DBG_FUNC("dump_whole_ufs2_inode")
501         union dinodep dp;
502         int     rb;
503         unsigned int    ind2ctr, ind3ctr;
504         ufs2_daddr_t    *ind2ptr, *ind3ptr;
505         char    comment[80];
506         
507         DBG_ENTER;
508
509         /*
510          * Read the inode from disk/cache.
511          */
512         if (getinode(&disk, &dp, inode) == -1)
513                 err(1, "getinode: %s", disk.d_error);
514
515         if (dp.dp2->di_nlink == 0) {
516                 DBG_LEAVE;
517                 return; /* inode not in use */
518         }
519
520         /*
521          * Dump the main inode structure.
522          */
523         snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
524         if (level & 0x100) {
525                 DBG_DUMP_INO(&sblock, comment, dp.dp2);
526         }
527
528         if (!(level & 0x200)) {
529                 DBG_LEAVE;
530                 return;
531         }
532
533         /*
534          * Ok, now prepare for dumping all direct and indirect pointers.
535          */
536         rb = howmany(dp.dp2->di_size, sblock.fs_bsize) - UFS_NDADDR;
537         if (rb > 0) {
538                 /*
539                  * Dump single indirect block.
540                  */
541                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[0]),
542                     (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
543                         err(1, "bread: %s", disk.d_error);
544                 }
545                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
546                     (uintmax_t)inode);
547                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
548                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
549         }
550         if (rb > 0) {
551                 /*
552                  * Dump double indirect blocks.
553                  */
554                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[1]),
555                     (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
556                         err(1, "bread: %s", disk.d_error);
557                 }
558                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
559                     (uintmax_t)inode);
560                 DBG_DUMP_IBLK(&sblock,
561                         comment,
562                         i2blk,
563                         howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
564                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
565                         sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
566                         ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
567
568                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
569                             (void *)&i1blk, (size_t)sblock.fs_bsize) == -1) {
570                                 err(1, "bread: %s", disk.d_error);
571                         }
572                         snprintf(comment, sizeof(comment),
573                                 "Inode 0x%08jx: indirect 1->%d",
574                                 (uintmax_t)inode, ind2ctr);
575                         DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
576                         rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
577                 }
578         }
579         if (rb > 0) {
580                 /*
581                  * Dump triple indirect blocks.
582                  */
583                 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[2]),
584                     (void *)&i3blk, (size_t)sblock.fs_bsize) == -1) {
585                         err(1, "bread: %s", disk.d_error);
586                 }
587                 snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
588                     (uintmax_t)inode);
589 #define SQUARE(a) ((a)*(a))
590                 DBG_DUMP_IBLK(&sblock,
591                         comment,
592                         i3blk,
593                         howmany(rb,
594                                 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
595 #undef SQUARE
596                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
597                         sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
598                         ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
599
600                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr),
601                             (void *)&i2blk, (size_t)sblock.fs_bsize) == -1) {
602                                 err(1, "bread: %s", disk.d_error);
603                         }
604                         snprintf(comment, sizeof(comment),
605                                 "Inode 0x%08jx: indirect 2->%d",
606                                 (uintmax_t)inode, ind3ctr);
607                         DBG_DUMP_IBLK(&sblock,
608                                 comment,
609                                 i2blk,
610                                 howmany(rb,
611                                         howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
612                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
613                                 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
614                                 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
615                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
616                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
617                                     == -1) {
618                                         err(1, "bread: %s", disk.d_error);
619                                 }
620                                 snprintf(comment, sizeof(comment),
621                                         "Inode 0x%08jx: indirect 2->%d->%d",
622                                         (uintmax_t)inode, ind3ctr, ind3ctr);
623                                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
624                                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
625                         }
626                 }
627         }
628
629         DBG_LEAVE;
630         return;
631 }
632
633 /* ************************************************************* usage ***** */
634 /*
635  * Dump a line of usage.
636  */
637 void
638 usage(void)
639 {
640         DBG_FUNC("usage")       
641
642         DBG_ENTER;
643
644         fprintf(stderr,
645             "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
646             "[-o outfile]\n"
647             "               special | file\n");
648
649         DBG_LEAVE;
650         exit(1);
651 }