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