dirs.c revision 1.19 1 /* $NetBSD: dirs.c,v 1.19 1996/09/27 03:23:33 thorpej Exp $ */
2
3 /*
4 * Copyright (c) 1983, 1993
5 * The Regents of the University of California. All rights reserved.
6 * (c) UNIX System Laboratories, Inc.
7 * All or some portions of this file are derived from material licensed
8 * to the University of California by American Telephone and Telegraph
9 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
10 * the permission of UNIX System Laboratories, Inc.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by the University of
23 * California, Berkeley and its contributors.
24 * 4. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)dirs.c 8.5 (Berkeley) 8/31/94";
44 #else
45 static char rcsid[] = "$NetBSD: dirs.c,v 1.19 1996/09/27 03:23:33 thorpej Exp $";
46 #endif
47 #endif /* not lint */
48
49 #include <sys/param.h>
50 #include <sys/file.h>
51 #include <sys/stat.h>
52 #include <sys/time.h>
53
54 #include <ufs/ffs/fs.h>
55 #include <ufs/ufs/dinode.h>
56 #include <ufs/ufs/dir.h>
57 #include <protocols/dumprestore.h>
58
59 #include <errno.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
64
65 #include <machine/endian.h>
66
67 #include "pathnames.h"
68 #include "restore.h"
69 #include "extern.h"
70
71 /*
72 * Symbol table of directories read from tape.
73 */
74 #define HASHSIZE 1000
75 #define INOHASH(val) (val % HASHSIZE)
76 struct inotab {
77 struct inotab *t_next;
78 ino_t t_ino;
79 long t_seekpt;
80 long t_size;
81 };
82 static struct inotab *inotab[HASHSIZE];
83
84 /*
85 * Information retained about directories.
86 */
87 struct modeinfo {
88 ino_t ino;
89 struct timeval timep[2];
90 mode_t mode;
91 uid_t uid;
92 gid_t gid;
93 int flags;
94 };
95
96 /*
97 * Definitions for library routines operating on directories.
98 */
99 #undef DIRBLKSIZ
100 #define DIRBLKSIZ 1024
101 struct rstdirdesc {
102 int dd_fd;
103 long dd_loc;
104 long dd_size;
105 char dd_buf[DIRBLKSIZ];
106 };
107
108 /*
109 * Global variables for this file.
110 */
111 static long seekpt;
112 static FILE *df, *mf;
113 static RST_DIR *dirp;
114 static char dirfile[32] = "#"; /* No file */
115 static char modefile[32] = "#"; /* No file */
116 static char dot[2] = "."; /* So it can be modified */
117
118 /*
119 * Format of old style directories.
120 */
121 #define ODIRSIZ 14
122 struct odirect {
123 u_short d_ino;
124 char d_name[ODIRSIZ];
125 };
126
127 static struct inotab *allocinotab __P((ino_t, struct dinode *, long));
128 static void dcvt __P((struct odirect *, struct direct *));
129 static void flushent __P((void));
130 static struct inotab *inotablookup __P((ino_t));
131 static RST_DIR *opendirfile __P((const char *));
132 static void putdir __P((char *, long));
133 static void putent __P((struct direct *));
134 static void rst_seekdir __P((RST_DIR *, long, long));
135 static long rst_telldir __P((RST_DIR *));
136 static struct direct *searchdir __P((ino_t, char *));
137
138 /*
139 * Extract directory contents, building up a directory structure
140 * on disk for extraction by name.
141 * If genmode is requested, save mode, owner, and times for all
142 * directories on the tape.
143 */
144 void
145 extractdirs(genmode)
146 int genmode;
147 {
148 register int i;
149 register struct dinode *ip;
150 struct inotab *itp;
151 struct direct nulldir;
152
153 vprintf(stdout, "Extract directories from tape\n");
154 (void) snprintf(dirfile, sizeof(dirfile), "%s/rstdir%d",
155 _PATH_TMP, dumpdate);
156 df = fopen(dirfile, "w");
157 if (df == NULL) {
158 fprintf(stderr,
159 "restore: %s - cannot create directory temporary\n",
160 dirfile);
161 fprintf(stderr, "fopen: %s\n", strerror(errno));
162 exit(1);
163 }
164 if (genmode != 0) {
165 (void) snprintf(modefile, sizeof(modefile), "%s/rstmode%d",
166 _PATH_TMP, dumpdate);
167 mf = fopen(modefile, "w");
168 if (mf == NULL) {
169 fprintf(stderr,
170 "restore: %s - cannot create modefile \n",
171 modefile);
172 fprintf(stderr, "fopen: %s\n", strerror(errno));
173 exit(1);
174 }
175 }
176 nulldir.d_ino = 0;
177 nulldir.d_type = DT_DIR;
178 nulldir.d_namlen = 1;
179 (void) strcpy(nulldir.d_name, "/");
180 nulldir.d_reclen = DIRSIZ(0, &nulldir);
181 for (;;) {
182 curfile.name = "<directory file - name unknown>";
183 curfile.action = USING;
184 ip = curfile.dip;
185 if (ip == NULL || (ip->di_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.ino, ip, 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()
211 {
212
213 while ((curfile.dip->di_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(pname, ino, todo)
224 char *pname;
225 ino_t ino;
226 long (*todo) __P((char *, ino_t, int));
227 {
228 register struct inotab *itp;
229 register struct direct *dp;
230 int namelen;
231 long bpt;
232 char locname[MAXPATHLEN + 1];
233
234 itp = inotablookup(ino);
235 if (itp == NULL) {
236 /*
237 * Pname is name of a simple file or an unchanged directory.
238 */
239 (void) (*todo)(pname, ino, LEAF);
240 return;
241 }
242 /*
243 * Pname is a dumped directory name.
244 */
245 if ((*todo)(pname, ino, NODE) == FAIL)
246 return;
247 /*
248 * begin search through the directory
249 * skipping over "." and ".."
250 */
251 (void) strncpy(locname, pname, MAXPATHLEN);
252 (void) strncat(locname, "/", MAXPATHLEN);
253 namelen = strlen(locname);
254 rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
255 dp = rst_readdir(dirp); /* "." */
256 if (dp != NULL && strcmp(dp->d_name, ".") == 0)
257 dp = rst_readdir(dirp); /* ".." */
258 else
259 fprintf(stderr, "Warning: `.' missing from directory %s\n",
260 pname);
261 if (dp != NULL && strcmp(dp->d_name, "..") == 0)
262 dp = rst_readdir(dirp); /* first real entry */
263 else
264 fprintf(stderr, "Warning: `..' missing from directory %s\n",
265 pname);
266 bpt = rst_telldir(dirp);
267 /*
268 * a zero inode signals end of directory
269 */
270 while (dp != NULL) {
271 locname[namelen] = '\0';
272 if (namelen + dp->d_namlen >= MAXPATHLEN) {
273 fprintf(stderr, "%s%s: name exceeds %d char\n",
274 locname, dp->d_name, MAXPATHLEN);
275 } else {
276 (void) strncat(locname, dp->d_name, (int)dp->d_namlen);
277 treescan(locname, dp->d_ino, todo);
278 rst_seekdir(dirp, bpt, itp->t_seekpt);
279 }
280 dp = rst_readdir(dirp);
281 bpt = rst_telldir(dirp);
282 }
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 dp = NULL;
302 while ((name = strsep(&path, "/")) != NULL && *name != NULL) {
303 if ((dp = searchdir(ino, name)) == NULL)
304 return (NULL);
305 ino = dp->d_ino;
306 }
307 return (dp);
308 }
309
310 /*
311 * Lookup the requested name in directory inum.
312 * Return its inode number if found, zero if it does not exist.
313 */
314 static struct direct *
315 searchdir(inum, name)
316 ino_t inum;
317 char *name;
318 {
319 register struct direct *dp;
320 register struct inotab *itp;
321 int len;
322
323 itp = inotablookup(inum);
324 if (itp == NULL)
325 return (NULL);
326 rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
327 len = strlen(name);
328 do {
329 dp = rst_readdir(dirp);
330 if (dp == NULL)
331 return (NULL);
332 } while (dp->d_namlen != len || strncmp(dp->d_name, name, len) != 0);
333 return (dp);
334 }
335
336 /*
337 * Put the directory entries in the directory file
338 */
339 static void
340 putdir(buf, size)
341 char *buf;
342 long size;
343 {
344 struct direct cvtbuf;
345 register struct odirect *odp;
346 struct odirect *eodp;
347 register struct direct *dp;
348 long loc, i;
349
350 if (cvtflag) {
351 eodp = (struct odirect *)&buf[size];
352 for (odp = (struct odirect *)buf; odp < eodp; odp++)
353 if (odp->d_ino != 0) {
354 dcvt(odp, &cvtbuf);
355 putent(&cvtbuf);
356 }
357 } else {
358 for (loc = 0; loc < size; ) {
359 dp = (struct direct *)(buf + loc);
360 if (Bcvt)
361 swabst((u_char *)"ls", (u_char *) dp);
362 if (oldinofmt && dp->d_ino != 0) {
363 # if BYTE_ORDER == BIG_ENDIAN
364 if (Bcvt)
365 dp->d_namlen = dp->d_type;
366 # else
367 if (!Bcvt)
368 dp->d_namlen = dp->d_type;
369 # endif
370 dp->d_type = DT_UNKNOWN;
371 }
372 i = DIRBLKSIZ - (loc & (DIRBLKSIZ - 1));
373 if ((dp->d_reclen & 0x3) != 0 ||
374 dp->d_reclen > i ||
375 dp->d_reclen < DIRSIZ(0, dp) ||
376 dp->d_namlen > NAME_MAX) {
377 vprintf(stdout, "Mangled directory: ");
378 if ((dp->d_reclen & 0x3) != 0)
379 vprintf(stdout,
380 "reclen not multiple of 4 ");
381 if (dp->d_reclen < DIRSIZ(0, dp))
382 vprintf(stdout,
383 "reclen less than DIRSIZ (%d < %d) ",
384 dp->d_reclen, DIRSIZ(0, dp));
385 if (dp->d_namlen > NAME_MAX)
386 vprintf(stdout,
387 "reclen name too big (%d > %d) ",
388 dp->d_namlen, NAME_MAX);
389 vprintf(stdout, "\n");
390 loc += i;
391 continue;
392 }
393 loc += dp->d_reclen;
394 if (dp->d_ino != 0) {
395 putent(dp);
396 }
397 }
398 }
399 }
400
401 /*
402 * These variables are "local" to the following two functions.
403 */
404 char dirbuf[DIRBLKSIZ];
405 long dirloc = 0;
406 long prev = 0;
407
408 /*
409 * add a new directory entry to a file.
410 */
411 static void
412 putent(dp)
413 struct direct *dp;
414 {
415 dp->d_reclen = DIRSIZ(0, dp);
416 if (dirloc + dp->d_reclen > DIRBLKSIZ) {
417 ((struct direct *)(dirbuf + prev))->d_reclen =
418 DIRBLKSIZ - prev;
419 (void) fwrite(dirbuf, 1, DIRBLKSIZ, df);
420 dirloc = 0;
421 }
422 memcpy(dirbuf + dirloc, dp, (long)dp->d_reclen);
423 prev = dirloc;
424 dirloc += dp->d_reclen;
425 }
426
427 /*
428 * flush out a directory that is finished.
429 */
430 static void
431 flushent()
432 {
433 ((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev;
434 (void) fwrite(dirbuf, (int)dirloc, 1, df);
435 seekpt = ftell(df);
436 dirloc = 0;
437 }
438
439 static void
440 dcvt(odp, ndp)
441 register struct odirect *odp;
442 register struct direct *ndp;
443 {
444
445 memset(ndp, 0, (long)(sizeof *ndp));
446 ndp->d_ino = odp->d_ino;
447 ndp->d_type = DT_UNKNOWN;
448 (void) strncpy(ndp->d_name, odp->d_name, ODIRSIZ);
449 ndp->d_namlen = strlen(ndp->d_name);
450 ndp->d_reclen = DIRSIZ(0, ndp);
451 }
452
453 /*
454 * Seek to an entry in a directory.
455 * Only values returned by rst_telldir should be passed to rst_seekdir.
456 * This routine handles many directories in a single file.
457 * It takes the base of the directory in the file, plus
458 * the desired seek offset into it.
459 */
460 static void
461 rst_seekdir(dirp, loc, base)
462 register RST_DIR *dirp;
463 long loc, base;
464 {
465
466 if (loc == rst_telldir(dirp))
467 return;
468 loc -= base;
469 if (loc < 0)
470 fprintf(stderr, "bad seek pointer to rst_seekdir %d\n", loc);
471 (void) lseek(dirp->dd_fd, base + (loc & ~(DIRBLKSIZ - 1)), SEEK_SET);
472 dirp->dd_loc = loc & (DIRBLKSIZ - 1);
473 if (dirp->dd_loc != 0)
474 dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, DIRBLKSIZ);
475 }
476
477 /*
478 * get next entry in a directory.
479 */
480 struct direct *
481 rst_readdir(dirp)
482 register RST_DIR *dirp;
483 {
484 register struct direct *dp;
485
486 for (;;) {
487 if (dirp->dd_loc == 0) {
488 dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
489 DIRBLKSIZ);
490 if (dirp->dd_size <= 0) {
491 dprintf(stderr, "error reading directory\n");
492 return (NULL);
493 }
494 }
495 if (dirp->dd_loc >= dirp->dd_size) {
496 dirp->dd_loc = 0;
497 continue;
498 }
499 dp = (struct direct *)(dirp->dd_buf + dirp->dd_loc);
500 if (dp->d_reclen == 0 ||
501 dp->d_reclen > DIRBLKSIZ + 1 - dirp->dd_loc) {
502 dprintf(stderr, "corrupted directory: bad reclen %d\n",
503 dp->d_reclen);
504 return (NULL);
505 }
506 dirp->dd_loc += dp->d_reclen;
507 if (dp->d_ino == 0 && strcmp(dp->d_name, "/") == 0)
508 return (NULL);
509 if (dp->d_ino >= maxino) {
510 dprintf(stderr, "corrupted directory: bad inum %d\n",
511 dp->d_ino);
512 continue;
513 }
514 return (dp);
515 }
516 }
517
518 /*
519 * Simulate the opening of a directory
520 */
521 RST_DIR *
522 rst_opendir(name)
523 const char *name;
524 {
525 struct inotab *itp;
526 RST_DIR *dirp;
527 ino_t ino;
528
529 if ((ino = dirlookup(name)) > 0 &&
530 (itp = inotablookup(ino)) != NULL) {
531 dirp = opendirfile(dirfile);
532 rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
533 return (dirp);
534 }
535 return (NULL);
536 }
537
538 /*
539 * In our case, there is nothing to do when closing a directory.
540 */
541 void
542 rst_closedir(dirp)
543 RST_DIR *dirp;
544 {
545
546 (void)close(dirp->dd_fd);
547 free(dirp);
548 return;
549 }
550
551 /*
552 * Simulate finding the current offset in the directory.
553 */
554 static long
555 rst_telldir(dirp)
556 RST_DIR *dirp;
557 {
558 return ((long)lseek(dirp->dd_fd,
559 (off_t)0, SEEK_CUR) - dirp->dd_size + dirp->dd_loc);
560 }
561
562 /*
563 * Open a directory file.
564 */
565 static RST_DIR *
566 opendirfile(name)
567 const char *name;
568 {
569 register RST_DIR *dirp;
570 register int fd;
571
572 if ((fd = open(name, O_RDONLY)) == -1)
573 return (NULL);
574 if ((dirp = malloc(sizeof(RST_DIR))) == NULL) {
575 (void)close(fd);
576 return (NULL);
577 }
578 dirp->dd_fd = fd;
579 dirp->dd_loc = 0;
580 return (dirp);
581 }
582
583 /*
584 * Set the mode, owner, and times for all new or changed directories
585 */
586 void
587 setdirmodes(flags)
588 int flags;
589 {
590 FILE *mf;
591 struct modeinfo node;
592 struct entry *ep;
593 char *cp;
594
595 vprintf(stdout, "Set directory mode, owner, and times.\n");
596 (void) snprintf(modefile, sizeof(modefile), "%s/rstmode%d",
597 _PATH_TMP, dumpdate);
598 mf = fopen(modefile, "r");
599 if (mf == NULL) {
600 fprintf(stderr, "fopen: %s\n", strerror(errno));
601 fprintf(stderr, "cannot open mode file %s\n", modefile);
602 fprintf(stderr, "directory mode, owner, and times not set\n");
603 return;
604 }
605 clearerr(mf);
606 for (;;) {
607 (void) fread((char *)&node, 1, sizeof(struct modeinfo), mf);
608 if (feof(mf))
609 break;
610 ep = lookupino(node.ino);
611 if (command == 'i' || command == 'x') {
612 if (ep == NULL)
613 continue;
614 if ((flags & FORCE) == 0 && ep->e_flags & EXISTED) {
615 ep->e_flags &= ~NEW;
616 continue;
617 }
618 if (node.ino == ROOTINO &&
619 reply("set owner/mode for '.'") == FAIL)
620 continue;
621 }
622 if (ep == NULL) {
623 panic("cannot find directory inode %d\n", node.ino);
624 } else {
625 cp = myname(ep);
626 (void) chown(cp, node.uid, node.gid);
627 (void) chmod(cp, node.mode);
628 (void) chflags(cp, node.flags);
629 utimes(cp, node.timep);
630 ep->e_flags &= ~NEW;
631 }
632 }
633 if (ferror(mf))
634 panic("error setting directory modes\n");
635 (void) fclose(mf);
636 }
637
638 /*
639 * Generate a literal copy of a directory.
640 */
641 int
642 genliteraldir(name, ino)
643 char *name;
644 ino_t ino;
645 {
646 register struct inotab *itp;
647 int ofile, dp, i, size;
648 char buf[BUFSIZ];
649
650 itp = inotablookup(ino);
651 if (itp == NULL)
652 panic("Cannot find directory inode %d named %s\n", ino, name);
653 if ((ofile = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
654 fprintf(stderr, "%s: ", name);
655 (void) fflush(stderr);
656 fprintf(stderr, "cannot create file: %s\n", strerror(errno));
657 return (FAIL);
658 }
659 rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
660 dp = dup(dirp->dd_fd);
661 for (i = itp->t_size; i > 0; i -= BUFSIZ) {
662 size = i < BUFSIZ ? i : BUFSIZ;
663 if (read(dp, buf, (int) size) == -1) {
664 fprintf(stderr,
665 "write error extracting inode %d, name %s\n",
666 curfile.ino, curfile.name);
667 fprintf(stderr, "read: %s\n", strerror(errno));
668 exit(1);
669 }
670 if (!Nflag && write(ofile, buf, (int) size) == -1) {
671 fprintf(stderr,
672 "write error extracting inode %d, name %s\n",
673 curfile.ino, curfile.name);
674 fprintf(stderr, "write: %s\n", strerror(errno));
675 exit(1);
676 }
677 }
678 (void) close(dp);
679 (void) close(ofile);
680 return (GOOD);
681 }
682
683 /*
684 * Determine the type of an inode
685 */
686 int
687 inodetype(ino)
688 ino_t ino;
689 {
690 struct inotab *itp;
691
692 itp = inotablookup(ino);
693 if (itp == NULL)
694 return (LEAF);
695 return (NODE);
696 }
697
698 /*
699 * Allocate and initialize a directory inode entry.
700 * If requested, save its pertinent mode, owner, and time info.
701 */
702 static struct inotab *
703 allocinotab(ino, dip, seekpt)
704 ino_t ino;
705 struct dinode *dip;
706 long seekpt;
707 {
708 register struct inotab *itp;
709 struct modeinfo node;
710
711 itp = calloc(1, sizeof(struct inotab));
712 if (itp == NULL)
713 panic("no memory directory table\n");
714 itp->t_next = inotab[INOHASH(ino)];
715 inotab[INOHASH(ino)] = itp;
716 itp->t_ino = ino;
717 itp->t_seekpt = seekpt;
718 if (mf == NULL)
719 return (itp);
720 node.ino = ino;
721 node.timep[0].tv_sec = dip->di_atime;
722 node.timep[0].tv_usec = dip->di_atimensec / 1000;
723 node.timep[1].tv_sec = dip->di_mtime;
724 node.timep[1].tv_usec = dip->di_mtimensec / 1000;
725 node.mode = dip->di_mode;
726 node.flags = dip->di_flags;
727 node.uid = dip->di_uid;
728 node.gid = dip->di_gid;
729 (void) fwrite((char *)&node, 1, sizeof(struct modeinfo), mf);
730 return (itp);
731 }
732
733 /*
734 * Look up an inode in the table of directories
735 */
736 static struct inotab *
737 inotablookup(ino)
738 ino_t ino;
739 {
740 register struct inotab *itp;
741
742 for (itp = inotab[INOHASH(ino)]; itp != NULL; itp = itp->t_next)
743 if (itp->t_ino == ino)
744 return (itp);
745 return (NULL);
746 }
747
748 /*
749 * Clean up and exit
750 */
751 void
752 cleanup()
753 {
754
755 closemt();
756 if (modefile[0] != '#')
757 (void) unlink(modefile);
758 if (dirfile[0] != '#')
759 (void) unlink(dirfile);
760 }
761