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