xref: /openbsd/sbin/ncheck_ffs/ncheck_ffs.c (revision fd84ef7e)
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