]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sbin/ffsinfo/ffsinfo.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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 static 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 = strdup("-");
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                         break;
155                 case 'i':
156                         cfg_in = strtol(optarg, NULL, 0);
157                         if (errno == EINVAL || errno == ERANGE)
158                                 err(1, "%s", optarg);
159                         if (cfg_in < 0)
160                                 usage();
161                         break; 
162                 case 'l':
163                         cfg_lv = strtol(optarg, NULL, 0);
164                         if (errno == EINVAL||errno == ERANGE)
165                                 err(1, "%s", optarg);
166                         if (cfg_lv < 0x1 || cfg_lv > 0x3ff)
167                                 usage();
168                         break;
169                 case 'o':
170                         free(out_file);
171                         out_file = strdup(optarg);
172                         if (out_file == NULL)
173                                 errx(1, "strdup failed");
174                         break;
175                 case '?':
176                         /* FALLTHROUGH */
177                 default:
178                         usage();
179                 }
180         }
181         argc -= optind;
182         argv += optind;
183
184         if (argc != 1)
185                 usage();
186         device = *argv;
187
188         /*
189          * Now we try to guess the (raw)device name.
190          */
191         if (0 == strrchr(device, '/') && stat(device, &st) == -1) {
192                 /*-
193                  * No path prefix was given, so try in this order:
194                  *     /dev/r%s
195                  *     /dev/%s
196                  *     /dev/vinum/r%s
197                  *     /dev/vinum/%s.
198                  * 
199                  * FreeBSD now doesn't distinguish between raw and  block
200                  * devices any longer, but it should still work this way.
201                  */
202                 len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
203                 special = (char *)malloc(len);
204                 if (special == NULL)
205                         errx(1, "malloc failed");
206                 snprintf(special, len, "%sr%s", _PATH_DEV, device);
207                 if (stat(special, &st) == -1) {
208                         snprintf(special, len, "%s%s", _PATH_DEV, device);
209                         if (stat(special, &st) == -1) {
210                                 snprintf(special, len, "%svinum/r%s",
211                                     _PATH_DEV, device);
212                                 if (stat(special, &st) == -1)
213                                         /* For now this is the 'last resort' */
214                                         snprintf(special, len, "%svinum/%s",
215                                             _PATH_DEV, device);
216                         }
217                 }
218                 device = special;
219         }
220
221         if (ufs_disk_fillout(&disk, device) == -1)
222                 err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
223
224         DBG_OPEN(out_file);     /* already here we need a superblock */
225
226         if (cfg_lv & 0x001)
227                 DBG_DUMP_FS(&sblock, "primary sblock");
228
229         /* Determine here what cylinder groups to dump */
230         if (cfg_cg==-2) {
231                 cg_start = 0;
232                 cg_stop = sblock.fs_ncg;
233         } else if (cfg_cg == -1) {
234                 cg_start = sblock.fs_ncg - 1;
235                 cg_stop = sblock.fs_ncg;
236         } else if (cfg_cg < sblock.fs_ncg) {
237                 cg_start = cfg_cg;
238                 cg_stop = cfg_cg + 1;
239         } else {
240                 cg_start = sblock.fs_ncg;
241                 cg_stop = sblock.fs_ncg;
242         }
243
244         if (cfg_lv & 0x004) {
245                 fscs = (struct csum *)calloc((size_t)1,
246                     (size_t)sblock.fs_cssize);
247                 if (fscs == NULL)
248                         errx(1, "calloc failed");
249
250                 /* get the cylinder summary into the memory ... */
251                 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
252                         if (bread(&disk, fsbtodb(&sblock,
253                             sblock.fs_csaddr + numfrags(&sblock, i)), 
254                             (void *)(((char *)fscs)+i), 
255                             (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ?
256                             sblock.fs_cssize - i : sblock.fs_bsize)) == -1)
257                                 err(1, "bread: %s", disk.d_error);
258                 }
259
260                 dbg_csp = fscs;
261                 /* ... and dump it */
262                 for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
263                         snprintf(dbg_line, sizeof(dbg_line),
264                             "%d. csum in fscs", dbg_csc);
265                         DBG_DUMP_CSUM(&sblock,
266                             dbg_line,
267                             dbg_csp++);
268                 }
269         }
270
271         if (cfg_lv & 0xf8) {
272                 /* for each requested cylinder group ... */
273                 for (cylno = cg_start; cylno < cg_stop; cylno++) {
274                         snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
275                         if (cfg_lv & 0x002) {
276                                 /* dump the superblock copies */
277                                 if (bread(&disk, fsbtodb(&sblock,
278                                     cgsblock(&sblock, cylno)), 
279                                     (void *)&osblock, SBLOCKSIZE) == -1)
280                                         err(1, "bread: %s", disk.d_error);
281                                 DBG_DUMP_FS(&osblock, dbg_line);
282                         }
283
284                         /*
285                          * Read the cylinder group and dump whatever was
286                          * requested.
287                          */
288                         if (bread(&disk, fsbtodb(&sblock,
289                             cgtod(&sblock, cylno)), (void *)&acg,
290                             (size_t)sblock.fs_cgsize) == -1)
291                                 err(1, "bread: %s", disk.d_error);
292
293                         if (cfg_lv & 0x008)
294                                 DBG_DUMP_CG(&sblock, dbg_line, &acg);
295                         if (cfg_lv & 0x010)
296                                 DBG_DUMP_INMAP(&sblock, dbg_line, &acg);
297                         if (cfg_lv & 0x020)
298                                 DBG_DUMP_FRMAP(&sblock, dbg_line, &acg);
299                         if (cfg_lv & 0x040) {
300                                 DBG_DUMP_CLMAP(&sblock, dbg_line, &acg);
301                                 DBG_DUMP_CLSUM(&sblock, dbg_line, &acg);
302                         }
303         #ifdef NOT_CURRENTLY
304                         /*
305                          * See the comment in sbin/growfs/debug.c for why this
306                          * is currently disabled, and what needs to be done to
307                          * re-enable it.
308                          */
309                         if (disk.d_ufs == 1 && cfg_lv & 0x080)
310                                 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg);
311         #endif
312                 }
313         }
314
315         if (cfg_lv & 0x300) {
316                 /* Dump the requested inode(s) */
317                 if (cfg_in != -2)
318                         DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
319                 else {
320                         for (in = cg_start * sblock.fs_ipg;
321                             in < (ino_t)cg_stop * sblock.fs_ipg; 
322                             in++)
323                                 DUMP_WHOLE_INODE(in, cfg_lv);
324                 }
325         }
326
327         DBG_CLOSE;
328         DBG_LEAVE;
329
330         return 0;
331 }
332
333 /* ********************************************** dump_whole_ufs1_inode ***** */
334 /*
335  * Here we dump a list of all blocks allocated by this inode. We follow
336  * all indirect blocks.
337  */
338 void
339 dump_whole_ufs1_inode(ino_t inode, int level)
340 {
341         DBG_FUNC("dump_whole_ufs1_inode")
342         struct ufs1_dinode      *ino;
343         int     rb, mode;
344         unsigned int    ind2ctr, ind3ctr;
345         ufs1_daddr_t    *ind2ptr, *ind3ptr;
346         char    comment[80];
347         
348         DBG_ENTER;
349
350         /*
351          * Read the inode from disk/cache.
352          */
353         if (getino(&disk, (void **)&ino, inode, &mode) == -1)
354                 err(1, "getino: %s", disk.d_error);
355
356         if(ino->di_nlink==0) {
357                 DBG_LEAVE;
358                 return; /* inode not in use */
359         }
360
361         /*
362          * Dump the main inode structure.
363          */
364         snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
365         if (level & 0x100) {
366                 DBG_DUMP_INO(&sblock,
367                     comment,
368                     ino);
369         }
370
371         if (!(level & 0x200)) {
372                 DBG_LEAVE;
373                 return;
374         }
375
376         /*
377          * Ok, now prepare for dumping all direct and indirect pointers.
378          */
379         rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
380         if(rb>0) {
381                 /*
382                  * Dump single indirect block.
383                  */
384                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 
385                         (size_t)sblock.fs_bsize) == -1) {
386                         err(1, "bread: %s", disk.d_error);
387                 }
388                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0",
389                     inode);
390                 DBG_DUMP_IBLK(&sblock,
391                     comment,
392                     i1blk,
393                     (size_t)rb);
394                 rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
395         }
396         if(rb>0) {
397                 /*
398                  * Dump double indirect blocks.
399                  */
400                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 
401                         (size_t)sblock.fs_bsize) == -1) {
402                         err(1, "bread: %s", disk.d_error);
403                 }
404                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1",
405                     inode);
406                 DBG_DUMP_IBLK(&sblock,
407                     comment,
408                     i2blk,
409                     howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
410                 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
411                         sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) {
412                         ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
413
414                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
415                                 (size_t)sblock.fs_bsize) == -1) {
416                                 err(1, "bread: %s", disk.d_error);
417                         }
418                         snprintf(comment, sizeof(comment),
419                             "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
420                         DBG_DUMP_IBLK(&sblock,
421                             comment,
422                             i1blk,
423                             (size_t)rb);
424                         rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
425                 }
426         }
427         if(rb>0) {
428                 /*
429                  * Dump triple indirect blocks.
430                  */
431                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 
432                         (size_t)sblock.fs_bsize) == -1) {
433                         err(1, "bread: %s", disk.d_error);
434                 }
435                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2",
436                     inode);
437 #define SQUARE(a) ((a)*(a))
438                 DBG_DUMP_IBLK(&sblock,
439                     comment,
440                     i3blk,
441                     howmany(rb,
442                       SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
443 #undef SQUARE
444                 for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize,
445                         sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) {
446                         ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
447
448                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk, 
449                                 (size_t)sblock.fs_bsize) == -1) {
450                                 err(1, "bread: %s", disk.d_error);
451                         }
452                         snprintf(comment, sizeof(comment),
453                             "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
454                         DBG_DUMP_IBLK(&sblock,
455                             comment,
456                             i2blk,
457                             howmany(rb,
458                               howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
459                         for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
460                             sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) {
461                                 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
462                                     [ind2ctr];
463                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
464                                     (void *)&i1blk, (size_t)sblock.fs_bsize)
465                                     == -1) {
466                                         err(1, "bread: %s", disk.d_error);
467                                 }
468                                 snprintf(comment, sizeof(comment),
469                                     "Inode 0x%08x: indirect 2->%d->%d", inode,
470                                     ind3ctr, ind3ctr);
471                                 DBG_DUMP_IBLK(&sblock,
472                                     comment,
473                                     i1blk,
474                                     (size_t)rb);
475                                 rb-=howmany(sblock.fs_bsize,
476                                     sizeof(ufs1_daddr_t));
477                         }
478                 }
479         }
480
481         DBG_LEAVE;
482         return;
483 }
484
485 /* ********************************************** dump_whole_ufs2_inode ***** */
486 /*
487  * Here we dump a list of all blocks allocated by this inode. We follow
488  * all indirect blocks.
489  */
490 void
491 dump_whole_ufs2_inode(ino_t inode, int level)
492 {
493         DBG_FUNC("dump_whole_ufs2_inode")
494         struct ufs2_dinode      *ino;
495         int     rb, mode;
496         unsigned int    ind2ctr, ind3ctr;
497         ufs2_daddr_t    *ind2ptr, *ind3ptr;
498         char    comment[80];
499         
500         DBG_ENTER;
501
502         /*
503          * Read the inode from disk/cache.
504          */
505         if (getino(&disk, (void **)&ino, inode, &mode) == -1)
506                 err(1, "getino: %s", disk.d_error);
507
508         if (ino->di_nlink == 0) {
509                 DBG_LEAVE;
510                 return; /* inode not in use */
511         }
512
513         /*
514          * Dump the main inode structure.
515          */
516         snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
517         if (level & 0x100) {
518                 DBG_DUMP_INO(&sblock, comment, ino);
519         }
520
521         if (!(level & 0x200)) {
522                 DBG_LEAVE;
523                 return;
524         }
525
526         /*
527          * Ok, now prepare for dumping all direct and indirect pointers.
528          */
529         rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR;
530         if (rb > 0) {
531                 /*
532                  * Dump single indirect block.
533                  */
534                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 
535                         (size_t)sblock.fs_bsize) == -1) {
536                         err(1, "bread: %s", disk.d_error);
537                 }
538                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", inode);
539                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
540                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
541         }
542         if (rb > 0) {
543                 /*
544                  * Dump double indirect blocks.
545                  */
546                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 
547                         (size_t)sblock.fs_bsize) == -1) {
548                         err(1, "bread: %s", disk.d_error);
549                 }
550                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", inode);
551                 DBG_DUMP_IBLK(&sblock,
552                         comment,
553                         i2blk,
554                         howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
555                 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
556                         sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
557                         ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
558
559                         if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
560                                 (size_t)sblock.fs_bsize) == -1) {
561                                 err(1, "bread: %s", disk.d_error);
562                         }
563                         snprintf(comment, sizeof(comment),
564                                 "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
565                         DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
566                         rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
567                 }
568         }
569         if (rb > 0) {
570                 /*
571                  * Dump triple indirect blocks.
572                  */
573                 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 
574                         (size_t)sblock.fs_bsize) == -1) {
575                         err(1, "bread: %s", disk.d_error);
576                 }
577                 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", inode);
578 #define SQUARE(a) ((a)*(a))
579                 DBG_DUMP_IBLK(&sblock,
580                         comment,
581                         i3blk,
582                         howmany(rb,
583                                 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
584 #undef SQUARE
585                 for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
586                         sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
587                         ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
588
589                         if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk, 
590                                 (size_t)sblock.fs_bsize) == -1) {
591                                 err(1, "bread: %s", disk.d_error);
592                         }
593                         snprintf(comment, sizeof(comment),
594                                 "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
595                         DBG_DUMP_IBLK(&sblock,
596                                 comment,
597                                 i2blk,
598                                 howmany(rb,
599                                         howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
600                         for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
601                                 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
602                                 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
603                                 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 
604                                         (size_t)sblock.fs_bsize) == -1) {
605                                         err(1, "bread: %s", disk.d_error);
606                                 }
607                                 snprintf(comment, sizeof(comment),
608                                         "Inode 0x%08x: indirect 2->%d->%d", inode,
609                                         ind3ctr, ind3ctr);
610                                 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
611                                 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
612                         }
613                 }
614         }
615
616         DBG_LEAVE;
617         return;
618 }
619
620 /* ************************************************************* usage ***** */
621 /*
622  * Dump a line of usage.
623  */
624 void
625 usage(void)
626 {
627         DBG_FUNC("usage")       
628
629         DBG_ENTER;
630
631         fprintf(stderr,
632             "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
633             "[-o outfile]\n"
634             "               special | file\n");
635
636         DBG_LEAVE;
637         exit(1);
638 }