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/stat.h> 58 59 #include <stdio.h> 60 #include <paths.h> 61 #include <ctype.h> 62 #include <err.h> 63 #include <fcntl.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 union { 76 struct fs fs; 77 char pad[SBSIZE]; 78 } fsun1, fsun2; 79 #define sblock fsun1.fs 80 #define osblock fsun2.fs 81 82 static union { 83 struct cg cg; 84 char pad[MAXBSIZE]; 85 } cgun1; 86 #define acg cgun1.cg 87 88 static char ablk[MAXBSIZE]; 89 static char i1blk[MAXBSIZE]; 90 static char i2blk[MAXBSIZE]; 91 static char i3blk[MAXBSIZE]; 92 93 static struct csum *fscs; 94 95 /* ******************************************************** PROTOTYPES ***** */ 96 static void rdfs(daddr_t, size_t, void *, int); 97 static void usage(void); 98 static struct disklabel *get_disklabel(int); 99 static struct dinode *ginode(ino_t, int); 100 static void dump_whole_inode(ino_t, int, int); 101 102 /* ************************************************************** rdfs ***** */ 103 /* 104 * Here we read some block(s) from disk. 105 */ 106 void 107 rdfs(daddr_t bno, size_t size, void *bf, int fsi) 108 { 109 DBG_FUNC("rdfs") 110 ssize_t n; 111 112 DBG_ENTER; 113 114 if (lseek(fsi, (off_t)bno * DEV_BSIZE, 0) < 0) { 115 err(33, "rdfs: seek error: %ld", (long)bno); 116 } 117 n = read(fsi, bf, size); 118 if (n != (ssize_t)size) { 119 err(34, "rdfs: read error: %ld", (long)bno); 120 } 121 122 DBG_LEAVE; 123 return; 124 } 125 126 /* ************************************************************** main ***** */ 127 /* 128 * ffsinfo(8) is a tool to dump all metadata of a filesystem. It helps to find 129 * errors is the filesystem much easier. You can run ffsinfo before and after 130 * an fsck(8), and compare the two ascii dumps easy with diff, and you see 131 * directly where the problem is. You can control how much detail you want to 132 * see with some command line arguments. You can also easy check the status 133 * of a filesystem, like is there is enough space for growing a filesystem, 134 * or how many active snapshots do we have. It provides much more detailed 135 * information then dumpfs. Snapshots, as they are very new, are not really 136 * supported. They are just mentioned currently, but it is planned to run 137 * also over active snapshots, to even get that output. 138 */ 139 int 140 main(int argc, char **argv) 141 { 142 DBG_FUNC("main") 143 char *device, *special, *cp; 144 char ch; 145 size_t len; 146 struct stat st; 147 struct disklabel *lp; 148 struct partition *pp; 149 int fsi; 150 struct csum *dbg_csp; 151 int dbg_csc; 152 char dbg_line[80]; 153 int cylno,i; 154 int cfg_cg, cfg_in, cfg_lv; 155 int cg_start, cg_stop; 156 ino_t in; 157 char *out_file; 158 int Lflag=0; 159 160 DBG_ENTER; 161 162 cfg_lv=0xff; 163 cfg_in=-2; 164 cfg_cg=-2; 165 out_file=strdup("/var/tmp/ffsinfo"); 166 if(out_file == NULL) { 167 errx(1, "strdup failed"); 168 } 169 170 while ((ch=getopt(argc, argv, "Lg:i:l:o:")) != -1) { 171 switch(ch) { 172 case 'L': 173 Lflag=1; 174 break; 175 case 'g': 176 cfg_cg=atol(optarg); 177 if(cfg_cg < -1) { 178 usage(); 179 } 180 break; 181 case 'i': 182 cfg_in=atol(optarg); 183 if(cfg_in < 0) { 184 usage(); 185 } 186 break; 187 case 'l': 188 cfg_lv=atol(optarg); 189 if(cfg_lv < 0x1||cfg_lv > 0x3ff) { 190 usage(); 191 } 192 break; 193 case 'o': 194 free(out_file); 195 out_file=strdup(optarg); 196 if(out_file == NULL) { 197 errx(1, "strdup failed"); 198 } 199 break; 200 case '?': 201 /* FALLTHROUGH */ 202 default: 203 usage(); 204 } 205 } 206 argc -= optind; 207 argv += optind; 208 209 if(argc != 1) { 210 usage(); 211 } 212 device=*argv; 213 214 /* 215 * Now we try to guess the (raw)device name. 216 */ 217 if (0 == strrchr(device, '/') && (stat(device, &st) == -1)) { 218 /* 219 * No path prefix was given, so try in that order: 220 * /dev/r%s 221 * /dev/%s 222 * /dev/vinum/r%s 223 * /dev/vinum/%s. 224 * 225 * FreeBSD now doesn't distinguish between raw and block 226 * devices any longer, but it should still work this way. 227 */ 228 len=strlen(device)+strlen(_PATH_DEV)+2+strlen("vinum/"); 229 special=(char *)malloc(len); 230 if(special == NULL) { 231 errx(1, "malloc failed"); 232 } 233 snprintf(special, len, "%sr%s", _PATH_DEV, device); 234 if (stat(special, &st) == -1) { 235 snprintf(special, len, "%s%s", _PATH_DEV, device); 236 if (stat(special, &st) == -1) { 237 snprintf(special, len, "%svinum/r%s", 238 _PATH_DEV, device); 239 if (stat(special, &st) == -1) { 240 /* 241 * For now this is the 'last resort'. 242 */ 243 snprintf(special, len, "%svinum/%s", 244 _PATH_DEV, device); 245 } 246 } 247 } 248 device = special; 249 } 250 251 /* 252 * Open our device for reading. 253 */ 254 fsi = open(device, O_RDONLY); 255 if (fsi < 0) { 256 err(1, "%s", device); 257 } 258 259 stat(device, &st); 260 261 if(S_ISREG(st.st_mode)) { /* label check not supported for files */ 262 Lflag=1; 263 } 264 265 if(!Lflag) { 266 /* 267 * Try to read a label and gess the slice if not specified. 268 * This code should guess the right thing and avaid to bother 269 * the user user with the task of specifying the option -v on 270 * vinum volumes. 271 */ 272 cp=device+strlen(device)-1; 273 lp = get_disklabel(fsi); 274 if(lp->d_type == DTYPE_VINUM) { 275 pp = &lp->d_partitions[0]; 276 } else if (isdigit(*cp)) { 277 pp = &lp->d_partitions[2]; 278 } else if (*cp>='a' && *cp<='h') { 279 pp = &lp->d_partitions[*cp - 'a']; 280 } else { 281 errx(1, "unknown device"); 282 } 283 284 /* 285 * Check if that partition looks suited for dumping. 286 */ 287 if (pp->p_size < 1) { 288 errx(1, "partition is unavailable"); 289 } 290 if (pp->p_fstype != FS_BSDFFS) { 291 errx(1, "partition not 4.2BSD"); 292 } 293 } 294 295 /* 296 * Read the current superblock. 297 */ 298 rdfs((daddr_t)(SBOFF/DEV_BSIZE), (size_t)SBSIZE, (void *)&sblock, fsi); 299 if (sblock.fs_magic != FS_MAGIC) { 300 errx(1, "superblock not recognized"); 301 } 302 303 DBG_OPEN(out_file); /* already here we need a superblock */ 304 305 if(cfg_lv & 0x001) { 306 DBG_DUMP_FS(&sblock, 307 "primary sblock"); 308 } 309 310 /* 311 * Determine here what cylinder groups to dump. 312 */ 313 if(cfg_cg==-2) { 314 cg_start=0; 315 cg_stop=sblock.fs_ncg; 316 } else if (cfg_cg==-1) { 317 cg_start=sblock.fs_ncg-1; 318 cg_stop=sblock.fs_ncg; 319 } else if (cfg_cg<sblock.fs_ncg) { 320 cg_start=cfg_cg; 321 cg_stop=cfg_cg+1; 322 } else { 323 cg_start=sblock.fs_ncg; 324 cg_stop=sblock.fs_ncg; 325 } 326 327 if (cfg_lv & 0x004) { 328 fscs = (struct csum *)calloc((size_t)1, 329 (size_t)sblock.fs_cssize); 330 if(fscs == NULL) { 331 errx(1, "calloc failed"); 332 } 333 334 /* 335 * Get the cylinder summary into the memory ... 336 */ 337 for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) { 338 rdfs(fsbtodb(&sblock, sblock.fs_csaddr + 339 numfrags(&sblock, i)), (size_t)(sblock.fs_cssize-i< 340 sblock.fs_bsize ? sblock.fs_cssize - i : 341 sblock.fs_bsize), (void *)(((char *)fscs)+i), fsi); 342 } 343 344 dbg_csp=fscs; 345 /* 346 * ... and dump it. 347 */ 348 for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) { 349 snprintf(dbg_line, sizeof(dbg_line), 350 "%d. csum in fscs", dbg_csc); 351 DBG_DUMP_CSUM(&sblock, 352 dbg_line, 353 dbg_csp++); 354 } 355 } 356 357 /* 358 * For each requested cylinder group ... 359 */ 360 for(cylno=cg_start; cylno<cg_stop; cylno++) { 361 snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno); 362 if(cfg_lv & 0x002) { 363 /* 364 * ... dump the superblock copies ... 365 */ 366 rdfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)), 367 (size_t)SBSIZE, (void *)&osblock, fsi); 368 DBG_DUMP_FS(&osblock, 369 dbg_line); 370 } 371 /* 372 * ... read the cylinder group and dump whatever was requested. 373 */ 374 rdfs(fsbtodb(&sblock, cgtod(&sblock, cylno)), 375 (size_t)sblock.fs_cgsize, (void *)&acg, fsi); 376 if(cfg_lv & 0x008) { 377 DBG_DUMP_CG(&sblock, 378 dbg_line, 379 &acg); 380 } 381 if(cfg_lv & 0x010) { 382 DBG_DUMP_INMAP(&sblock, 383 dbg_line, 384 &acg); 385 } 386 if(cfg_lv & 0x020) { 387 DBG_DUMP_FRMAP(&sblock, 388 dbg_line, 389 &acg); 390 } 391 if(cfg_lv & 0x040) { 392 DBG_DUMP_CLMAP(&sblock, 393 dbg_line, 394 &acg); 395 DBG_DUMP_CLSUM(&sblock, 396 dbg_line, 397 &acg); 398 } 399 if(cfg_lv & 0x080) { 400 DBG_DUMP_SPTBL(&sblock, 401 dbg_line, 402 &acg); 403 } 404 } 405 /* 406 * Dump the requested inode(s). 407 */ 408 if(cfg_in != -2) { 409 dump_whole_inode((ino_t)cfg_in, fsi, cfg_lv); 410 } else { 411 for(in=cg_start*sblock.fs_ipg; in<(ino_t)cg_stop*sblock.fs_ipg; 412 in++) { 413 dump_whole_inode(in, fsi, cfg_lv); 414 } 415 } 416 417 DBG_CLOSE; 418 419 close(fsi); 420 421 DBG_LEAVE; 422 return 0; 423 } 424 425 /* ************************************************** dump_whole_inode ***** */ 426 /* 427 * Here we dump a list of all blocks allocated by this inode. We follow 428 * all indirect blocks. 429 */ 430 void 431 dump_whole_inode(ino_t inode, int fsi, int level) 432 { 433 DBG_FUNC("dump_whole_inode") 434 struct dinode *ino; 435 int rb; 436 unsigned int ind2ctr, ind3ctr; 437 ufs_daddr_t *ind2ptr, *ind3ptr; 438 char comment[80]; 439 440 DBG_ENTER; 441 442 /* 443 * Read the inode from disk/cache. 444 */ 445 ino=ginode(inode, fsi); 446 447 if(ino->di_nlink==0) { 448 DBG_LEAVE; 449 return; /* inode not in use */ 450 } 451 452 /* 453 * Dump the main inode structure. 454 */ 455 snprintf(comment, sizeof(comment), "Inode 0x%08x", inode); 456 if (level & 0x100) { 457 DBG_DUMP_INO(&sblock, 458 comment, 459 ino); 460 } 461 462 if (!(level & 0x200)) { 463 DBG_LEAVE; 464 return; 465 } 466 467 /* 468 * Ok, now prepare for dumping all direct and indirect pointers. 469 */ 470 rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR; 471 if(rb>0) { 472 /* 473 * Dump single indirect block. 474 */ 475 rdfs(fsbtodb(&sblock, ino->di_ib[0]), (size_t)sblock.fs_bsize, 476 (void *)&i1blk, fsi); 477 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", 478 inode); 479 DBG_DUMP_IBLK(&sblock, 480 comment, 481 i1blk, 482 (size_t)rb); 483 rb-=howmany(sblock.fs_bsize, sizeof(ufs_daddr_t)); 484 } 485 if(rb>0) { 486 /* 487 * Dump double indirect blocks. 488 */ 489 rdfs(fsbtodb(&sblock, ino->di_ib[1]), (size_t)sblock.fs_bsize, 490 (void *)&i2blk, fsi); 491 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", 492 inode); 493 DBG_DUMP_IBLK(&sblock, 494 comment, 495 i2blk, 496 howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs_daddr_t)))); 497 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize, 498 sizeof(ufs_daddr_t)))&&(rb>0)); ind2ctr++) { 499 ind2ptr=&((ufs_daddr_t *)(void *)&i2blk)[ind2ctr]; 500 501 rdfs(fsbtodb(&sblock, *ind2ptr), 502 (size_t)sblock.fs_bsize, (void *)&i1blk, fsi); 503 snprintf(comment, sizeof(comment), 504 "Inode 0x%08x: indirect 1->%d", inode, ind2ctr); 505 DBG_DUMP_IBLK(&sblock, 506 comment, 507 i1blk, 508 (size_t)rb); 509 rb-=howmany(sblock.fs_bsize, sizeof(ufs_daddr_t)); 510 } 511 } 512 if(rb>0) { 513 /* 514 * Dump triple indirect blocks. 515 */ 516 rdfs(fsbtodb(&sblock, ino->di_ib[2]), (size_t)sblock.fs_bsize, 517 (void *)&i3blk, fsi); 518 snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", 519 inode); 520 #define SQUARE(a) ((a)*(a)) 521 DBG_DUMP_IBLK(&sblock, 522 comment, 523 i3blk, 524 howmany(rb, 525 SQUARE(howmany(sblock.fs_bsize, sizeof(ufs_daddr_t))))); 526 #undef SQUARE 527 for(ind3ctr=0; ((ind3ctr < howmany(sblock.fs_bsize, 528 sizeof(ufs_daddr_t)))&&(rb>0)); ind3ctr ++) { 529 ind3ptr=&((ufs_daddr_t *)(void *)&i3blk)[ind3ctr]; 530 531 rdfs(fsbtodb(&sblock, *ind3ptr), 532 (size_t)sblock.fs_bsize, (void *)&i2blk, fsi); 533 snprintf(comment, sizeof(comment), 534 "Inode 0x%08x: indirect 2->%d", inode, ind3ctr); 535 DBG_DUMP_IBLK(&sblock, 536 comment, 537 i2blk, 538 howmany(rb, 539 howmany(sblock.fs_bsize, sizeof(ufs_daddr_t)))); 540 for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize, 541 sizeof(ufs_daddr_t)))&&(rb>0)); ind2ctr ++) { 542 ind2ptr=&((ufs_daddr_t *)(void *)&i2blk) 543 [ind2ctr]; 544 rdfs(fsbtodb(&sblock, *ind2ptr), 545 (size_t)sblock.fs_bsize, (void *)&i1blk, 546 fsi); 547 snprintf(comment, sizeof(comment), 548 "Inode 0x%08x: indirect 2->%d->%d", inode, 549 ind3ctr, ind3ctr); 550 DBG_DUMP_IBLK(&sblock, 551 comment, 552 i1blk, 553 (size_t)rb); 554 rb-=howmany(sblock.fs_bsize, 555 sizeof(ufs_daddr_t)); 556 } 557 } 558 } 559 560 DBG_LEAVE; 561 return; 562 } 563 564 /* ***************************************************** get_disklabel ***** */ 565 /* 566 * Read the disklabel from disk. 567 */ 568 struct disklabel * 569 get_disklabel(int fd) 570 { 571 DBG_FUNC("get_disklabel") 572 static struct disklabel *lab; 573 574 DBG_ENTER; 575 576 lab=(struct disklabel *)malloc(sizeof(struct disklabel)); 577 if (!lab) { 578 errx(1, "malloc failed"); 579 } 580 if (ioctl(fd, DIOCGDINFO, (char *)lab) < 0) { 581 errx(1, "DIOCGDINFO failed"); 582 exit(-1); 583 } 584 585 DBG_LEAVE; 586 return (lab); 587 } 588 589 590 /* ************************************************************* usage ***** */ 591 /* 592 * Dump a line of usage. 593 */ 594 void 595 usage(void) 596 { 597 DBG_FUNC("usage") 598 599 DBG_ENTER; 600 601 fprintf(stderr, 602 "usage: ffsinfo [-L] [-g cylgrp] [-i inode] [-l level] " 603 "[-o outfile]\n" 604 " special | file\n"); 605 606 DBG_LEAVE; 607 exit(1); 608 } 609 610 /* ************************************************************ ginode ***** */ 611 /* 612 * This function provides access to an individual inode. We find out in which 613 * block the requested inode is located, read it from disk if needed, and 614 * return the pointer into that block. We maintain a cache of one block to 615 * not read the same block again and again if we iterate linearly over all 616 * inodes. 617 */ 618 struct dinode * 619 ginode(ino_t inumber, int fsi) 620 { 621 DBG_FUNC("ginode") 622 ufs_daddr_t iblk; 623 static ino_t startinum=0; /* first inode in cached block */ 624 struct dinode *pi; 625 626 DBG_ENTER; 627 628 pi=(struct dinode *)(void *)ablk; 629 if (startinum == 0 || inumber < startinum || 630 inumber >= startinum + INOPB(&sblock)) { 631 /* 632 * The block needed is not cached, so we have to read it from 633 * disk now. 634 */ 635 iblk = ino_to_fsba(&sblock, inumber); 636 rdfs(fsbtodb(&sblock, iblk), (size_t)sblock.fs_bsize, 637 (void *)&ablk, fsi); 638 startinum = (inumber / INOPB(&sblock)) * INOPB(&sblock); 639 } 640 641 DBG_LEAVE; 642 return (&(pi[inumber % INOPB(&sblock)])); 643 } 644 645