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