1 /* $OpenBSD: ncheck_ffs.c,v 1.33 2007/08/06 19:16:05 sobrado Exp $ */ 2 3 /*- 4 * Copyright (c) 1995, 1996 SigmaSoft, Th. Lockert <tholo@sigmasoft.com> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 /*- 29 * Copyright (c) 1980, 1988, 1991, 1993 30 * The Regents of the University of California. All rights reserved. 31 * 32 * Redistribution and use in source and binary forms, with or without 33 * modification, are permitted provided that the following conditions 34 * are met: 35 * 1. Redistributions of source code must retain the above copyright 36 * notice, this list of conditions and the following disclaimer. 37 * 2. Redistributions in binary form must reproduce the above copyright 38 * notice, this list of conditions and the following disclaimer in the 39 * documentation and/or other materials provided with the distribution. 40 * 3. Neither the name of the University nor the names of its contributors 41 * may be used to endorse or promote products derived from this software 42 * without specific prior written permission. 43 * 44 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 45 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 46 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 47 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 48 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 49 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 50 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 51 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 52 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 53 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 54 * SUCH DAMAGE. 55 */ 56 57 #ifndef lint 58 static const char rcsid[] = "$OpenBSD: ncheck_ffs.c,v 1.33 2007/08/06 19:16:05 sobrado Exp $"; 59 #endif /* not lint */ 60 61 #include <sys/param.h> 62 #include <sys/time.h> 63 #include <sys/stat.h> 64 #include <ufs/ffs/fs.h> 65 #include <ufs/ufs/dir.h> 66 #include <ufs/ufs/dinode.h> 67 68 #include <stdio.h> 69 #include <stdlib.h> 70 #include <fcntl.h> 71 #include <string.h> 72 #include <ctype.h> 73 #include <unistd.h> 74 #include <fstab.h> 75 #include <errno.h> 76 #include <err.h> 77 78 #define DIP(dp, field) \ 79 ((sblock->fs_magic == FS_UFS1_MAGIC) ? \ 80 ((struct ufs1_dinode *)(dp))->field : \ 81 ((struct ufs2_dinode *)(dp))->field) 82 83 char *disk; /* name of the disk file */ 84 char rdisk[PATH_MAX];/* resolved name of the disk file */ 85 int diskfd; /* disk file descriptor */ 86 struct fs *sblock; /* the file system super block */ 87 char sblock_buf[MAXBSIZE]; 88 int sblock_try[] = SBLOCKSEARCH; /* possible superblock locations */ 89 long dev_bsize; /* block size of underlying disk device */ 90 int dev_bshift; /* log2(dev_bsize) */ 91 ino_t *ilist; /* list of inodes to check */ 92 int ninodes; /* number of inodes in list */ 93 int sflag; /* only suid and special files */ 94 int aflag; /* print the . and .. entries too */ 95 int mflag; /* verbose output */ 96 int iflag; /* specific inode */ 97 char *format; /* output format */ 98 99 struct icache_s { 100 ino_t ino; 101 union { 102 struct ufs1_dinode dp1; 103 struct ufs2_dinode dp2; 104 } di; 105 } *icache; 106 int nicache; 107 108 void addinode(ino_t inum); 109 void *getino(ino_t inum); 110 void findinodes(ino_t); 111 void bread(daddr64_t, char *, int); 112 __dead void usage(void); 113 void scanonedir(ino_t, const char *); 114 void dirindir(ino_t, daddr64_t, int, off_t, const char *); 115 void searchdir(ino_t, daddr64_t, long, off_t, const char *); 116 int matchino(const void *, const void *); 117 int matchcache(const void *, const void *); 118 void cacheino(ino_t, void *); 119 void *cached(ino_t); 120 int main(int, char *[]); 121 char *rawname(char *); 122 void format_entry(const char *, struct direct *); 123 124 /* 125 * Check to see if the indicated inodes are the same 126 */ 127 int 128 matchino(const void *key, const void *val) 129 { 130 ino_t k = *(ino_t *)key; 131 ino_t v = *(ino_t *)val; 132 133 if (k < v) 134 return -1; 135 else if (k > v) 136 return 1; 137 return 0; 138 } 139 140 /* 141 * Check if the indicated inode match the entry in the cache 142 */ 143 int 144 matchcache(const void *key, const void *val) 145 { 146 ino_t ino = *(ino_t *)key; 147 struct icache_s *ic = (struct icache_s *)val; 148 149 if (ino < ic->ino) 150 return -1; 151 else if (ino > ic->ino) 152 return 1; 153 return 0; 154 } 155 156 /* 157 * Add an inode to the cached entries 158 */ 159 void 160 cacheino(ino_t ino, void *dp) 161 { 162 struct icache_s *newicache; 163 164 newicache = realloc(icache, (nicache + 1) * sizeof(struct icache_s)); 165 if (newicache == NULL) { 166 if (icache) 167 free(icache); 168 icache = NULL; 169 errx(1, "malloc"); 170 } 171 icache = newicache; 172 icache[nicache].ino = ino; 173 if (sblock->fs_magic == FS_UFS1_MAGIC) 174 icache[nicache++].di.dp1 = *(struct ufs1_dinode *)dp; 175 else 176 icache[nicache++].di.dp2 = *(struct ufs2_dinode *)dp; 177 } 178 179 /* 180 * Get a cached inode 181 */ 182 void * 183 cached(ino_t ino) 184 { 185 struct icache_s *ic; 186 void *dp = NULL; 187 188 ic = (struct icache_s *)bsearch(&ino, icache, nicache, 189 sizeof(struct icache_s), matchcache); 190 if (ic != NULL) { 191 if (sblock->fs_magic == FS_UFS1_MAGIC) 192 dp = &ic->di.dp1; 193 else 194 dp = &ic->di.dp2; 195 } 196 return (dp); 197 } 198 199 /* 200 * Walk the inode list for a filesystem to find all allocated inodes 201 * Remember inodes we want to give information about and cache all 202 * inodes pointing to directories 203 */ 204 void 205 findinodes(ino_t maxino) 206 { 207 ino_t ino; 208 void *dp; 209 mode_t mode; 210 211 for (ino = ROOTINO; ino < maxino; ino++) { 212 dp = getino(ino); 213 mode = DIP(dp, di_mode) & IFMT; 214 if (!mode) 215 continue; 216 if (mode == IFDIR) 217 cacheino(ino, dp); 218 if (iflag || 219 (sflag && (mode == IFDIR || 220 ((DIP(dp, di_mode) & (ISGID | ISUID)) == 0 && 221 (mode == IFREG || mode == IFLNK))))) 222 continue; 223 addinode(ino); 224 } 225 } 226 227 /* 228 * Get a specified inode from disk. Attempt to minimize reads to once 229 * per cylinder group 230 */ 231 void * 232 getino(ino_t inum) 233 { 234 static char *itab = NULL; 235 static daddr64_t iblk = -1; 236 void *dp; 237 size_t dsize; 238 239 if (inum < ROOTINO || inum >= sblock->fs_ncg * sblock->fs_ipg) 240 return NULL; 241 if ((dp = cached(inum)) != NULL) 242 return dp; 243 if (sblock->fs_magic == FS_UFS1_MAGIC) 244 dsize = sizeof(struct ufs1_dinode); 245 else 246 dsize = sizeof(struct ufs2_dinode); 247 if ((inum / sblock->fs_ipg) != iblk || itab == NULL) { 248 iblk = inum / sblock->fs_ipg; 249 if (itab == NULL && 250 (itab = calloc(sblock->fs_ipg, dsize)) == NULL) 251 errx(1, "no memory for inodes"); 252 bread(fsbtodb(sblock, cgimin(sblock, iblk)), itab, 253 sblock->fs_ipg * dsize); 254 } 255 return itab + (inum % sblock->fs_ipg) * dsize; 256 } 257 258 /* 259 * Read a chunk of data from the disk. 260 * Try to recover from hard errors by reading in sector sized pieces. 261 * Error recovery is attempted at most BREADEMAX times before seeking 262 * consent from the operator to continue. 263 */ 264 int breaderrors = 0; 265 #define BREADEMAX 32 266 267 void 268 bread(daddr64_t blkno, char *buf, int size) 269 { 270 int cnt, i; 271 272 loop: 273 if (lseek(diskfd, ((off_t)blkno << dev_bshift), SEEK_SET) < 0) 274 warnx("bread: lseek fails"); 275 if ((cnt = read(diskfd, buf, size)) == size) 276 return; 277 if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_ffs1_size)) { 278 /* 279 * Trying to read the final fragment. 280 * 281 * NB - dump only works in TP_BSIZE blocks, hence 282 * rounds `dev_bsize' fragments up to TP_BSIZE pieces. 283 * It should be smarter about not actually trying to 284 * read more than it can get, but for the time being 285 * we punt and scale back the read only when it gets 286 * us into trouble. (mkm 9/25/83) 287 */ 288 size -= dev_bsize; 289 goto loop; 290 } 291 if (cnt == -1) 292 warnx("read error from %s: %s: [block %lld]: count=%d", 293 disk, strerror(errno), (long long)blkno, size); 294 else 295 warnx("short read error from %s: [block %lld]: count=%d, got=%d", 296 disk, (long long)blkno, size, cnt); 297 if (++breaderrors > BREADEMAX) 298 errx(1, "More than %d block read errors from %s", BREADEMAX, disk); 299 /* 300 * Zero buffer, then try to read each sector of buffer separately. 301 */ 302 memset(buf, 0, size); 303 for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) { 304 if (lseek(diskfd, ((off_t)blkno << dev_bshift), SEEK_SET) < 0) 305 warnx("bread: lseek2 fails!"); 306 if ((cnt = read(diskfd, buf, (int)dev_bsize)) == dev_bsize) 307 continue; 308 if (cnt == -1) { 309 warnx("read error from %s: %s: [sector %lld]: count=%ld", 310 disk, strerror(errno), (long long)blkno, dev_bsize); 311 continue; 312 } 313 warnx("short read error from %s: [sector %lld]: count=%ld, got=%d", 314 disk, (long long)blkno, dev_bsize, cnt); 315 } 316 } 317 318 /* 319 * Add an inode to the in-memory list of inodes to dump 320 */ 321 void 322 addinode(ino_t ino) 323 { 324 ino_t *newilist; 325 326 newilist = realloc(ilist, sizeof(ino_t) * (ninodes + 1)); 327 if (newilist == NULL) { 328 if (ilist) 329 free(ilist); 330 ilist = NULL; 331 errx(4, "not enough memory to allocate tables"); 332 } 333 ilist = newilist; 334 ilist[ninodes] = ino; 335 ninodes++; 336 } 337 338 /* 339 * Scan the directory pointer at by ino 340 */ 341 void 342 scanonedir(ino_t ino, const char *path) 343 { 344 void *dp; 345 off_t filesize; 346 int i; 347 348 if ((dp = cached(ino)) == NULL) 349 return; 350 filesize = (off_t)DIP(dp, di_size); 351 for (i = 0; filesize > 0 && i < NDADDR; i++) { 352 if (DIP(dp, di_db[i]) != 0) { 353 searchdir(ino, DIP(dp, di_db[i]), 354 sblksize(sblock, DIP(dp, di_size), i), 355 filesize, path); 356 } 357 filesize -= sblock->fs_bsize; 358 } 359 for (i = 0; filesize > 0 && i < NIADDR; i++) { 360 if (DIP(dp, di_ib[i])) 361 dirindir(ino, DIP(dp, di_ib[i]), i, filesize, path); 362 } 363 } 364 365 /* 366 * Read indirect blocks, and pass the data blocks to be searched 367 * as directories. Quit as soon as any entry is found that will 368 * require the directory to be dumped. 369 */ 370 void 371 dirindir(ino_t ino, daddr64_t blkno, int ind_level, off_t filesize, 372 const char *path) 373 { 374 int i; 375 static void *idblk; 376 377 if (idblk == NULL && (idblk = malloc(sblock->fs_bsize)) == NULL) 378 errx(1, "dirindir: cannot allocate indirect memory.\n"); 379 bread(fsbtodb(sblock, blkno), idblk, (int)sblock->fs_bsize); 380 if (ind_level <= 0) { 381 for (i = 0; filesize > 0 && i < NINDIR(sblock); i++) { 382 if (sblock->fs_magic == FS_UFS1_MAGIC) 383 blkno = ((int32_t *)idblk)[i]; 384 else 385 blkno = ((int64_t *)idblk)[i]; 386 if (blkno != 0) 387 searchdir(ino, blkno, sblock->fs_bsize, 388 filesize, path); 389 } 390 return; 391 } 392 ind_level--; 393 for (i = 0; filesize > 0 && i < NINDIR(sblock); i++) { 394 if (sblock->fs_magic == FS_UFS1_MAGIC) 395 blkno = ((int32_t *)idblk)[i]; 396 else 397 blkno = ((int64_t *)idblk)[i]; 398 if (blkno != 0) 399 dirindir(ino, blkno, ind_level, filesize, path); 400 } 401 } 402 403 /* 404 * Scan a disk block containing directory information looking to see if 405 * any of the entries are on the dump list and to see if the directory 406 * contains any subdirectories. 407 */ 408 void 409 searchdir(ino_t ino, daddr64_t blkno, long size, off_t filesize, 410 const char *path) 411 { 412 char *dblk; 413 struct direct *dp; 414 void *di; 415 mode_t mode; 416 char *npath; 417 long loc; 418 419 if ((dblk = malloc(sblock->fs_bsize)) == NULL) 420 errx(1, "searchdir: cannot allocate indirect memory."); 421 bread(fsbtodb(sblock, blkno), dblk, (int)size); 422 if (filesize < size) 423 size = filesize; 424 for (loc = 0; loc < size; ) { 425 dp = (struct direct *)(dblk + loc); 426 if (dp->d_reclen == 0) { 427 warnx("corrupted directory, inode %u", ino); 428 break; 429 } 430 loc += dp->d_reclen; 431 if (dp->d_ino == 0) 432 continue; 433 if (dp->d_name[0] == '.') { 434 if (!aflag && (dp->d_name[1] == '\0' || 435 (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) 436 continue; 437 } 438 di = getino(dp->d_ino); 439 mode = DIP(di, di_mode) & IFMT; 440 if (bsearch(&dp->d_ino, ilist, ninodes, sizeof(*ilist), matchino)) { 441 if (format) { 442 format_entry(path, dp); 443 } else { 444 if (mflag) 445 printf("mode %-6o uid %-5u gid %-5u ino ", 446 DIP(di, di_mode), DIP(di, di_uid), 447 DIP(di, di_gid)); 448 printf("%-7u %s/%s%s\n", dp->d_ino, path, 449 dp->d_name, mode == IFDIR ? "/." : ""); 450 } 451 } 452 if (mode == IFDIR) { 453 if (dp->d_name[0] == '.') { 454 if (dp->d_name[1] == '\0' || 455 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')) 456 continue; 457 } 458 if (asprintf(&npath, "%s/%s", path, dp->d_name) == -1) 459 errx(1, "malloc"); 460 scanonedir(dp->d_ino, npath); 461 free(npath); 462 } 463 } 464 } 465 466 char * 467 rawname(char *name) 468 { 469 static char newname[MAXPATHLEN]; 470 char *p; 471 472 if ((p = strrchr(name, '/')) == NULL) 473 return name; 474 *p = '\0'; 475 strlcpy(newname, name, sizeof newname - 2); 476 *p++ = '/'; 477 strlcat(newname, "/r", sizeof newname); 478 strlcat(newname, p, sizeof newname); 479 return(newname); 480 } 481 482 __dead void 483 usage(void) 484 { 485 extern char *__progname; 486 487 fprintf(stderr, 488 "usage: %s [-ams] [-f format] [-i number ...] filesystem\n", 489 __progname); 490 exit(3); 491 } 492 493 int 494 main(int argc, char *argv[]) 495 { 496 struct stat stblock; 497 struct fstab *fsp; 498 unsigned long ulval; 499 ssize_t n; 500 char *ep, *odisk; 501 int c, i; 502 503 while ((c = getopt(argc, argv, "af:i:ms")) != -1) 504 switch (c) { 505 case 'a': 506 aflag++; 507 break; 508 case 'i': 509 iflag++; 510 511 errno = 0; 512 ulval = strtoul(optarg, &ep, 10); 513 if (optarg[0] == '\0' || *ep != '\0') 514 errx(1, "%s is not a number", 515 optarg); 516 if (errno == ERANGE && ulval == ULONG_MAX) 517 errx(1, "%s is out or range", 518 optarg); 519 addinode((ino_t)ulval); 520 521 while (optind < argc) { 522 errno = 0; 523 ulval = strtoul(argv[optind], &ep, 10); 524 if (argv[optind][0] == '\0' || *ep != '\0') 525 break; 526 if (errno == ERANGE && ulval == ULONG_MAX) 527 errx(1, "%s is out or range", 528 argv[optind]); 529 addinode((ino_t)ulval); 530 optind++; 531 } 532 break; 533 case 'f': 534 format = optarg; 535 break; 536 case 'm': 537 mflag++; 538 break; 539 case 's': 540 sflag++; 541 break; 542 default: 543 usage(); 544 exit(2); 545 } 546 if (optind != argc - 1 || (mflag && format)) 547 usage(); 548 549 odisk = argv[optind]; 550 if (realpath(odisk, rdisk) == NULL) 551 err(1, "cannot find real path for %s", odisk); 552 disk = rdisk; 553 554 if (stat(disk, &stblock) < 0) 555 err(1, "cannot stat %s", disk); 556 557 if (S_ISBLK(stblock.st_mode)) { 558 disk = rawname(disk); 559 } else if (!S_ISCHR(stblock.st_mode)) { 560 if ((fsp = getfsfile(disk)) == NULL) 561 err(1, "could not find file system %s", disk); 562 disk = rawname(fsp->fs_spec); 563 } 564 565 if ((diskfd = open(disk, O_RDONLY)) < 0) 566 err(1, "cannot open %s", disk); 567 sblock = (struct fs *)sblock_buf; 568 for (i = 0; sblock_try[i] != -1; i++) { 569 n = pread(diskfd, sblock, SBLOCKSIZE, (off_t)sblock_try[i]); 570 if (n == SBLOCKSIZE && (sblock->fs_magic == FS_UFS1_MAGIC || 571 (sblock->fs_magic == FS_UFS2_MAGIC && 572 sblock->fs_sblockloc == sblock_try[i])) && 573 sblock->fs_bsize <= MAXBSIZE && 574 sblock->fs_bsize >= sizeof(struct fs)) 575 break; 576 } 577 if (sblock_try[i] == -1) 578 errx(1, "cannot find filesystem superblock"); 579 580 dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1); 581 dev_bshift = ffs(dev_bsize) - 1; 582 if (dev_bsize != (1 << dev_bshift)) 583 errx(2, "blocksize (%ld) not a power of 2", dev_bsize); 584 findinodes(sblock->fs_ipg * sblock->fs_ncg); 585 if (!format) 586 printf("%s:\n", disk); 587 scanonedir(ROOTINO, ""); 588 close(diskfd); 589 exit (0); 590 } 591 592 void 593 format_entry(const char *path, struct direct *dp) 594 { 595 static size_t size; 596 static char *buf; 597 size_t len, nsize; 598 char *src, *dst, *newbuf; 599 600 if (buf == NULL) { 601 if ((buf = malloc(LINE_MAX)) == NULL) 602 err(1, "malloc"); 603 size = LINE_MAX; 604 } 605 606 for (src = format, dst = buf; *src; src++) { 607 /* Need room for at least one character in buf. */ 608 if (size <= dst - buf) { 609 expand_buf: 610 nsize = size << 1; 611 612 if ((newbuf = realloc(buf, nsize)) == NULL) 613 err(1, "realloc"); 614 buf = newbuf; 615 size = nsize; 616 } 617 if (src[0] =='\\') { 618 switch (src[1]) { 619 case 'I': 620 len = snprintf(dst, size - (dst - buf), "%u", 621 dp->d_ino); 622 if (len == -1 || len >= size - (dst - buf)) 623 goto expand_buf; 624 dst += len; 625 break; 626 case 'P': 627 len = snprintf(dst, size - (dst - buf), "%s/%s", 628 path, dp->d_name); 629 if (len == -1 || len >= size - (dst - buf)) 630 goto expand_buf; 631 dst += len; 632 break; 633 case '\\': 634 *dst++ = '\\'; 635 break; 636 case '0': 637 /* XXX - support other octal numbers? */ 638 *dst++ = '\0'; 639 break; 640 case 'a': 641 *dst++ = '\a'; 642 break; 643 case 'b': 644 *dst++ = '\b'; 645 break; 646 case 'e': 647 *dst++ = '\e'; 648 break; 649 case 'f': 650 *dst++ = '\f'; 651 break; 652 case 'n': 653 *dst++ = '\n'; 654 break; 655 case 'r': 656 *dst++ = '\r'; 657 break; 658 case 't': 659 *dst++ = '\t'; 660 break; 661 case 'v': 662 *dst++ = '\v'; 663 break; 664 default: 665 *dst++ = src[1]; 666 break; 667 } 668 src++; 669 } else 670 *dst++ = *src; 671 } 672 fwrite(buf, dst - buf, 1, stdout); 673 } 674