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 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 98 struct 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 */ 108 static long seekpt; 109 static FILE *df, *mf; 110 static RST_DIR *dirp; 111 static char dirfile[MAXPATHLEN] = "#"; /* No file */ 112 static char modefile[MAXPATHLEN] = "#"; /* No file */ 113 static char dot[2] = "."; /* So it can be modified */ 114 115 /* 116 * Format of old style directories. 117 */ 118 #define ODIRSIZ 14 119 struct odirect { 120 u_short d_ino; 121 char d_name[ODIRSIZ]; 122 }; 123 124 static struct inotab *allocinotab(struct context *, long); 125 static void dcvt(struct odirect *, struct direct *); 126 static void flushent(void); 127 static struct inotab *inotablookup(ino_t); 128 static RST_DIR *opendirfile(const char *); 129 static void putdir(char *, long); 130 static void putent(struct direct *); 131 static void rst_seekdir(RST_DIR *, long, long); 132 static long rst_telldir(RST_DIR *); 133 static 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 */ 141 void 142 extractdirs(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 */ 210 void 211 skipdirs(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 */ 223 void 224 treescan(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 */ 287 struct direct * 288 pathsearch(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 */ 312 static struct direct * 313 searchdir(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 */ 335 static void 336 putdir(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 */ 379 char dirbuf[DIRBLKSIZ]; 380 long dirloc = 0; 381 long prev = 0; 382 383 /* 384 * add a new directory entry to a file. 385 */ 386 static void 387 putent(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 */ 404 static void 405 flushent(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 413 static void 414 dcvt(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 */ 432 static void 433 rst_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 */ 450 struct direct * 451 rst_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 */ 490 RST_DIR * 491 rst_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 */ 509 void 510 rst_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 */ 521 static long 522 rst_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 */ 531 static RST_DIR * 532 opendirfile(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 */ 551 void 552 setdirmodes(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 */ 616 int 617 genliteraldir(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 */ 659 int 660 inodetype(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 */ 674 static struct inotab * 675 allocinotab(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 */ 709 static struct inotab * 710 inotablookup(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 */ 723 void 724 done(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