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 <errno.h> 67 #include <fcntl.h> 68 #include <libufs.h> 69 #include <paths.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 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 = "-"; 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(&disk, device) == -1) 222 err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error); 223 224 DBG_OPEN(out_file); /* already here we need a superblock */ 225 226 if (cfg_lv & 0x001) 227 DBG_DUMP_FS(&sblock, "primary sblock"); 228 229 /* Determine here what cylinder groups to dump */ 230 if (cfg_cg==-2) { 231 cg_start = 0; 232 cg_stop = sblock.fs_ncg; 233 } else if (cfg_cg == -1) { 234 cg_start = sblock.fs_ncg - 1; 235 cg_stop = sblock.fs_ncg; 236 } else if (cfg_cg < sblock.fs_ncg) { 237 cg_start = cfg_cg; 238 cg_stop = cfg_cg + 1; 239 } else { 240 cg_start = sblock.fs_ncg; 241 cg_stop = sblock.fs_ncg; 242 } 243 244 if (cfg_lv & 0x004) { 245 fscs = (struct csum *)calloc((size_t)1, 246 (size_t)sblock.fs_cssize); 247 if (fscs == NULL) 248 errx(1, "calloc failed"); 249 250 /* get the cylinder summary into the memory ... */ 251 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) { 252 if (bread(&disk, fsbtodb(&sblock, 253 sblock.fs_csaddr + numfrags(&sblock, i)), 254 (void *)(((char *)fscs)+i), 255 (size_t)(sblock.fs_cssize-i < sblock.fs_bsize ? 256 sblock.fs_cssize - i : sblock.fs_bsize)) == -1) 257 err(1, "bread: %s", disk.d_error); 258 } 259 260 dbg_csp = fscs; 261 /* ... and dump it */ 262 for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) { 263 snprintf(dbg_line, sizeof(dbg_line), 264 "%d. csum in fscs", dbg_csc); 265 DBG_DUMP_CSUM(&sblock, 266 dbg_line, 267 dbg_csp++); 268 } 269 } 270 271 if (cfg_lv & 0xf8) { 272 /* for each requested cylinder group ... */ 273 for (cylno = cg_start; cylno < cg_stop; cylno++) { 274 snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno); 275 if (cfg_lv & 0x002) { 276 /* dump the superblock copies */ 277 if (bread(&disk, fsbtodb(&sblock, 278 cgsblock(&sblock, cylno)), 279 (void *)&osblock, SBLOCKSIZE) == -1) 280 err(1, "bread: %s", disk.d_error); 281 DBG_DUMP_FS(&osblock, dbg_line); 282 } 283 284 /* 285 * Read the cylinder group and dump whatever was 286 * requested. 287 */ 288 if (bread(&disk, fsbtodb(&sblock, 289 cgtod(&sblock, cylno)), (void *)&acg, 290 (size_t)sblock.fs_cgsize) == -1) 291 err(1, "bread: %s", disk.d_error); 292 293 if (cfg_lv & 0x008) 294 DBG_DUMP_CG(&sblock, dbg_line, &acg); 295 if (cfg_lv & 0x010) 296 DBG_DUMP_INMAP(&sblock, dbg_line, &acg); 297 if (cfg_lv & 0x020) 298 DBG_DUMP_FRMAP(&sblock, dbg_line, &acg); 299 if (cfg_lv & 0x040) { 300 DBG_DUMP_CLMAP(&sblock, dbg_line, &acg); 301 DBG_DUMP_CLSUM(&sblock, dbg_line, &acg); 302 } 303 #ifdef NOT_CURRENTLY 304 /* 305 * See the comment in sbin/growfs/debug.c for why this 306 * is currently disabled, and what needs to be done to 307 * re-enable it. 308 */ 309 if (disk.d_ufs == 1 && cfg_lv & 0x080) 310 DBG_DUMP_SPTBL(&sblock, dbg_line, &acg); 311 #endif 312 } 313 } 314 315 if (cfg_lv & 0x300) { 316 /* Dump the requested inode(s) */ 317 if (cfg_in != -2) 318 DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv); 319 else { 320 for (in = cg_start * sblock.fs_ipg; 321 in < (ino_t)cg_stop * sblock.fs_ipg; 322 in++) 323 DUMP_WHOLE_INODE(in, cfg_lv); 324 } 325 } 326 327 DBG_CLOSE; 328 DBG_LEAVE; 329 330 return 0; 331 } 332 333 /* ********************************************** dump_whole_ufs1_inode ***** */ 334 /* 335 * Here we dump a list of all blocks allocated by this inode. We follow 336 * all indirect blocks. 337 */ 338 void 339 dump_whole_ufs1_inode(ino_t inode, int level) 340 { 341 DBG_FUNC("dump_whole_ufs1_inode") 342 struct ufs1_dinode *ino; 343 int rb, mode; 344 unsigned int ind2ctr, ind3ctr; 345 ufs1_daddr_t *ind2ptr, *ind3ptr; 346 char comment[80]; 347 348 DBG_ENTER; 349 350 /* 351 * Read the inode from disk/cache. 352 */ 353 if (getino(&disk, (void **)&ino, inode, &mode) == -1) 354 err(1, "getino: %s", disk.d_error); 355 356 if(ino->di_nlink==0) { 357 DBG_LEAVE; 358 return; /* inode not in use */ 359 } 360 361 /* 362 * Dump the main inode structure. 363 */ 364 snprintf(comment, sizeof(comment), "Inode 0x%08x", inode); 365 if (level & 0x100) { 366 DBG_DUMP_INO(&sblock, 367 comment, 368 ino); 369 } 370 371 if (!(level & 0x200)) { 372 DBG_LEAVE; 373 return; 374 } 375 376 /* 377 * Ok, now prepare for dumping all direct and indirect pointers. 378 */ 379 rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR; 380 if(rb>0) { 381 /* 382 * Dump single indirect block. 383 */ 384 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 385 (size_t)sblock.fs_bsize) == -1) { 386 err(1, "bread: %s", disk.d_error); 387 } 388 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", 389 inode); 390 DBG_DUMP_IBLK(&sblock, 391 comment, 392 i1blk, 393 (size_t)rb); 394 rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)); 395 } 396 if(rb>0) { 397 /* 398 * Dump double indirect blocks. 399 */ 400 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 401 (size_t)sblock.fs_bsize) == -1) { 402 err(1, "bread: %s", disk.d_error); 403 } 404 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", 405 inode); 406 DBG_DUMP_IBLK(&sblock, 407 comment, 408 i2blk, 409 howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))); 410 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize, 411 sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) { 412 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr]; 413 414 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 415 (size_t)sblock.fs_bsize) == -1) { 416 err(1, "bread: %s", disk.d_error); 417 } 418 snprintf(comment, sizeof(comment), 419 "Inode 0x%08x: indirect 1->%d", inode, ind2ctr); 420 DBG_DUMP_IBLK(&sblock, 421 comment, 422 i1blk, 423 (size_t)rb); 424 rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)); 425 } 426 } 427 if(rb>0) { 428 /* 429 * Dump triple indirect blocks. 430 */ 431 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 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 2", 436 inode); 437 #define SQUARE(a) ((a)*(a)) 438 DBG_DUMP_IBLK(&sblock, 439 comment, 440 i3blk, 441 howmany(rb, 442 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))))); 443 #undef SQUARE 444 for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize, 445 sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) { 446 ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr]; 447 448 if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk, 449 (size_t)sblock.fs_bsize) == -1) { 450 err(1, "bread: %s", disk.d_error); 451 } 452 snprintf(comment, sizeof(comment), 453 "Inode 0x%08x: indirect 2->%d", inode, ind3ctr); 454 DBG_DUMP_IBLK(&sblock, 455 comment, 456 i2blk, 457 howmany(rb, 458 howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))); 459 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize, 460 sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) { 461 ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk) 462 [ind2ctr]; 463 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), 464 (void *)&i1blk, (size_t)sblock.fs_bsize) 465 == -1) { 466 err(1, "bread: %s", disk.d_error); 467 } 468 snprintf(comment, sizeof(comment), 469 "Inode 0x%08x: indirect 2->%d->%d", inode, 470 ind3ctr, ind3ctr); 471 DBG_DUMP_IBLK(&sblock, 472 comment, 473 i1blk, 474 (size_t)rb); 475 rb-=howmany(sblock.fs_bsize, 476 sizeof(ufs1_daddr_t)); 477 } 478 } 479 } 480 481 DBG_LEAVE; 482 return; 483 } 484 485 /* ********************************************** dump_whole_ufs2_inode ***** */ 486 /* 487 * Here we dump a list of all blocks allocated by this inode. We follow 488 * all indirect blocks. 489 */ 490 void 491 dump_whole_ufs2_inode(ino_t inode, int level) 492 { 493 DBG_FUNC("dump_whole_ufs2_inode") 494 struct ufs2_dinode *ino; 495 int rb, mode; 496 unsigned int ind2ctr, ind3ctr; 497 ufs2_daddr_t *ind2ptr, *ind3ptr; 498 char comment[80]; 499 500 DBG_ENTER; 501 502 /* 503 * Read the inode from disk/cache. 504 */ 505 if (getino(&disk, (void **)&ino, inode, &mode) == -1) 506 err(1, "getino: %s", disk.d_error); 507 508 if (ino->di_nlink == 0) { 509 DBG_LEAVE; 510 return; /* inode not in use */ 511 } 512 513 /* 514 * Dump the main inode structure. 515 */ 516 snprintf(comment, sizeof(comment), "Inode 0x%08x", inode); 517 if (level & 0x100) { 518 DBG_DUMP_INO(&sblock, comment, ino); 519 } 520 521 if (!(level & 0x200)) { 522 DBG_LEAVE; 523 return; 524 } 525 526 /* 527 * Ok, now prepare for dumping all direct and indirect pointers. 528 */ 529 rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR; 530 if (rb > 0) { 531 /* 532 * Dump single indirect block. 533 */ 534 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk, 535 (size_t)sblock.fs_bsize) == -1) { 536 err(1, "bread: %s", disk.d_error); 537 } 538 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", inode); 539 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb); 540 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)); 541 } 542 if (rb > 0) { 543 /* 544 * Dump double indirect blocks. 545 */ 546 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk, 547 (size_t)sblock.fs_bsize) == -1) { 548 err(1, "bread: %s", disk.d_error); 549 } 550 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", inode); 551 DBG_DUMP_IBLK(&sblock, 552 comment, 553 i2blk, 554 howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))); 555 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize, 556 sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) { 557 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr]; 558 559 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 560 (size_t)sblock.fs_bsize) == -1) { 561 err(1, "bread: %s", disk.d_error); 562 } 563 snprintf(comment, sizeof(comment), 564 "Inode 0x%08x: indirect 1->%d", inode, ind2ctr); 565 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb); 566 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)); 567 } 568 } 569 if (rb > 0) { 570 /* 571 * Dump triple indirect blocks. 572 */ 573 if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk, 574 (size_t)sblock.fs_bsize) == -1) { 575 err(1, "bread: %s", disk.d_error); 576 } 577 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", 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), (void *)&i2blk, 590 (size_t)sblock.fs_bsize) == -1) { 591 err(1, "bread: %s", disk.d_error); 592 } 593 snprintf(comment, sizeof(comment), 594 "Inode 0x%08x: indirect 2->%d", inode, ind3ctr); 595 DBG_DUMP_IBLK(&sblock, 596 comment, 597 i2blk, 598 howmany(rb, 599 howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))); 600 for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize, 601 sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) { 602 ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr]; 603 if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk, 604 (size_t)sblock.fs_bsize) == -1) { 605 err(1, "bread: %s", disk.d_error); 606 } 607 snprintf(comment, sizeof(comment), 608 "Inode 0x%08x: indirect 2->%d->%d", inode, 609 ind3ctr, ind3ctr); 610 DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb); 611 rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)); 612 } 613 } 614 } 615 616 DBG_LEAVE; 617 return; 618 } 619 620 /* ************************************************************* usage ***** */ 621 /* 622 * Dump a line of usage. 623 */ 624 void 625 usage(void) 626 { 627 DBG_FUNC("usage") 628 629 DBG_ENTER; 630 631 fprintf(stderr, 632 "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] " 633 "[-o outfile]\n" 634 " special | file\n"); 635 636 DBG_LEAVE; 637 exit(1); 638 } 639