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 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 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 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 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