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