]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/ffsinfo/ffsinfo.c
This commit was generated by cvs2svn to compensate for changes in r156373,
[FreeBSD/FreeBSD.git] / sbin / ffsinfo / ffsinfo.c
1 /*
2  * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
3  * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
4  * All rights reserved.
5  * 
6  * This code is derived from software contributed to Berkeley by
7  * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
8  * 
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgment:
19  *      This product includes software developed by the University of
20  *      California, Berkeley and its contributors, as well as Christoph
21  *      Herrmann and Thomas-Henning von Kamptz.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  * 
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  *
38  * $TSHeader: src/sbin/ffsinfo/ffsinfo.c,v 1.4 2000/12/12 19:30:55 tomsoft Exp $
39  *
40  */
41
42 #ifndef lint
43 static const char copyright[] =
44 "@(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz\n\
45 Copyright (c) 1980, 1989, 1993 The Regents of the University of California.\n\
46 All rights reserved.\n";
47 #endif /* not lint */
48
49 #ifndef lint
50 static const char rcsid[] =
51   "$FreeBSD$";
52 #endif /* not lint */
53
54 /* ********************************************************** INCLUDES ***** */
55 #include <sys/param.h>
56 #include <sys/disklabel.h>
57 #include <sys/mount.h>
58 #include <sys/stat.h>
59
60 #include <ufs/ufs/ufsmount.h>
61 #include <ufs/ufs/dinode.h>
62 #include <ufs/ffs/fs.h>
63
64 #include <ctype.h>
65 #include <err.h>
66 #include <errno.h>
67 #include <fcntl.h>
68 #include <libufs.h>
69 #include <paths.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74
75 #include "debug.h"
76
77 /* *********************************************************** GLOBALS ***** */
78 #ifdef FS_DEBUG
79 int     _dbg_lvl_ = (DL_INFO); /* DL_TRC */
80 #endif /* FS_DEBUG */
81
82 struct uufsd disk;
83
84 #define sblock disk.d_fs
85 #define acg    disk.d_cg
86
87 static union {
88         struct fs fs;
89         char pad[SBLOCKSIZE];
90 } fsun;
91
92 #define osblock fsun.fs
93
94 static char     i1blk[MAXBSIZE];
95 static char     i2blk[MAXBSIZE];
96 static char     i3blk[MAXBSIZE];
97
98 static struct csum      *fscs;
99
100 /* ******************************************************** PROTOTYPES ***** */
101 static void     usage(void);
102 static void     dump_whole_ufs1_inode(ino_t, int);
103 static void     dump_whole_ufs2_inode(ino_t, int);
104
105 #define DUMP_WHOLE_INODE(A,B) \
106         ( disk.d_ufs == 1 \
107                 ? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
108
109 /* ************************************************************** main ***** */
110 /*
111  * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
112  * errors is the file system much easier. You can run ffsinfo before and  after
113  * an  fsck(8),  and compare the two ascii dumps easy with diff, and  you  see
114  * directly where the problem is. You can control how much detail you want  to
115  * see  with some command line arguments. You can also easy check  the  status
116  * of  a file system, like is there is enough space for growing  a  file system,
117  * or  how  many active snapshots do we have. It provides much  more  detailed
118  * information  then dumpfs. Snapshots, as they are very new, are  not  really
119  * supported.  They  are just mentioned currently, but it is  planned  to  run
120  * also over active snapshots, to even get that output.
121  */
122 int
123 main(int argc, char **argv)
124 {
125         DBG_FUNC("main")
126         char    *device, *special;
127         int     ch;
128         size_t  len;
129         struct stat     st;
130         struct csum     *dbg_csp;
131         int     dbg_csc;
132         char    dbg_line[80];
133         int     cylno,i;
134         int     cfg_cg, cfg_in, cfg_lv;
135         int     cg_start, cg_stop;
136         ino_t   in;
137         char    *out_file;
138
139         DBG_ENTER;
140
141         cfg_lv=0xff;
142         cfg_in=-2;
143         cfg_cg=-2;
144         out_file=NULL;
145
146         while ((ch=getopt(argc, argv, "g:i:l:o:")) != -1) {
147                 switch(ch) {
148                 case 'g':
149                         cfg_cg=strtol(optarg, NULL, 0);
150                         if(errno == EINVAL||errno == ERANGE)
151                                 err(1, "%s", optarg);
152                         if(cfg_cg < -1) {
153                                 usage();
154                         }
155                         break;
156                 case 'i':
157                         cfg_in=strtol(optarg, NULL, 0);
158                         if(errno == EINVAL||errno == ERANGE)
159                                 err(1, "%s", optarg);
160                         if(cfg_in < 0) {
161                                 usage();
162                         }
163                         break; 
164                 case 'l':
165                         cfg_lv=strtol(optarg, NULL, 0);
166                         if(errno == EINVAL||errno == ERANGE)
167                                 err(1, "%s", optarg);
168                         if(cfg_lv < 0x1||cfg_lv > 0x3ff) {
169                                 usage();
170                         }
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                         }
178                         break;
179                 case '?':
180                         /* FALLTHROUGH */
181                 default:
182                         usage();
183                 }
184         }
185         argc -= optind;
186         argv += optind;
187
188         if(argc != 1) {
189                 usage();
190         }
191         device=*argv;
192         if (out_file == NULL)
193                 errx(1, "out_file not specified");
194         
195         /*
196          * Now we try to guess the (raw)device name.
197          */
198         if (0 == strrchr(device, '/') && (stat(device, &st) == -1)) {
199                 /*
200                  * No path prefix was given, so try in that order:
201                  *     /dev/r%s
202                  *     /dev/%s
203                  *     /dev/vinum/r%s
204                  *     /dev/vinum/%s.
205                  * 
206                  * FreeBSD now doesn't distinguish between raw and  block
207                  * devices any longer, but it should still work this way.
208                  */
209                 len=strlen(device)+strlen(_PATH_DEV)+2+strlen("vinum/");
210                 special=(char *)malloc(len);
211                 if(special == NULL) {
212                         errx(1, "malloc failed");
213                 }
214                 snprintf(special, len, "%sr%s", _PATH_DEV, device);
215                 if (stat(special, &st) == -1) {
216                         snprintf(special, len, "%s%s", _PATH_DEV, device);
217                         if (stat(special, &st) == -1) {
218                                 snprintf(special, len, "%svinum/r%s",
219                                     _PATH_DEV, device);
220                                 if (stat(special, &st) == -1) {
221                                         /*
222                                          * For now this is the 'last resort'.
223                                          */
224                                         snprintf(special, len, "%svinum/%s",
225                                             _PATH_DEV, device);
226                                 }
227                         }
228                 }
229                 device = special;
230         }
231
232         if (ufs_disk_fillout(&disk, device) == -1)
233                 err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
234
235         DBG_OPEN(out_file); /* already here we need a superblock */
236
237         if(cfg_lv & 0x001) {
238                 DBG_DUMP_FS(&sblock,
239                     "primary sblock");
240         }
241
242         /*
243          * Determine here what cylinder groups to dump.
244          */
245         if(cfg_cg==-2) {
246                 cg_start=0;
247                 cg_stop=sblock.fs_ncg;
248         } else if (cfg_cg==-1) {
249                 cg_start=sblock.fs_ncg-1;
250                 cg_stop=sblock.fs_ncg;
251         } else if (cfg_cg<sblock.fs_ncg) {
252                 cg_start=cfg_cg;
253                 cg_stop=cfg_cg+1;
254         } else {
255                 cg_start=sblock.fs_ncg;
256                 cg_stop=sblock.fs_ncg;
257         }
258
259         if (cfg_lv & 0x004) {
260                 fscs = (struct csum *)calloc((size_t)1,
261                     (size_t)sblock.fs_cssize);
262                 if(fscs == NULL) {
263                         errx(1, "calloc failed");
264                 }
265
266                 /*
267                  * Get the cylinder summary into the memory ...
268                  */
269                 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
270                         if (bread(&disk, 
271                                 fsbtodb(&sblock, sblock.fs_csaddr + numfrags(&sblock, i)), 
272                                 (void *)(((char *)fscs)+i), 
273                                 (size_t)(sblock.fs_cssize-i < sblock.fs_bsize 
274                                         ? sblock.fs_cssize - i 
275                                         : sblock.fs_bsize)) == -1) {
276                                 err(1, "bread: %s", disk.d_error);
277                         }
278                 }
279
280                 dbg_csp=fscs;
281                 /*
282                  * ... and dump it.
283                  */
284                 for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
285                         snprintf(dbg_line, sizeof(dbg_line),
286                             "%d. csum in fscs", dbg_csc);
287                         DBG_DUMP_CSUM(&sblock,
288                             dbg_line,
289                             dbg_csp++);
290                 }
291         }
292
293         /*
294          * For each requested cylinder group ...
295          */
296         for(cylno=cg_start; cylno<cg_stop; cylno++) {
297                 snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
298                 if(cfg_lv & 0x002) {
299                         /*
300                          * ... dump the superblock copies ...
301                          */
302                         if (bread(&disk, fsbtodb(&sblock, cgsblock(&sblock, cylno)), 
303                                 (void *)&osblock, SBLOCKSIZE) == -1) {
304                                 err(1, "bread: %s", disk.d_error);
305                         }
306                         DBG_DUMP_FS(&osblock,
307                             dbg_line);
308                 }
309                 /*
310                  * ... read the cylinder group and dump whatever was requested.
311                  */
312                 if (bread(&disk, fsbtodb(&sblock, cgtod(&sblock, cylno)), 
313                         (void *)&acg, (size_t)sblock.fs_cgsize) == -1) {
314                         err(1, "bread: %s", disk.d_error);
315                 }
316                 if(cfg_lv & 0x008) {
317                         DBG_DUMP_CG(&sblock,
318                             dbg_line,
319                             &acg);
320                 }
321                 if(cfg_lv & 0x010) {
322                         DBG_DUMP_INMAP(&sblock,
323                             dbg_line,
324                             &acg);
325                 }
326                 if(cfg_lv & 0x020) {
327                         DBG_DUMP_FRMAP(&sblock,
328                             dbg_line,
329                             &acg);
330                 }
331                 if(cfg_lv & 0x040) {
332                         DBG_DUMP_CLMAP(&sblock,
333                             dbg_line,
334                             &acg);
335                         DBG_DUMP_CLSUM(&sblock,
336                             dbg_line,
337                             &acg);
338                 }
339 #ifdef NOT_CURRENTLY
340                 /*
341                  * See the comment in sbin/growfs/debug.c for why this
342                  * is currently disabled, and what needs to be done to
343                  * re-enable it.
344                  */
345                 if(disk.d_ufs == 1 && cfg_lv & 0x080) {
346                         DBG_DUMP_SPTBL(&sblock,
347                             dbg_line,
348                             &acg);
349                 }
350 #endif
351         }
352         /*
353          * Dump the requested inode(s).
354          */
355         if(cfg_in != -2) {
356                 DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
357         } else {
358                 for(in=cg_start*sblock.fs_ipg; in<(ino_t)cg_stop*sblock.fs_ipg; 
359                     in++) {
360                         DUMP_WHOLE_INODE(in, cfg_lv);
361                 }
362         }
363
364         DBG_CLOSE;
365
366         DBG_LEAVE;
367         return 0;
368 }
369
370 /* ********************************************** dump_whole_ufs1_inode ***** */
371 /*
372  * Here we dump a list of all blocks allocated by this inode. We follow
373  * all indirect blocks.
374  */
375 void
376 dump_whole_ufs1_inode(ino_t inode, int level)
377 {
378         DBG_FUNC("dump_whole_ufs1_inode")
379         struct ufs1_dinode      *ino;
380         int     rb, mode;
381         unsigned int    ind2ctr, ind3ctr;
382         ufs1_daddr_t    *ind2ptr, *ind3ptr;
383         char    comment[80];
384         
385         DBG_ENTER;
386
387         /*
388          * Read the inode from disk/cache.
389          */
390         if (getino(&disk, (void **)&ino, inode, &mode) == -1)
391                 err(1, "getino: %s", disk.d_error);
392
393         if(ino->di_nlink==0) {
394                 DBG_LEAVE;
395                 return; /* inode not in use */
396         }
397
398         /*
399          * Dump the main inode structure.
400          */
401         snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
402         if (level & 0x100) {
403                 DBG_DUMP_INO(&sblock,
404                     comment,
405                     ino);
406         }
407
408         if (!(level & 0x200)) {
409                 DBG_LEAVE;
410                 return;
411         }
412
413         /*
414          * Ok, now prepare for dumping all direct and indirect pointers.
415          */
416         rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
417         if(rb>0) {
418                 /*
419                  * Dump single indirect block.
420                  */
421                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 
422                         (size_t)sblock.fs_bsize) == -1) {
423                         err(1, "bread: %s", disk.d_error);
424                 }
425                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0",
426                     inode);
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         if(rb>0) {
434                 /*
435                  * Dump double indirect blocks.
436                  */
437                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 
438                         (size_t)sblock.fs_bsize) == -1) {
439                         err(1, "bread: %s", disk.d_error);
440                 }
441                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1",
442                     inode);
443                 DBG_DUMP_IBLK(&sblock,
444                     comment,
445                     i2blk,
446                     howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
447                 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
448                         sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) {
449                         ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
450
451                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
452                                 (size_t)sblock.fs_bsize) == -1) {
453                                 err(1, "bread: %s", disk.d_error);
454                         }
455                         snprintf(comment, sizeof(comment),
456                             "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
457                         DBG_DUMP_IBLK(&sblock,
458                             comment,
459                             i1blk,
460                             (size_t)rb);
461                         rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
462                 }
463         }
464         if(rb>0) {
465                 /*
466                  * Dump triple indirect blocks.
467                  */
468                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 
469                         (size_t)sblock.fs_bsize) == -1) {
470                         err(1, "bread: %s", disk.d_error);
471                 }
472                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2",
473                     inode);
474 #define SQUARE(a) ((a)*(a))
475                 DBG_DUMP_IBLK(&sblock,
476                     comment,
477                     i3blk,
478                     howmany(rb,
479                       SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
480 #undef SQUARE
481                 for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize,
482                         sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) {
483                         ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
484
485                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk, 
486                                 (size_t)sblock.fs_bsize) == -1) {
487                                 err(1, "bread: %s", disk.d_error);
488                         }
489                         snprintf(comment, sizeof(comment),
490                             "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
491                         DBG_DUMP_IBLK(&sblock,
492                             comment,
493                             i2blk,
494                             howmany(rb,
495                               howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
496                         for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
497                             sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) {
498                                 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
499                                     [ind2ctr];
500                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
501                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
502                                     == -1) {
503                                         err(1, "bread: %s", disk.d_error);
504                                 }
505                                 snprintf(comment, sizeof(comment),
506                                     "Inode 0x%08x: indirect 2->%d->%d", inode,
507                                     ind3ctr, ind3ctr);
508                                 DBG_DUMP_IBLK(&sblock,
509                                     comment,
510                                     i1blk,
511                                     (size_t)rb);
512                                 rb-=howmany(sblock.fs_bsize,
513                                     sizeof(ufs1_daddr_t));
514                         }
515                 }
516         }
517
518         DBG_LEAVE;
519         return;
520 }
521
522 /* ********************************************** dump_whole_ufs2_inode ***** */
523 /*
524  * Here we dump a list of all blocks allocated by this inode. We follow
525  * all indirect blocks.
526  */
527 void
528 dump_whole_ufs2_inode(ino_t inode, int level)
529 {
530         DBG_FUNC("dump_whole_ufs2_inode")
531         struct ufs2_dinode      *ino;
532         int     rb, mode;
533         unsigned int    ind2ctr, ind3ctr;
534         ufs2_daddr_t    *ind2ptr, *ind3ptr;
535         char    comment[80];
536         
537         DBG_ENTER;
538
539         /*
540          * Read the inode from disk/cache.
541          */
542         if (getino(&disk, (void **)&ino, inode, &mode) == -1)
543                 err(1, "getino: %s", disk.d_error);
544
545         if (ino->di_nlink == 0) {
546                 DBG_LEAVE;
547                 return; /* inode not in use */
548         }
549
550         /*
551          * Dump the main inode structure.
552          */
553         snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
554         if (level & 0x100) {
555                 DBG_DUMP_INO(&sblock, comment, ino);
556         }
557
558         if (!(level & 0x200)) {
559                 DBG_LEAVE;
560                 return;
561         }
562
563         /*
564          * Ok, now prepare for dumping all direct and indirect pointers.
565          */
566         rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR;
567         if (rb > 0) {
568                 /*
569                  * Dump single indirect block.
570                  */
571                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 
572                         (size_t)sblock.fs_bsize) == -1) {
573                         err(1, "bread: %s", disk.d_error);
574                 }
575                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", inode);
576                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
577                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
578         }
579         if (rb > 0) {
580                 /*
581                  * Dump double indirect blocks.
582                  */
583                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 
584                         (size_t)sblock.fs_bsize) == -1) {
585                         err(1, "bread: %s", disk.d_error);
586                 }
587                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", inode);
588                 DBG_DUMP_IBLK(&sblock,
589                         comment,
590                         i2blk,
591                         howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
592                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
593                         sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
594                         ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
595
596                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
597                                 (size_t)sblock.fs_bsize) == -1) {
598                                 err(1, "bread: %s", disk.d_error);
599                         }
600                         snprintf(comment, sizeof(comment),
601                                 "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
602                         DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
603                         rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
604                 }
605         }
606         if (rb > 0) {
607                 /*
608                  * Dump triple indirect blocks.
609                  */
610                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 
611                         (size_t)sblock.fs_bsize) == -1) {
612                         err(1, "bread: %s", disk.d_error);
613                 }
614                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", inode);
615 #define SQUARE(a) ((a)*(a))
616                 DBG_DUMP_IBLK(&sblock,
617                         comment,
618                         i3blk,
619                         howmany(rb,
620                                 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
621 #undef SQUARE
622                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
623                         sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
624                         ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
625
626                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk, 
627                                 (size_t)sblock.fs_bsize) == -1) {
628                                 err(1, "bread: %s", disk.d_error);
629                         }
630                         snprintf(comment, sizeof(comment),
631                                 "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
632                         DBG_DUMP_IBLK(&sblock,
633                                 comment,
634                                 i2blk,
635                                 howmany(rb,
636                                         howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
637                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
638                                 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
639                                 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
640                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
641                                         (size_t)sblock.fs_bsize) == -1) {
642                                         err(1, "bread: %s", disk.d_error);
643                                 }
644                                 snprintf(comment, sizeof(comment),
645                                         "Inode 0x%08x: indirect 2->%d->%d", inode,
646                                         ind3ctr, ind3ctr);
647                                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
648                                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
649                         }
650                 }
651         }
652
653         DBG_LEAVE;
654         return;
655 }
656
657 /* ************************************************************* usage ***** */
658 /*
659  * Dump a line of usage.
660  */
661 void
662 usage(void)
663 {
664         DBG_FUNC("usage")       
665
666         DBG_ENTER;
667
668         fprintf(stderr,
669             "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
670             "[-o outfile]\n"
671             "               special | file\n");
672
673         DBG_LEAVE;
674         exit(1);
675 }