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