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