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