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 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 342 dump_whole_ufs1_inode(ino_t inode, int level) 343 { 344 DBG_FUNC("dump_whole_ufs1_inode") 345 struct ufs1_dinode *ino; 346 int rb, mode; 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 (getino(&disk, (void **)&ino, inode, &mode) == -1) 357 err(1, "getino: %s", disk.d_error); 358 359 if(ino->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 ino); 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(ino->di_size, sblock.fs_bsize) - UFS_NDADDR; 383 if(rb>0) { 384 /* 385 * Dump single indirect block. 386 */ 387 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 388 (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, ino->di_ib[1]), (void *)&i2blk, 404 (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), (void *)&i1blk, 418 (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, ino->di_ib[2]), (void *)&i3blk, 436 (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), (void *)&i2blk, 453 (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 496 dump_whole_ufs2_inode(ino_t inode, int level) 497 { 498 DBG_FUNC("dump_whole_ufs2_inode") 499 struct ufs2_dinode *ino; 500 int rb, mode; 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 (getino(&disk, (void **)&ino, inode, &mode) == -1) 511 err(1, "getino: %s", disk.d_error); 512 513 if (ino->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, ino); 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(ino->di_size, sblock.fs_bsize) - UFS_NDADDR; 535 if (rb > 0) { 536 /* 537 * Dump single indirect block. 538 */ 539 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 540 (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, ino->di_ib[1]), (void *)&i2blk, 553 (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), (void *)&i1blk, 567 (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, ino->di_ib[2]), (void *)&i3blk, 582 (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), (void *)&i2blk, 599 (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), (void *)&i1blk, 614 (size_t)sblock.fs_bsize) == -1) { 615 err(1, "bread: %s", disk.d_error); 616 } 617 snprintf(comment, sizeof(comment), 618 "Inode 0x%08jx: indirect 2->%d->%d", 619 (uintmax_t)inode, ind3ctr, ind3ctr); 620 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb); 621 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)); 622 } 623 } 624 } 625 626 DBG_LEAVE; 627 return; 628 } 629 630 /* ************************************************************* usage ***** */ 631 /* 632 * Dump a line of usage. 633 */ 634 void 635 usage(void) 636 { 637 DBG_FUNC("usage") 638 639 DBG_ENTER; 640 641 fprintf(stderr, 642 "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] " 643 "[-o outfile]\n" 644 " special | file\n"); 645 646 DBG_LEAVE; 647 exit(1); 648 } 649