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