Deleted Added
sdiff udiff text old ( 100207 ) new ( 103949 )
full compact
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}