1 /* $OpenBSD: ncheck_ffs.c,v 1.6 2001/07/07 18:26:16 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.6 2001/07/07 18:26:16 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 register ino_t ino; 162 register 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), filesize, path); 305 filesize -= sblock->fs_bsize; 306 } 307 for (i = 0; filesize > 0 && i < NIADDR; i++) { 308 if (dp->di_ib[i]) 309 dirindir(ino, dp->di_ib[i], i, &filesize, path); 310 } 311 } 312 313 /* 314 * Read indirect blocks, and pass the data blocks to be searched 315 * as directories. Quit as soon as any entry is found that will 316 * require the directory to be dumped. 317 */ 318 void 319 dirindir(ino, blkno, ind_level, filesize, path) 320 ino_t ino; 321 daddr_t blkno; 322 int ind_level; 323 long *filesize; 324 const char *path; 325 { 326 daddr_t idblk[MAXBSIZE / sizeof(daddr_t)]; 327 int i; 328 329 bread(fsbtodb(sblock, blkno), (char *)idblk, (int)sblock->fs_bsize); 330 if (ind_level <= 0) { 331 for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { 332 blkno = idblk[i]; 333 if (blkno) 334 searchdir(ino, blkno, sblock->fs_bsize, *filesize, path); 335 } 336 return; 337 } 338 ind_level--; 339 for (i = 0; *filesize > 0 && NINDIR(sblock); i++) { 340 blkno = idblk[i]; 341 if (blkno) 342 dirindir(ino, blkno, ind_level, filesize, path); 343 } 344 } 345 346 /* 347 * Scan a disk block containing directory information looking to see if 348 * any of the entries are on the inode list and to see if the directory 349 * contains any subdirectories. Display entries for marked inodes. 350 * Pass inodes pointing to directories back to scanonedir(). 351 */ 352 void 353 searchdir(ino, blkno, size, filesize, path) 354 ino_t ino; 355 daddr_t blkno; 356 long size; 357 long filesize; 358 const char *path; 359 { 360 char dblk[MAXBSIZE]; 361 struct direct *dp; 362 struct dinode *di; 363 mode_t mode; 364 char *npath; 365 long loc; 366 367 bread(fsbtodb(sblock, blkno), dblk, (int)size); 368 if (filesize < size) 369 size = filesize; 370 for (loc = 0; loc < size;) { 371 dp = (struct direct *)(dblk + loc); 372 if (dp->d_reclen == 0) { 373 warnx("corrupted directory, inode %lu", (long)ino); 374 break; 375 } 376 loc += dp->d_reclen; 377 if (!dp->d_ino) 378 continue; 379 if (dp->d_name[0] == '.') { 380 if (!aflag && (dp->d_name[1] == '\0' || 381 (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) 382 continue; 383 } 384 di = getino(dp->d_ino); 385 mode = di->di_mode & IFMT; 386 if (bsearch(&dp->d_ino, ilist, ninodes, sizeof(*ilist), matchino)) { 387 if (mflag) 388 printf("mode %-6o uid %-5lu gid %-5lu ino ", 389 di->di_mode, (unsigned long)di->di_uid, 390 (unsigned long)di->di_gid); 391 printf("%-7lu %s/%s%s\n", (unsigned long)dp->d_ino, 392 path, dp->d_name, mode == IFDIR ? "/." : ""); 393 } 394 if (mode == IFDIR) { 395 if (dp->d_name[0] == '.') { 396 if (dp->d_name[1] == '\0' || 397 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')) 398 continue; 399 } 400 npath = malloc(strlen(path) + strlen(dp->d_name) + 2); 401 strcpy(npath, path); 402 strcat(npath, "/"); 403 strcat(npath, dp->d_name); 404 scanonedir(dp->d_ino, npath); 405 free(npath); 406 } 407 } 408 } 409 410 char * 411 rawname(name) 412 char *name; 413 { 414 static char newname[MAXPATHLEN]; 415 char *p; 416 417 if ((p = strrchr(name, '/')) == NULL) 418 return name; 419 *p = '\0'; 420 strcpy(newname, name); 421 *p++ = '/'; 422 strcat(newname, "/r"); 423 strcat(newname, p); 424 return(newname); 425 } 426 427 void 428 usage() 429 { 430 fprintf(stderr, "Usage: ncheck_ffs [-i numbers] [-ams] filesystem\n"); 431 exit(3); 432 } 433 434 int 435 main(argc, argv) 436 int argc; 437 char *argv[]; 438 { 439 struct stat stblock; 440 struct fstab *fsp; 441 int c; 442 ino_t ino; 443 444 while ((c = getopt(argc, argv, "ai:ms")) != -1) 445 switch (c) { 446 case 'a': 447 aflag++; 448 break; 449 case 'i': 450 iflag++; 451 addinode(strtoul(optarg, NULL, 10)); 452 while (optind < argc && (ino = strtoul(argv[optind], NULL, 10)) != 0) { 453 addinode(ino); 454 optind++; 455 } 456 break; 457 case 'm': 458 mflag++; 459 break; 460 case 's': 461 sflag++; 462 break; 463 case '?': 464 exit(2); 465 } 466 if (optind != argc - 1) 467 usage(); 468 469 disk = argv[optind]; 470 471 if (stat(disk, &stblock) < 0) 472 err(1, "cannot stat %s", disk); 473 474 if (S_ISBLK(stblock.st_mode)) { 475 disk = rawname(disk); 476 } 477 else if (!S_ISCHR(stblock.st_mode)) { 478 if ((fsp = getfsfile(disk)) == NULL) 479 err(1, "cound not find file system %s", disk); 480 disk = rawname(fsp->fs_spec); 481 } 482 483 if ((diskfd = open(disk, O_RDONLY)) < 0) 484 err(1, "cannot open %s", disk); 485 sblock = (struct fs *)sblock_buf; 486 bread(SBOFF, (char *)sblock, SBSIZE); 487 if (sblock->fs_magic != FS_MAGIC) 488 errx(1, "not a file system"); 489 dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1); 490 dev_bshift = ffs(dev_bsize) - 1; 491 if (dev_bsize != (1 << dev_bshift)) 492 errx(2, "blocksize (%ld) not a power of 2", dev_bsize); 493 findinodes(sblock->fs_ipg * sblock->fs_ncg); 494 printf("%s:\n", disk); 495 scanonedir(ROOTINO, ""); 496 close(diskfd); 497 return 0; 498 } 499