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 /* ********************************************************** INCLUDES ***** */ 52 #include <sys/param.h> 53 #include <sys/disklabel.h> 54 #include <sys/mount.h> 55 #include <sys/stat.h> 56 57 #include <ufs/ufs/extattr.h> 58 #include <ufs/ufs/quota.h> 59 #include <ufs/ufs/ufsmount.h> 60 #include <ufs/ufs/dinode.h> 61 #include <ufs/ffs/fs.h> 62 63 #include <ctype.h> 64 #include <err.h> 65 #include <errno.h> 66 #include <fcntl.h> 67 #include <libufs.h> 68 #include <paths.h> 69 #include <stdint.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_blank(&disk, device) == -1 || 222 sbfind(&disk, 0) == -1) 223 err(1, "superblock fetch(%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 union dinodep dp; 344 int rb; 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 (getinode(&disk, &dp, inode) == -1) 355 err(1, "getinode: %s", disk.d_error); 356 357 if (dp.dp1->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 dp.dp1); 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(dp.dp1->di_size, sblock.fs_bsize) - UFS_NDADDR; 381 if (rb > 0) { 382 /* 383 * Dump single indirect block. 384 */ 385 if (bread(&disk, fsbtodb(&sblock, dp.dp1->di_ib[0]), 386 (void *)&i1blk, (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, dp.dp1->di_ib[1]), 402 (void *)&i2blk, (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), 416 (void *)&i1blk, (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, dp.dp1->di_ib[2]), 434 (void *)&i3blk, (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), 451 (void *)&i2blk, (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 union dinodep dp; 498 int rb; 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 (getinode(&disk, &dp, inode) == -1) 509 err(1, "getinode: %s", disk.d_error); 510 511 if (dp.dp2->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, dp.dp2); 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(dp.dp2->di_size, sblock.fs_bsize) - UFS_NDADDR; 533 if (rb > 0) { 534 /* 535 * Dump single indirect block. 536 */ 537 if (bread(&disk, fsbtodb(&sblock, dp.dp2->di_ib[0]), 538 (void *)&i1blk, (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, dp.dp2->di_ib[1]), 551 (void *)&i2blk, (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), 565 (void *)&i1blk, (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, dp.dp2->di_ib[2]), 580 (void *)&i3blk, (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), 597 (void *)&i2blk, (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), 612 (void *)&i1blk, (size_t)sblock.fs_bsize) 613 == -1) { 614 err(1, "bread: %s", disk.d_error); 615 } 616 snprintf(comment, sizeof(comment), 617 "Inode 0x%08jx: indirect 2->%d->%d", 618 (uintmax_t)inode, ind3ctr, ind3ctr); 619 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb); 620 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)); 621 } 622 } 623 } 624 625 DBG_LEAVE; 626 return; 627 } 628 629 /* ************************************************************* usage ***** */ 630 /* 631 * Dump a line of usage. 632 */ 633 void 634 usage(void) 635 { 636 DBG_FUNC("usage") 637 638 DBG_ENTER; 639 640 fprintf(stderr, 641 "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] " 642 "[-o outfile]\n" 643 " special | file\n"); 644 645 DBG_LEAVE; 646 exit(1); 647 } 648