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