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