dirs.c revision 100207
1/*
2 * Copyright (c) 1983, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 * (c) UNIX System Laboratories, Inc.
5 * All or some portions of this file are derived from material licensed
6 * to the University of California by American Telephone and Telegraph
7 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
8 * the permission of UNIX System Laboratories, Inc.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *	This product includes software developed by the University of
21 *	California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 *    may be used to endorse or promote products derived from this software
24 *    without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39#ifndef lint
40#if 0
41static char sccsid[] = "@(#)dirs.c	8.7 (Berkeley) 5/1/95";
42#endif
43static const char rcsid[] =
44  "$FreeBSD: head/sbin/restore/dirs.c 100207 2002-07-17 02:03:19Z mckusick $";
45#endif /* not lint */
46
47#include <sys/param.h>
48#include <sys/file.h>
49#include <sys/stat.h>
50#include <sys/time.h>
51
52#include <ufs/ufs/dinode.h>
53#include <ufs/ufs/dir.h>
54#include <protocols/dumprestore.h>
55
56#include <err.h>
57#include <errno.h>
58#include <paths.h>
59#include <stdio.h>
60#include <stdlib.h>
61#include <string.h>
62#include <unistd.h>
63
64#include "restore.h"
65#include "extern.h"
66
67/*
68 * Symbol table of directories read from tape.
69 */
70#define HASHSIZE	1000
71#define INOHASH(val) (val % HASHSIZE)
72struct inotab {
73	struct	inotab *t_next;
74	ino_t	t_ino;
75	int32_t	t_seekpt;
76	int32_t	t_size;
77};
78static struct inotab *inotab[HASHSIZE];
79
80/*
81 * Information retained about directories.
82 */
83struct modeinfo {
84	ino_t ino;
85	struct timeval ctimep[2];
86	struct timeval mtimep[2];
87	mode_t mode;
88	uid_t uid;
89	gid_t gid;
90	int flags;
91};
92
93/*
94 * Definitions for library routines operating on directories.
95 */
96#undef DIRBLKSIZ
97#define DIRBLKSIZ 1024
98struct rstdirdesc {
99	int	dd_fd;
100	int32_t	dd_loc;
101	int32_t	dd_size;
102	char	dd_buf[DIRBLKSIZ];
103};
104
105/*
106 * Global variables for this file.
107 */
108static long	seekpt;
109static FILE	*df, *mf;
110static RST_DIR	*dirp;
111static char	dirfile[MAXPATHLEN] = "#";	/* No file */
112static char	modefile[MAXPATHLEN] = "#";	/* No file */
113static char	dot[2] = ".";			/* So it can be modified */
114
115/*
116 * Format of old style directories.
117 */
118#define ODIRSIZ 14
119struct odirect {
120	u_short	d_ino;
121	char	d_name[ODIRSIZ];
122};
123
124static struct inotab	*allocinotab(struct context *, long);
125static void		 dcvt(struct odirect *, struct direct *);
126static void		 flushent(void);
127static struct inotab	*inotablookup(ino_t);
128static RST_DIR		*opendirfile(const char *);
129static void		 putdir(char *, long);
130static void		 putent(struct direct *);
131static void		 rst_seekdir(RST_DIR *, long, long);
132static long		 rst_telldir(RST_DIR *);
133static struct direct	*searchdir(ino_t, char *);
134
135/*
136 *	Extract directory contents, building up a directory structure
137 *	on disk for extraction by name.
138 *	If genmode is requested, save mode, owner, and times for all
139 *	directories on the tape.
140 */
141void
142extractdirs(int genmode)
143{
144	struct inotab *itp;
145	struct direct nulldir;
146	int i, fd;
147	const char *tmpdir;
148
149	vprintf(stdout, "Extract directories from tape\n");
150	if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
151		tmpdir = _PATH_TMP;
152	(void) sprintf(dirfile, "%s/rstdir%d", tmpdir, dumpdate);
153	if (command != 'r' && command != 'R') {
154		(void *) strcat(dirfile, "-XXXXXX");
155		fd = mkstemp(dirfile);
156	} else
157		fd = open(dirfile, O_RDWR|O_CREAT|O_EXCL, 0666);
158	if (fd == -1 || (df = fdopen(fd, "w")) == NULL) {
159		if (fd != -1)
160			close(fd);
161		warn("%s - cannot create directory temporary\nfopen", dirfile);
162		done(1);
163	}
164	if (genmode != 0) {
165		(void) sprintf(modefile, "%s/rstmode%d", tmpdir, dumpdate);
166		if (command != 'r' && command != 'R') {
167			(void *) strcat(modefile, "-XXXXXX");
168			fd = mkstemp(modefile);
169		} else
170			fd = open(modefile, O_RDWR|O_CREAT|O_EXCL, 0666);
171		if (fd == -1 || (mf = fdopen(fd, "w")) == NULL) {
172			if (fd != -1)
173				close(fd);
174			warn("%s - cannot create modefile\nfopen", modefile);
175			done(1);
176		}
177	}
178	nulldir.d_ino = 0;
179	nulldir.d_type = DT_DIR;
180	nulldir.d_namlen = 1;
181	(void) strcpy(nulldir.d_name, "/");
182	nulldir.d_reclen = DIRSIZ(0, &nulldir);
183	for (;;) {
184		curfile.name = "<directory file - name unknown>";
185		curfile.action = USING;
186		if (curfile.mode == 0 || (curfile.mode & IFMT) != IFDIR) {
187			(void) fclose(df);
188			dirp = opendirfile(dirfile);
189			if (dirp == NULL)
190				fprintf(stderr, "opendirfile: %s\n",
191				    strerror(errno));
192			if (mf != NULL)
193				(void) fclose(mf);
194			i = dirlookup(dot);
195			if (i == 0)
196				panic("Root directory is not on tape\n");
197			return;
198		}
199		itp = allocinotab(&curfile, seekpt);
200		getfile(putdir, xtrnull);
201		putent(&nulldir);
202		flushent();
203		itp->t_size = seekpt - itp->t_seekpt;
204	}
205}
206
207/*
208 * skip over all the directories on the tape
209 */
210void
211skipdirs(void)
212{
213
214	while (curfile.ino && (curfile.mode & IFMT) == IFDIR) {
215		skipfile();
216	}
217}
218
219/*
220 *	Recursively find names and inumbers of all files in subtree
221 *	pname and pass them off to be processed.
222 */
223void
224treescan(char *pname, ino_t ino, long (*todo)(char *, ino_t, int))
225{
226	struct inotab *itp;
227	struct direct *dp;
228	int namelen;
229	long bpt;
230	char locname[MAXPATHLEN + 1];
231
232	itp = inotablookup(ino);
233	if (itp == NULL) {
234		/*
235		 * Pname is name of a simple file or an unchanged directory.
236		 */
237		(void) (*todo)(pname, ino, LEAF);
238		return;
239	}
240	/*
241	 * Pname is a dumped directory name.
242	 */
243	if ((*todo)(pname, ino, NODE) == FAIL)
244		return;
245	/*
246	 * begin search through the directory
247	 * skipping over "." and ".."
248	 */
249	(void) strncpy(locname, pname, sizeof(locname) - 1);
250	locname[sizeof(locname) - 1] = '\0';
251	(void) strncat(locname, "/", sizeof(locname) - strlen(locname));
252	namelen = strlen(locname);
253	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
254	dp = rst_readdir(dirp); /* "." */
255	if (dp != NULL && strcmp(dp->d_name, ".") == 0)
256		dp = rst_readdir(dirp); /* ".." */
257	else
258		fprintf(stderr, "Warning: `.' missing from directory %s\n",
259			pname);
260	if (dp != NULL && strcmp(dp->d_name, "..") == 0)
261		dp = rst_readdir(dirp); /* first real entry */
262	else
263		fprintf(stderr, "Warning: `..' missing from directory %s\n",
264			pname);
265	bpt = rst_telldir(dirp);
266	/*
267	 * a zero inode signals end of directory
268	 */
269	while (dp != NULL) {
270		locname[namelen] = '\0';
271		if (namelen + dp->d_namlen >= sizeof(locname)) {
272			fprintf(stderr, "%s%s: name exceeds %d char\n",
273				locname, dp->d_name, sizeof(locname) - 1);
274		} else {
275			(void) strncat(locname, dp->d_name, (int)dp->d_namlen);
276			treescan(locname, dp->d_ino, todo);
277			rst_seekdir(dirp, bpt, itp->t_seekpt);
278		}
279		dp = rst_readdir(dirp);
280		bpt = rst_telldir(dirp);
281	}
282}
283
284/*
285 * Lookup a pathname which is always assumed to start from the ROOTINO.
286 */
287struct direct *
288pathsearch(const char *pathname)
289{
290	ino_t ino;
291	struct direct *dp;
292	char *path, *name, buffer[MAXPATHLEN];
293
294	strcpy(buffer, pathname);
295	path = buffer;
296	ino = ROOTINO;
297	while (*path == '/')
298		path++;
299	dp = NULL;
300	while ((name = strsep(&path, "/")) != NULL && *name != '\0') {
301		if ((dp = searchdir(ino, name)) == NULL)
302			return (NULL);
303		ino = dp->d_ino;
304	}
305	return (dp);
306}
307
308/*
309 * Lookup the requested name in directory inum.
310 * Return its inode number if found, zero if it does not exist.
311 */
312static struct direct *
313searchdir(ino_t	inum, char *name)
314{
315	struct direct *dp;
316	struct inotab *itp;
317	int len;
318
319	itp = inotablookup(inum);
320	if (itp == NULL)
321		return (NULL);
322	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
323	len = strlen(name);
324	do {
325		dp = rst_readdir(dirp);
326		if (dp == NULL)
327			return (NULL);
328	} while (dp->d_namlen != len || strncmp(dp->d_name, name, len) != 0);
329	return (dp);
330}
331
332/*
333 * Put the directory entries in the directory file
334 */
335static void
336putdir(char *buf, long size)
337{
338	struct direct cvtbuf;
339	struct odirect *odp;
340	struct odirect *eodp;
341	struct direct *dp;
342	long loc, i;
343
344	for (loc = 0; loc < size; ) {
345		dp = (struct direct *)(buf + loc);
346		if (Bcvt)
347			swabst((u_char *)"ls", (u_char *) dp);
348		i = DIRBLKSIZ - (loc & (DIRBLKSIZ - 1));
349		if ((dp->d_reclen & 0x3) != 0 ||
350		    dp->d_reclen > i ||
351		    dp->d_reclen < DIRSIZ(0, dp) ||
352		    dp->d_namlen > NAME_MAX) {
353			vprintf(stdout, "Mangled directory: ");
354			if ((dp->d_reclen & 0x3) != 0)
355				vprintf(stdout,
356				   "reclen not multiple of 4 ");
357			if (dp->d_reclen < DIRSIZ(0, dp))
358				vprintf(stdout,
359				   "reclen less than DIRSIZ (%d < %d) ",
360				   dp->d_reclen, DIRSIZ(0, dp));
361			if (dp->d_namlen > NAME_MAX)
362				vprintf(stdout,
363				   "reclen name too big (%d > %d) ",
364				   dp->d_namlen, NAME_MAX);
365			vprintf(stdout, "\n");
366			loc += i;
367			continue;
368		}
369		loc += dp->d_reclen;
370		if (dp->d_ino != 0) {
371			putent(dp);
372		}
373	}
374}
375
376/*
377 * These variables are "local" to the following two functions.
378 */
379char dirbuf[DIRBLKSIZ];
380long dirloc = 0;
381long prev = 0;
382
383/*
384 * add a new directory entry to a file.
385 */
386static void
387putent(struct direct *dp)
388{
389	dp->d_reclen = DIRSIZ(0, dp);
390	if (dirloc + dp->d_reclen > DIRBLKSIZ) {
391		((struct direct *)(dirbuf + prev))->d_reclen =
392		    DIRBLKSIZ - prev;
393		(void) fwrite(dirbuf, 1, DIRBLKSIZ, df);
394		dirloc = 0;
395	}
396	memmove(dirbuf + dirloc, dp, (long)dp->d_reclen);
397	prev = dirloc;
398	dirloc += dp->d_reclen;
399}
400
401/*
402 * flush out a directory that is finished.
403 */
404static void
405flushent(void)
406{
407	((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev;
408	(void) fwrite(dirbuf, (int)dirloc, 1, df);
409	seekpt = ftell(df);
410	dirloc = 0;
411}
412
413static void
414dcvt(struct odirect *odp, struct direct *ndp)
415{
416
417	memset(ndp, 0, (long)(sizeof *ndp));
418	ndp->d_ino =  odp->d_ino;
419	ndp->d_type = DT_UNKNOWN;
420	(void) strncpy(ndp->d_name, odp->d_name, ODIRSIZ);
421	ndp->d_namlen = strlen(ndp->d_name);
422	ndp->d_reclen = DIRSIZ(0, ndp);
423}
424
425/*
426 * Seek to an entry in a directory.
427 * Only values returned by rst_telldir should be passed to rst_seekdir.
428 * This routine handles many directories in a single file.
429 * It takes the base of the directory in the file, plus
430 * the desired seek offset into it.
431 */
432static void
433rst_seekdir(RST_DIR *dirp, long loc, long base)
434{
435
436	if (loc == rst_telldir(dirp))
437		return;
438	loc -= base;
439	if (loc < 0)
440		fprintf(stderr, "bad seek pointer to rst_seekdir %ld\n", loc);
441	(void) lseek(dirp->dd_fd, base + (loc & ~(DIRBLKSIZ - 1)), SEEK_SET);
442	dirp->dd_loc = loc & (DIRBLKSIZ - 1);
443	if (dirp->dd_loc != 0)
444		dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, DIRBLKSIZ);
445}
446
447/*
448 * get next entry in a directory.
449 */
450struct direct *
451rst_readdir(RST_DIR *dirp)
452{
453	struct direct *dp;
454
455	for (;;) {
456		if (dirp->dd_loc == 0) {
457			dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
458			    DIRBLKSIZ);
459			if (dirp->dd_size <= 0) {
460				dprintf(stderr, "error reading directory\n");
461				return (NULL);
462			}
463		}
464		if (dirp->dd_loc >= dirp->dd_size) {
465			dirp->dd_loc = 0;
466			continue;
467		}
468		dp = (struct direct *)(dirp->dd_buf + dirp->dd_loc);
469		if (dp->d_reclen == 0 ||
470		    dp->d_reclen > DIRBLKSIZ + 1 - dirp->dd_loc) {
471			dprintf(stderr, "corrupted directory: bad reclen %d\n",
472				dp->d_reclen);
473			return (NULL);
474		}
475		dirp->dd_loc += dp->d_reclen;
476		if (dp->d_ino == 0 && strcmp(dp->d_name, "/") == 0)
477			return (NULL);
478		if (dp->d_ino >= maxino) {
479			dprintf(stderr, "corrupted directory: bad inum %d\n",
480				dp->d_ino);
481			continue;
482		}
483		return (dp);
484	}
485}
486
487/*
488 * Simulate the opening of a directory
489 */
490RST_DIR *
491rst_opendir(const char *name)
492{
493	struct inotab *itp;
494	RST_DIR *dirp;
495	ino_t ino;
496
497	if ((ino = dirlookup(name)) > 0 &&
498	    (itp = inotablookup(ino)) != NULL) {
499		dirp = opendirfile(dirfile);
500		rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
501		return (dirp);
502	}
503	return (NULL);
504}
505
506/*
507 * In our case, there is nothing to do when closing a directory.
508 */
509void
510rst_closedir(RST_DIR *dirp)
511{
512
513	(void)close(dirp->dd_fd);
514	free(dirp);
515	return;
516}
517
518/*
519 * Simulate finding the current offset in the directory.
520 */
521static long
522rst_telldir(RST_DIR *dirp)
523{
524	return ((long)lseek(dirp->dd_fd,
525	    (off_t)0, SEEK_CUR) - dirp->dd_size + dirp->dd_loc);
526}
527
528/*
529 * Open a directory file.
530 */
531static RST_DIR *
532opendirfile(const char *name)
533{
534	RST_DIR *dirp;
535	int fd;
536
537	if ((fd = open(name, O_RDONLY)) == -1)
538		return (NULL);
539	if ((dirp = malloc(sizeof(RST_DIR))) == NULL) {
540		(void)close(fd);
541		return (NULL);
542	}
543	dirp->dd_fd = fd;
544	dirp->dd_loc = 0;
545	return (dirp);
546}
547
548/*
549 * Set the mode, owner, and times for all new or changed directories
550 */
551void
552setdirmodes(int flags)
553{
554	FILE *mf;
555	struct modeinfo node;
556	struct entry *ep;
557	char *cp;
558	const char *tmpdir;
559
560	vprintf(stdout, "Set directory mode, owner, and times.\n");
561	if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
562		tmpdir = _PATH_TMP;
563	if (command == 'r' || command == 'R')
564		(void) sprintf(modefile, "%s/rstmode%d", tmpdir, dumpdate);
565	if (modefile[0] == '#') {
566		panic("modefile not defined\n");
567		fprintf(stderr, "directory mode, owner, and times not set\n");
568		return;
569	}
570	mf = fopen(modefile, "r");
571	if (mf == NULL) {
572		fprintf(stderr, "fopen: %s\n", strerror(errno));
573		fprintf(stderr, "cannot open mode file %s\n", modefile);
574		fprintf(stderr, "directory mode, owner, and times not set\n");
575		return;
576	}
577	clearerr(mf);
578	for (;;) {
579		(void) fread((char *)&node, 1, sizeof(struct modeinfo), mf);
580		if (feof(mf))
581			break;
582		ep = lookupino(node.ino);
583		if (command == 'i' || command == 'x') {
584			if (ep == NULL)
585				continue;
586			if ((flags & FORCE) == 0 && ep->e_flags & EXISTED) {
587				ep->e_flags &= ~NEW;
588				continue;
589			}
590			if (node.ino == ROOTINO &&
591		   	    reply("set owner/mode for '.'") == FAIL)
592				continue;
593		}
594		if (ep == NULL) {
595			panic("cannot find directory inode %d\n", node.ino);
596		} else {
597			cp = myname(ep);
598			if (!Nflag) {
599				(void) chown(cp, node.uid, node.gid);
600				(void) chmod(cp, node.mode);
601				utimes(cp, node.ctimep);
602				utimes(cp, node.mtimep);
603				(void) chflags(cp, node.flags);
604			}
605			ep->e_flags &= ~NEW;
606		}
607	}
608	if (ferror(mf))
609		panic("error setting directory modes\n");
610	(void) fclose(mf);
611}
612
613/*
614 * Generate a literal copy of a directory.
615 */
616int
617genliteraldir(char *name, ino_t ino)
618{
619	struct inotab *itp;
620	int ofile, dp, i, size;
621	char buf[BUFSIZ];
622
623	itp = inotablookup(ino);
624	if (itp == NULL)
625		panic("Cannot find directory inode %d named %s\n", ino, name);
626	if ((ofile = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
627		fprintf(stderr, "%s: ", name);
628		(void) fflush(stderr);
629		fprintf(stderr, "cannot create file: %s\n", strerror(errno));
630		return (FAIL);
631	}
632	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
633	dp = dup(dirp->dd_fd);
634	for (i = itp->t_size; i > 0; i -= BUFSIZ) {
635		size = i < BUFSIZ ? i : BUFSIZ;
636		if (read(dp, buf, (int) size) == -1) {
637			fprintf(stderr,
638				"write error extracting inode %d, name %s\n",
639				curfile.ino, curfile.name);
640			fprintf(stderr, "read: %s\n", strerror(errno));
641			done(1);
642		}
643		if (!Nflag && write(ofile, buf, (int) size) == -1) {
644			fprintf(stderr,
645				"write error extracting inode %d, name %s\n",
646				curfile.ino, curfile.name);
647			fprintf(stderr, "write: %s\n", strerror(errno));
648			done(1);
649		}
650	}
651	(void) close(dp);
652	(void) close(ofile);
653	return (GOOD);
654}
655
656/*
657 * Determine the type of an inode
658 */
659int
660inodetype(ino_t ino)
661{
662	struct inotab *itp;
663
664	itp = inotablookup(ino);
665	if (itp == NULL)
666		return (LEAF);
667	return (NODE);
668}
669
670/*
671 * Allocate and initialize a directory inode entry.
672 * If requested, save its pertinent mode, owner, and time info.
673 */
674static struct inotab *
675allocinotab(struct context *ctxp, long seekpt)
676{
677	struct inotab	*itp;
678	struct modeinfo node;
679
680	itp = calloc(1, sizeof(struct inotab));
681	if (itp == NULL)
682		panic("no memory directory table\n");
683	itp->t_next = inotab[INOHASH(ctxp->ino)];
684	inotab[INOHASH(ctxp->ino)] = itp;
685	itp->t_ino = ctxp->ino;
686	itp->t_seekpt = seekpt;
687	if (mf == NULL)
688		return (itp);
689	node.ino = ctxp->ino;
690	node.mtimep[0].tv_sec = ctxp->atime_sec;
691	node.mtimep[0].tv_usec = ctxp->atime_nsec / 1000;
692	node.mtimep[1].tv_sec = ctxp->mtime_sec;
693	node.mtimep[1].tv_usec = ctxp->mtime_nsec / 1000;
694	node.ctimep[0].tv_sec = ctxp->atime_sec;
695	node.ctimep[0].tv_usec = ctxp->atime_nsec / 1000;
696	node.ctimep[1].tv_sec = ctxp->birthtime_sec;
697	node.ctimep[1].tv_usec = ctxp->birthtime_nsec / 1000;
698	node.mode = ctxp->mode;
699	node.flags = ctxp->file_flags;
700	node.uid = ctxp->uid;
701	node.gid = ctxp->gid;
702	(void) fwrite((char *)&node, 1, sizeof(struct modeinfo), mf);
703	return (itp);
704}
705
706/*
707 * Look up an inode in the table of directories
708 */
709static struct inotab *
710inotablookup(ino_t ino)
711{
712	struct inotab *itp;
713
714	for (itp = inotab[INOHASH(ino)]; itp != NULL; itp = itp->t_next)
715		if (itp->t_ino == ino)
716			return (itp);
717	return (NULL);
718}
719
720/*
721 * Clean up and exit
722 */
723void
724done(int exitcode)
725{
726
727	closemt();
728	if (modefile[0] != '#')
729		(void) unlink(modefile);
730	if (dirfile[0] != '#')
731		(void) unlink(dirfile);
732	exit(exitcode);
733}
734