1 /* $OpenBSD: ncheck_ffs.c,v 1.8 2001/12/01 19:05:39 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 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by SigmaSoft, Th. Lockert 18 * 4. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 22 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 23 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 24 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 30 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #ifndef lint 34 static char rcsid[] = "$OpenBSD: ncheck_ffs.c,v 1.8 2001/12/01 19:05:39 deraadt Exp $"; 35 #endif /* not lint */ 36 37 #include <sys/param.h> 38 #include <sys/time.h> 39 #include <sys/stat.h> 40 #include <ufs/ffs/fs.h> 41 #include <ufs/ufs/dir.h> 42 #include <ufs/ufs/dinode.h> 43 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <fcntl.h> 47 #include <string.h> 48 #include <ctype.h> 49 #include <unistd.h> 50 #include <fstab.h> 51 #include <errno.h> 52 #include <err.h> 53 54 #define MAXINOPB (MAXBSIZE / sizeof(struct dinode)) 55 56 char *disk; /* name of the disk file */ 57 int diskfd; /* disk file descriptor */ 58 struct fs *sblock; /* the file system super block */ 59 char sblock_buf[MAXBSIZE]; 60 long dev_bsize; /* block size of underlying disk device */ 61 int dev_bshift; /* log2(dev_bsize) */ 62 ino_t *ilist; /* list of inodes to check */ 63 int ninodes; /* number of inodes in list */ 64 int sflag; /* only suid and special files */ 65 int aflag; /* print the . and .. entries too */ 66 int mflag; /* verbose output */ 67 int iflag; /* specific inode */ 68 69 struct icache_s { 70 ino_t ino; 71 struct dinode di; 72 } *icache; 73 int nicache; 74 75 void addinode __P((ino_t inum)); 76 struct dinode *getino __P((ino_t inum)); 77 void findinodes __P((ino_t)); 78 void bread __P((daddr_t, char *, int)); 79 void usage __P((void)); 80 void scanonedir __P((ino_t, const char *)); 81 void dirindir __P((ino_t, daddr_t, int, long *, const char *)); 82 void searchdir __P((ino_t, daddr_t, long, long, const char *)); 83 int matchino __P((const void *, const void *)); 84 int matchcache __P((const void *, const void *)); 85 void cacheino __P((ino_t, struct dinode *)); 86 struct dinode *cached __P((ino_t)); 87 int main __P((int, char *[])); 88 char *rawname __P((char *)); 89 90 /* 91 * Check to see if the indicated inodes are the same 92 */ 93 int 94 matchino(key, val) 95 const void *key, *val; 96 { 97 ino_t k = *(ino_t *)key; 98 ino_t v = *(ino_t *)val; 99 100 if (k < v) 101 return -1; 102 else if (k > v) 103 return 1; 104 return 0; 105 } 106 107 /* 108 * Check if the indicated inode match the entry in the cache 109 */ 110 int matchcache(key, val) 111 const void *key, *val; 112 { 113 ino_t ino = *(ino_t *)key; 114 struct icache_s *ic = (struct icache_s *)val; 115 116 if (ino < ic->ino) 117 return -1; 118 else if (ino > ic->ino) 119 return 1; 120 return 0; 121 } 122 123 /* 124 * Add an inode to the cached entries 125 */ 126 void 127 cacheino(ino, ip) 128 ino_t ino; 129 struct dinode *ip; 130 { 131 if (nicache) 132 icache = realloc(icache, (nicache + 1) * sizeof(struct icache_s)); 133 else 134 icache = malloc(sizeof(struct icache_s)); 135 icache[nicache].ino = ino; 136 icache[nicache++].di = *ip; 137 } 138 139 /* 140 * Get a cached inode 141 */ 142 struct dinode * 143 cached(ino) 144 ino_t ino; 145 { 146 struct icache_s *ic; 147 148 ic = (struct icache_s *)bsearch(&ino, icache, nicache, sizeof(struct icache_s), matchcache); 149 return ic ? &ic->di : NULL; 150 } 151 152 /* 153 * Walk the inode list for a filesystem to find all allocated inodes 154 * Remember inodes we want to give information about and cache all 155 * inodes pointing to directories 156 */ 157 void 158 findinodes(maxino) 159 ino_t maxino; 160 { 161 ino_t ino; 162 struct dinode *dp; 163 mode_t mode; 164 165 for (ino = ROOTINO; ino < maxino; ino++) { 166 dp = getino(ino); 167 mode = dp->di_mode & IFMT; 168 if (!mode) 169 continue; 170 if (mode == IFDIR) 171 cacheino(ino, dp); 172 if (iflag || 173 (sflag && 174 (((dp->di_mode & (ISGID | ISUID)) == 0) && 175 ((mode == IFREG) || (mode == IFDIR) || (mode == IFLNK))))) 176 continue; 177 addinode(ino); 178 } 179 } 180 181 /* 182 * Get a specified inode from disk. Attempt to minimize reads to once 183 * per cylinder group 184 */ 185 struct dinode * 186 getino(inum) 187 ino_t inum; 188 { 189 static struct dinode *itab = NULL; 190 static daddr_t iblk = -1; 191 struct dinode *ip; 192 193 if (inum < ROOTINO || inum >= sblock->fs_ncg * sblock->fs_ipg) 194 return NULL; 195 if ((ip = cached(inum)) != NULL) 196 return ip; 197 if ((inum / sblock->fs_ipg) != iblk || itab == NULL) { 198 iblk = inum / sblock->fs_ipg; 199 if (itab == NULL && 200 (itab = calloc(sizeof(struct dinode), sblock->fs_ipg)) == NULL) 201 errx(1, "no memory for inodes"); 202 bread(fsbtodb(sblock, cgimin(sblock, iblk)), (char *)itab, 203 sblock->fs_ipg * sizeof(struct dinode)); 204 } 205 return &itab[inum % sblock->fs_ipg]; 206 } 207 208 /* 209 * Read a chunk of data from the disk. Try to recover from hard errors by 210 * reading in sector sized pieces. Error recovery is attempted at most 211 * BREADEMAX times before seeking consent from the operator to continue. 212 */ 213 int breaderrors = 0; 214 #define BREADEMAX 32 215 216 void 217 bread(blkno, buf, size) 218 daddr_t blkno; 219 char *buf; 220 int size; 221 { 222 int cnt, i; 223 224 loop: 225 if (lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0) 226 warnx("bread: lseek fails\n"); 227 if ((cnt = read(diskfd, buf, size)) == size) 228 return; 229 if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_size)) { 230 /* 231 * Trying to read the final fragment. 232 * 233 * NB - dump only works in TP_BSIZE blocks, hence 234 * rounds `dev_bsize' fragments up to TP_BSIZE pieces. 235 * It should be smarter about not actually trying to 236 * read more than it can get, but for the time being 237 * we punt and scale back the read only when it gets 238 * us into trouble. (mkm 9/25/83) 239 */ 240 size -= dev_bsize; 241 goto loop; 242 } 243 if (cnt == -1) 244 warnx("read error from %s: %s: [block %d]: count=%d\n", 245 disk, strerror(errno), blkno, size); 246 else 247 warnx("short read error from %s: [block %d]: count=%d, got=%d\n", 248 disk, blkno, size, cnt); 249 if (++breaderrors > BREADEMAX) 250 errx(1, "More than %d block read errors from %s\n", BREADEMAX, disk); 251 /* 252 * Zero buffer, then try to read each sector of buffer separately. 253 */ 254 memset(buf, 0, size); 255 for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) { 256 if (lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0) 257 warnx("bread: lseek2 fails!\n"); 258 if ((cnt = read(diskfd, buf, (int)dev_bsize)) == dev_bsize) 259 continue; 260 if (cnt == -1) { 261 warnx("read error from %s: %s: [sector %d]: count=%ld\n", 262 disk, strerror(errno), blkno, dev_bsize); 263 continue; 264 } 265 warnx("short read error from %s: [sector %d]: count=%ld, got=%d\n", 266 disk, blkno, dev_bsize, cnt); 267 } 268 } 269 270 /* 271 * Add an inode to the in-memory list of inodes to dump 272 */ 273 void 274 addinode(ino) 275 ino_t ino; 276 { 277 if (ninodes) 278 ilist = realloc(ilist, sizeof(ino_t) * (ninodes + 1)); 279 else 280 ilist = malloc(sizeof(ino_t)); 281 if (ilist == NULL) 282 errx(4, "not enough memory to allocate tables"); 283 ilist[ninodes] = ino; 284 ninodes++; 285 } 286 287 /* 288 * Scan the directory pointer at by ino 289 */ 290 void 291 scanonedir(ino, path) 292 ino_t ino; 293 const char *path; 294 { 295 struct dinode *dp; 296 long filesize; 297 int i; 298 299 if ((dp = cached(ino)) == NULL) 300 return; 301 filesize = dp->di_size; 302 for (i = 0; filesize > 0 && i < NDADDR; i++) { 303 if (dp->di_db[i]) 304 searchdir(ino, dp->di_db[i], dblksize(sblock, dp, i), 305 filesize, path); 306 filesize -= sblock->fs_bsize; 307 } 308 for (i = 0; filesize > 0 && i < NIADDR; i++) { 309 if (dp->di_ib[i]) 310 dirindir(ino, dp->di_ib[i], i, &filesize, path); 311 } 312 } 313 314 /* 315 * Read indirect blocks, and pass the data blocks to be searched 316 * as directories. Quit as soon as any entry is found that will 317 * require the directory to be dumped. 318 */ 319 void 320 dirindir(ino, blkno, ind_level, filesize, path) 321 ino_t ino; 322 daddr_t blkno; 323 int ind_level; 324 long *filesize; 325 const char *path; 326 { 327 daddr_t idblk[MAXBSIZE / sizeof(daddr_t)]; 328 int i; 329 330 bread(fsbtodb(sblock, blkno), (char *)idblk, (int)sblock->fs_bsize); 331 if (ind_level <= 0) { 332 for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { 333 blkno = idblk[i]; 334 if (blkno) 335 searchdir(ino, blkno, sblock->fs_bsize, 336 *filesize, path); 337 } 338 return; 339 } 340 ind_level--; 341 for (i = 0; *filesize > 0 && NINDIR(sblock); i++) { 342 blkno = idblk[i]; 343 if (blkno) 344 dirindir(ino, blkno, ind_level, filesize, path); 345 } 346 } 347 348 /* 349 * Scan a disk block containing directory information looking to see if 350 * any of the entries are on the inode list and to see if the directory 351 * contains any subdirectories. Display entries for marked inodes. 352 * Pass inodes pointing to directories back to scanonedir(). 353 */ 354 void 355 searchdir(ino, blkno, size, filesize, path) 356 ino_t ino; 357 daddr_t blkno; 358 long size; 359 long filesize; 360 const char *path; 361 { 362 char dblk[MAXBSIZE]; 363 struct direct *dp; 364 struct dinode *di; 365 mode_t mode; 366 char *npath; 367 long loc; 368 369 bread(fsbtodb(sblock, blkno), dblk, (int)size); 370 if (filesize < size) 371 size = filesize; 372 for (loc = 0; loc < size;) { 373 dp = (struct direct *)(dblk + loc); 374 if (dp->d_reclen == 0) { 375 warnx("corrupted directory, inode %lu", (long)ino); 376 break; 377 } 378 loc += dp->d_reclen; 379 if (!dp->d_ino) 380 continue; 381 if (dp->d_name[0] == '.') { 382 if (!aflag && (dp->d_name[1] == '\0' || 383 (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) 384 continue; 385 } 386 di = getino(dp->d_ino); 387 mode = di->di_mode & IFMT; 388 if (bsearch(&dp->d_ino, ilist, ninodes, sizeof(*ilist), matchino)) { 389 if (mflag) 390 printf("mode %-6o uid %-5lu gid %-5lu ino ", 391 di->di_mode, (unsigned long)di->di_uid, 392 (unsigned long)di->di_gid); 393 printf("%-7lu %s/%s%s\n", (unsigned long)dp->d_ino, 394 path, dp->d_name, mode == IFDIR ? "/." : ""); 395 } 396 if (mode == IFDIR) { 397 if (dp->d_name[0] == '.') { 398 if (dp->d_name[1] == '\0' || 399 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')) 400 continue; 401 } 402 npath = malloc(strlen(path) + strlen(dp->d_name) + 2); 403 strcpy(npath, path); 404 strcat(npath, "/"); 405 strcat(npath, dp->d_name); 406 scanonedir(dp->d_ino, npath); 407 free(npath); 408 } 409 } 410 } 411 412 char * 413 rawname(name) 414 char *name; 415 { 416 static char newname[MAXPATHLEN]; 417 char *p; 418 419 if ((p = strrchr(name, '/')) == NULL) 420 return name; 421 *p = '\0'; 422 strlcpy(newname, name, sizeof newname - 2); 423 *p++ = '/'; 424 strlcat(newname, "/r", sizeof newname); 425 strlcat(newname, p, sizeof newname); 426 return(newname); 427 } 428 429 void 430 usage() 431 { 432 fprintf(stderr, "Usage: ncheck_ffs [-i numbers] [-ams] filesystem\n"); 433 exit(3); 434 } 435 436 int 437 main(argc, argv) 438 int argc; 439 char *argv[]; 440 { 441 struct stat stblock; 442 struct fstab *fsp; 443 u_long ulval; 444 char *ep; 445 int c; 446 447 while ((c = getopt(argc, argv, "ai:ms")) != -1) 448 switch (c) { 449 case 'a': 450 aflag++; 451 break; 452 case 'i': 453 iflag++; 454 455 errno = 0; 456 ulval = strtoul(optarg, &ep, 10); 457 if (optarg[0] == '\0' || *ep != '\0') 458 errx(1, "%s is not a number", 459 optarg); 460 if (errno == ERANGE && ulval == ULONG_MAX) 461 errx(1, "%s is out or range", 462 optarg); 463 addinode((ino_t)ulval); 464 465 while (optind < argc) { 466 errno = 0; 467 ulval = strtoul(argv[optind], &ep, 10); 468 if (argv[optind][0] == '\0' || *ep != '\0') 469 break; 470 if (errno == ERANGE && ulval == ULONG_MAX) 471 errx(1, "%s is out or range", 472 argv[optind]); 473 addinode((ino_t)ulval); 474 optind++; 475 } 476 break; 477 case 'm': 478 mflag++; 479 break; 480 case 's': 481 sflag++; 482 break; 483 default: 484 usage(); 485 exit(2); 486 } 487 if (optind != argc - 1) 488 usage(); 489 490 disk = argv[optind]; 491 492 if (stat(disk, &stblock) < 0) 493 err(1, "cannot stat %s", disk); 494 495 if (S_ISBLK(stblock.st_mode)) { 496 disk = rawname(disk); 497 } 498 else if (!S_ISCHR(stblock.st_mode)) { 499 if ((fsp = getfsfile(disk)) == NULL) 500 err(1, "cound not find file system %s", disk); 501 disk = rawname(fsp->fs_spec); 502 } 503 504 if ((diskfd = open(disk, O_RDONLY)) < 0) 505 err(1, "cannot open %s", disk); 506 sblock = (struct fs *)sblock_buf; 507 bread(SBOFF, (char *)sblock, SBSIZE); 508 if (sblock->fs_magic != FS_MAGIC) 509 errx(1, "not a file system"); 510 dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1); 511 dev_bshift = ffs(dev_bsize) - 1; 512 if (dev_bsize != (1 << dev_bshift)) 513 errx(2, "blocksize (%ld) not a power of 2", dev_bsize); 514 findinodes(sblock->fs_ipg * sblock->fs_ncg); 515 printf("%s:\n", disk); 516 scanonedir(ROOTINO, ""); 517 close(diskfd); 518 return 0; 519 } 520