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