386BSD 0.0 development
[unix-history] / usr / src / sbin / restore / restore.c
CommitLineData
6beb798a
WJ
1/*
2 * Copyright (c) 1983 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char sccsid[] = "@(#)restore.c 5.7 (Berkeley) 6/1/90";
36#endif /* not lint */
37
38#include "restore.h"
39
40/*
41 * This implements the 't' option.
42 * List entries on the tape.
43 */
44long
45listfile(name, ino, type)
46 char *name;
47 ino_t ino;
48 int type;
49{
50 long descend = hflag ? GOOD : FAIL;
51
52 if (BIT(ino, dumpmap) == 0) {
53 return (descend);
54 }
55 vprintf(stdout, "%s", type == LEAF ? "leaf" : "dir ");
56 fprintf(stdout, "%10d\t%s\n", ino, name);
57 return (descend);
58}
59
60/*
61 * This implements the 'x' option.
62 * Request that new entries be extracted.
63 */
64long
65addfile(name, ino, type)
66 char *name;
67 ino_t ino;
68 int type;
69{
70 register struct entry *ep;
71 long descend = hflag ? GOOD : FAIL;
72 char buf[100];
73
74 if (BIT(ino, dumpmap) == 0) {
75 dprintf(stdout, "%s: not on the tape\n", name);
76 return (descend);
77 }
78 if (!mflag) {
79 (void) sprintf(buf, "./%u", ino);
80 name = buf;
81 if (type == NODE) {
82 (void) genliteraldir(name, ino);
83 return (descend);
84 }
85 }
86 ep = lookupino(ino);
87 if (ep != NIL) {
88 if (strcmp(name, myname(ep)) == 0) {
89 ep->e_flags |= NEW;
90 return (descend);
91 }
92 type |= LINK;
93 }
94 ep = addentry(name, ino, type);
95 if (type == NODE)
96 newnode(ep);
97 ep->e_flags |= NEW;
98 return (descend);
99}
100
101/*
102 * This is used by the 'i' option to undo previous requests made by addfile.
103 * Delete entries from the request queue.
104 */
105/* ARGSUSED */
106long
107deletefile(name, ino, type)
108 char *name;
109 ino_t ino;
110 int type;
111{
112 long descend = hflag ? GOOD : FAIL;
113 struct entry *ep;
114
115 if (BIT(ino, dumpmap) == 0) {
116 return (descend);
117 }
118 ep = lookupino(ino);
119 if (ep != NIL)
120 ep->e_flags &= ~NEW;
121 return (descend);
122}
123
124/*
125 * The following four routines implement the incremental
126 * restore algorithm. The first removes old entries, the second
127 * does renames and calculates the extraction list, the third
128 * cleans up link names missed by the first two, and the final
129 * one deletes old directories.
130 *
131 * Directories cannot be immediately deleted, as they may have
132 * other files in them which need to be moved out first. As
133 * directories to be deleted are found, they are put on the
134 * following deletion list. After all deletions and renames
135 * are done, this list is actually deleted.
136 */
137static struct entry *removelist;
138
139/*
140 * Remove unneeded leaves from the old tree.
141 * Remove directories from the lookup chains.
142 */
143removeoldleaves()
144{
145 register struct entry *ep;
146 register ino_t i;
147
148 vprintf(stdout, "Mark entries to be removed.\n");
149 for (i = ROOTINO + 1; i < maxino; i++) {
150 ep = lookupino(i);
151 if (ep == NIL)
152 continue;
153 if (BIT(i, clrimap))
154 continue;
155 for ( ; ep != NIL; ep = ep->e_links) {
156 dprintf(stdout, "%s: REMOVE\n", myname(ep));
157 if (ep->e_type == LEAF) {
158 removeleaf(ep);
159 freeentry(ep);
160 } else {
161 mktempname(ep);
162 deleteino(ep->e_ino);
163 ep->e_next = removelist;
164 removelist = ep;
165 }
166 }
167 }
168}
169
170/*
171 * For each directory entry on the incremental tape, determine which
172 * category it falls into as follows:
173 * KEEP - entries that are to be left alone.
174 * NEW - new entries to be added.
175 * EXTRACT - files that must be updated with new contents.
176 * LINK - new links to be added.
177 * Renames are done at the same time.
178 */
179long
180nodeupdates(name, ino, type)
181 char *name;
182 ino_t ino;
183 int type;
184{
185 register struct entry *ep, *np, *ip;
186 long descend = GOOD;
187 int lookuptype = 0;
188 int key = 0;
189 /* key values */
190# define ONTAPE 0x1 /* inode is on the tape */
191# define INOFND 0x2 /* inode already exists */
192# define NAMEFND 0x4 /* name already exists */
193# define MODECHG 0x8 /* mode of inode changed */
194 extern char *keyval();
195
196 /*
197 * This routine is called once for each element in the
198 * directory hierarchy, with a full path name.
199 * The "type" value is incorrectly specified as LEAF for
200 * directories that are not on the dump tape.
201 *
202 * Check to see if the file is on the tape.
203 */
204 if (BIT(ino, dumpmap))
205 key |= ONTAPE;
206 /*
207 * Check to see if the name exists, and if the name is a link.
208 */
209 np = lookupname(name);
210 if (np != NIL) {
211 key |= NAMEFND;
212 ip = lookupino(np->e_ino);
213 if (ip == NULL)
214 panic("corrupted symbol table\n");
215 if (ip != np)
216 lookuptype = LINK;
217 }
218 /*
219 * Check to see if the inode exists, and if one of its links
220 * corresponds to the name (if one was found).
221 */
222 ip = lookupino(ino);
223 if (ip != NIL) {
224 key |= INOFND;
225 for (ep = ip->e_links; ep != NIL; ep = ep->e_links) {
226 if (ep == np) {
227 ip = ep;
228 break;
229 }
230 }
231 }
232 /*
233 * If both a name and an inode are found, but they do not
234 * correspond to the same file, then both the inode that has
235 * been found and the inode corresponding to the name that
236 * has been found need to be renamed. The current pathname
237 * is the new name for the inode that has been found. Since
238 * all files to be deleted have already been removed, the
239 * named file is either a now unneeded link, or it must live
240 * under a new name in this dump level. If it is a link, it
241 * can be removed. If it is not a link, it is given a
242 * temporary name in anticipation that it will be renamed
243 * when it is later found by inode number.
244 */
245 if (((key & (INOFND|NAMEFND)) == (INOFND|NAMEFND)) && ip != np) {
246 if (lookuptype == LINK) {
247 removeleaf(np);
248 freeentry(np);
249 } else {
250 dprintf(stdout, "name/inode conflict, mktempname %s\n",
251 myname(np));
252 mktempname(np);
253 }
254 np = NIL;
255 key &= ~NAMEFND;
256 }
257 if ((key & ONTAPE) &&
258 (((key & INOFND) && ip->e_type != type) ||
259 ((key & NAMEFND) && np->e_type != type)))
260 key |= MODECHG;
261
262 /*
263 * Decide on the disposition of the file based on its flags.
264 * Note that we have already handled the case in which
265 * a name and inode are found that correspond to different files.
266 * Thus if both NAMEFND and INOFND are set then ip == np.
267 */
268 switch (key) {
269
270 /*
271 * A previously existing file has been found.
272 * Mark it as KEEP so that other links to the inode can be
273 * detected, and so that it will not be reclaimed by the search
274 * for unreferenced names.
275 */
276 case INOFND|NAMEFND:
277 ip->e_flags |= KEEP;
278 dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
279 flagvalues(ip));
280 break;
281
282 /*
283 * A file on the tape has a name which is the same as a name
284 * corresponding to a different file in the previous dump.
285 * Since all files to be deleted have already been removed,
286 * this file is either a now unneeded link, or it must live
287 * under a new name in this dump level. If it is a link, it
288 * can simply be removed. If it is not a link, it is given a
289 * temporary name in anticipation that it will be renamed
290 * when it is later found by inode number (see INOFND case
291 * below). The entry is then treated as a new file.
292 */
293 case ONTAPE|NAMEFND:
294 case ONTAPE|NAMEFND|MODECHG:
295 if (lookuptype == LINK) {
296 removeleaf(np);
297 freeentry(np);
298 } else {
299 mktempname(np);
300 }
301 /* fall through */
302
303 /*
304 * A previously non-existent file.
305 * Add it to the file system, and request its extraction.
306 * If it is a directory, create it immediately.
307 * (Since the name is unused there can be no conflict)
308 */
309 case ONTAPE:
310 ep = addentry(name, ino, type);
311 if (type == NODE)
312 newnode(ep);
313 ep->e_flags |= NEW|KEEP;
314 dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
315 flagvalues(ep));
316 break;
317
318 /*
319 * A file with the same inode number, but a different
320 * name has been found. If the other name has not already
321 * been found (indicated by the KEEP flag, see above) then
322 * this must be a new name for the file, and it is renamed.
323 * If the other name has been found then this must be a
324 * link to the file. Hard links to directories are not
325 * permitted, and are either deleted or converted to
326 * symbolic links. Finally, if the file is on the tape,
327 * a request is made to extract it.
328 */
329 case ONTAPE|INOFND:
330 if (type == LEAF && (ip->e_flags & KEEP) == 0)
331 ip->e_flags |= EXTRACT;
332 /* fall through */
333 case INOFND:
334 if ((ip->e_flags & KEEP) == 0) {
335 renameit(myname(ip), name);
336 moveentry(ip, name);
337 ip->e_flags |= KEEP;
338 dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
339 flagvalues(ip));
340 break;
341 }
342 if (ip->e_type == NODE) {
343 descend = FAIL;
344 fprintf(stderr,
345 "deleted hard link %s to directory %s\n",
346 name, myname(ip));
347 break;
348 }
349 ep = addentry(name, ino, type|LINK);
350 ep->e_flags |= NEW;
351 dprintf(stdout, "[%s] %s: %s|LINK\n", keyval(key), name,
352 flagvalues(ep));
353 break;
354
355 /*
356 * A previously known file which is to be updated.
357 */
358 case ONTAPE|INOFND|NAMEFND:
359 if (type == LEAF && lookuptype != LINK)
360 np->e_flags |= EXTRACT;
361 np->e_flags |= KEEP;
362 dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
363 flagvalues(np));
364 break;
365
366 /*
367 * An inode is being reused in a completely different way.
368 * Normally an extract can simply do an "unlink" followed
369 * by a "creat". Here we must do effectively the same
370 * thing. The complications arise because we cannot really
371 * delete a directory since it may still contain files
372 * that we need to rename, so we delete it from the symbol
373 * table, and put it on the list to be deleted eventually.
374 * Conversely if a directory is to be created, it must be
375 * done immediately, rather than waiting until the
376 * extraction phase.
377 */
378 case ONTAPE|INOFND|MODECHG:
379 case ONTAPE|INOFND|NAMEFND|MODECHG:
380 if (ip->e_flags & KEEP) {
381 badentry(ip, "cannot KEEP and change modes");
382 break;
383 }
384 if (ip->e_type == LEAF) {
385 /* changing from leaf to node */
386 removeleaf(ip);
387 freeentry(ip);
388 ip = addentry(name, ino, type);
389 newnode(ip);
390 } else {
391 /* changing from node to leaf */
392 if ((ip->e_flags & TMPNAME) == 0)
393 mktempname(ip);
394 deleteino(ip->e_ino);
395 ip->e_next = removelist;
396 removelist = ip;
397 ip = addentry(name, ino, type);
398 }
399 ip->e_flags |= NEW|KEEP;
400 dprintf(stdout, "[%s] %s: %s\n", keyval(key), name,
401 flagvalues(ip));
402 break;
403
404 /*
405 * A hard link to a diirectory that has been removed.
406 * Ignore it.
407 */
408 case NAMEFND:
409 dprintf(stdout, "[%s] %s: Extraneous name\n", keyval(key),
410 name);
411 descend = FAIL;
412 break;
413
414 /*
415 * If we find a directory entry for a file that is not on
416 * the tape, then we must have found a file that was created
417 * while the dump was in progress. Since we have no contents
418 * for it, we discard the name knowing that it will be on the
419 * next incremental tape.
420 */
421 case NIL:
422 fprintf(stderr, "%s: (inode %d) not found on tape\n",
423 name, ino);
424 break;
425
426 /*
427 * If any of these arise, something is grievously wrong with
428 * the current state of the symbol table.
429 */
430 case INOFND|NAMEFND|MODECHG:
431 case NAMEFND|MODECHG:
432 case INOFND|MODECHG:
433 fprintf(stderr, "[%s] %s: inconsistent state\n", keyval(key),
434 name);
435 break;
436
437 /*
438 * These states "cannot" arise for any state of the symbol table.
439 */
440 case ONTAPE|MODECHG:
441 case MODECHG:
442 default:
443 panic("[%s] %s: impossible state\n", keyval(key), name);
444 break;
445 }
446 return (descend);
447}
448
449/*
450 * Calculate the active flags in a key.
451 */
452char *
453keyval(key)
454 int key;
455{
456 static char keybuf[32];
457
458 (void) strcpy(keybuf, "|NIL");
459 keybuf[0] = '\0';
460 if (key & ONTAPE)
461 (void) strcat(keybuf, "|ONTAPE");
462 if (key & INOFND)
463 (void) strcat(keybuf, "|INOFND");
464 if (key & NAMEFND)
465 (void) strcat(keybuf, "|NAMEFND");
466 if (key & MODECHG)
467 (void) strcat(keybuf, "|MODECHG");
468 return (&keybuf[1]);
469}
470
471/*
472 * Find unreferenced link names.
473 */
474findunreflinks()
475{
476 register struct entry *ep, *np;
477 register ino_t i;
478
479 vprintf(stdout, "Find unreferenced names.\n");
480 for (i = ROOTINO; i < maxino; i++) {
481 ep = lookupino(i);
482 if (ep == NIL || ep->e_type == LEAF || BIT(i, dumpmap) == 0)
483 continue;
484 for (np = ep->e_entries; np != NIL; np = np->e_sibling) {
485 if (np->e_flags == 0) {
486 dprintf(stdout,
487 "%s: remove unreferenced name\n",
488 myname(np));
489 removeleaf(np);
490 freeentry(np);
491 }
492 }
493 }
494 /*
495 * Any leaves remaining in removed directories is unreferenced.
496 */
497 for (ep = removelist; ep != NIL; ep = ep->e_next) {
498 for (np = ep->e_entries; np != NIL; np = np->e_sibling) {
499 if (np->e_type == LEAF) {
500 if (np->e_flags != 0)
501 badentry(np, "unreferenced with flags");
502 dprintf(stdout,
503 "%s: remove unreferenced name\n",
504 myname(np));
505 removeleaf(np);
506 freeentry(np);
507 }
508 }
509 }
510}
511
512/*
513 * Remove old nodes (directories).
514 * Note that this routine runs in O(N*D) where:
515 * N is the number of directory entries to be removed.
516 * D is the maximum depth of the tree.
517 * If N == D this can be quite slow. If the list were
518 * topologically sorted, the deletion could be done in
519 * time O(N).
520 */
521removeoldnodes()
522{
523 register struct entry *ep, **prev;
524 long change;
525
526 vprintf(stdout, "Remove old nodes (directories).\n");
527 do {
528 change = 0;
529 prev = &removelist;
530 for (ep = removelist; ep != NIL; ep = *prev) {
531 if (ep->e_entries != NIL) {
532 prev = &ep->e_next;
533 continue;
534 }
535 *prev = ep->e_next;
536 removenode(ep);
537 freeentry(ep);
538 change++;
539 }
540 } while (change);
541 for (ep = removelist; ep != NIL; ep = ep->e_next)
542 badentry(ep, "cannot remove, non-empty");
543}
544
545/*
546 * This is the routine used to extract files for the 'r' command.
547 * Extract new leaves.
548 */
549createleaves(symtabfile)
550 char *symtabfile;
551{
552 register struct entry *ep;
553 ino_t first;
554 long curvol;
555
556 if (command == 'R') {
557 vprintf(stdout, "Continue extraction of new leaves\n");
558 } else {
559 vprintf(stdout, "Extract new leaves.\n");
560 dumpsymtable(symtabfile, volno);
561 }
562 first = lowerbnd(ROOTINO);
563 curvol = volno;
564 while (curfile.ino < maxino) {
565 first = lowerbnd(first);
566 /*
567 * If the next available file is not the one which we
568 * expect then we have missed one or more files. Since
569 * we do not request files that were not on the tape,
570 * the lost files must have been due to a tape read error,
571 * or a file that was removed while the dump was in progress.
572 */
573 while (first < curfile.ino) {
574 ep = lookupino(first);
575 if (ep == NIL)
576 panic("%d: bad first\n", first);
577 fprintf(stderr, "%s: not found on tape\n", myname(ep));
578 ep->e_flags &= ~(NEW|EXTRACT);
579 first = lowerbnd(first);
580 }
581 /*
582 * If we find files on the tape that have no corresponding
583 * directory entries, then we must have found a file that
584 * was created while the dump was in progress. Since we have
585 * no name for it, we discard it knowing that it will be
586 * on the next incremental tape.
587 */
588 if (first != curfile.ino) {
589 fprintf(stderr, "expected next file %d, got %d\n",
590 first, curfile.ino);
591 skipfile();
592 goto next;
593 }
594 ep = lookupino(curfile.ino);
595 if (ep == NIL)
596 panic("unknown file on tape\n");
597 if ((ep->e_flags & (NEW|EXTRACT)) == 0)
598 badentry(ep, "unexpected file on tape");
599 /*
600 * If the file is to be extracted, then the old file must
601 * be removed since its type may change from one leaf type
602 * to another (eg "file" to "character special").
603 */
604 if ((ep->e_flags & EXTRACT) != 0) {
605 removeleaf(ep);
606 ep->e_flags &= ~REMOVED;
607 }
608 (void) extractfile(myname(ep));
609 ep->e_flags &= ~(NEW|EXTRACT);
610 /*
611 * We checkpoint the restore after every tape reel, so
612 * as to simplify the amount of work re quired by the
613 * 'R' command.
614 */
615 next:
616 if (curvol != volno) {
617 dumpsymtable(symtabfile, volno);
618 skipmaps();
619 curvol = volno;
620 }
621 }
622}
623
624/*
625 * This is the routine used to extract files for the 'x' and 'i' commands.
626 * Efficiently extract a subset of the files on a tape.
627 */
628createfiles()
629{
630 register ino_t first, next, last;
631 register struct entry *ep;
632 long curvol;
633
634 vprintf(stdout, "Extract requested files\n");
635 curfile.action = SKIP;
636 getvol((long)1);
637 skipmaps();
638 skipdirs();
639 first = lowerbnd(ROOTINO);
640 last = upperbnd(maxino - 1);
641 for (;;) {
642 first = lowerbnd(first);
643 last = upperbnd(last);
644 /*
645 * Check to see if any files remain to be extracted
646 */
647 if (first > last)
648 return;
649 /*
650 * Reject any volumes with inodes greater
651 * than the last one needed
652 */
653 while (curfile.ino > last) {
654 curfile.action = SKIP;
655 getvol((long)0);
656 skipmaps();
657 skipdirs();
658 }
659 /*
660 * Decide on the next inode needed.
661 * Skip across the inodes until it is found
662 * or an out of order volume change is encountered
663 */
664 next = lowerbnd(curfile.ino);
665 do {
666 curvol = volno;
667 while (next > curfile.ino && volno == curvol)
668 skipfile();
669 skipmaps();
670 skipdirs();
671 } while (volno == curvol + 1);
672 /*
673 * If volume change out of order occurred the
674 * current state must be recalculated
675 */
676 if (volno != curvol)
677 continue;
678 /*
679 * If the current inode is greater than the one we were
680 * looking for then we missed the one we were looking for.
681 * Since we only attempt to extract files listed in the
682 * dump map, the lost files must have been due to a tape
683 * read error, or a file that was removed while the dump
684 * was in progress. Thus we report all requested files
685 * between the one we were looking for, and the one we
686 * found as missing, and delete their request flags.
687 */
688 while (next < curfile.ino) {
689 ep = lookupino(next);
690 if (ep == NIL)
691 panic("corrupted symbol table\n");
692 fprintf(stderr, "%s: not found on tape\n", myname(ep));
693 ep->e_flags &= ~NEW;
694 next = lowerbnd(next);
695 }
696 /*
697 * The current inode is the one that we are looking for,
698 * so extract it per its requested name.
699 */
700 if (next == curfile.ino && next <= last) {
701 ep = lookupino(next);
702 if (ep == NIL)
703 panic("corrupted symbol table\n");
704 (void) extractfile(myname(ep));
705 ep->e_flags &= ~NEW;
706 if (volno != curvol)
707 skipmaps();
708 }
709 }
710}
711
712/*
713 * Add links.
714 */
715createlinks()
716{
717 register struct entry *np, *ep;
718 register ino_t i;
719 char name[BUFSIZ];
720
721 vprintf(stdout, "Add links\n");
722 for (i = ROOTINO; i < maxino; i++) {
723 ep = lookupino(i);
724 if (ep == NIL)
725 continue;
726 for (np = ep->e_links; np != NIL; np = np->e_links) {
727 if ((np->e_flags & NEW) == 0)
728 continue;
729 (void) strcpy(name, myname(ep));
730 if (ep->e_type == NODE) {
731 (void) linkit(name, myname(np), SYMLINK);
732 } else {
733 (void) linkit(name, myname(np), HARDLINK);
734 }
735 np->e_flags &= ~NEW;
736 }
737 }
738}
739
740/*
741 * Check the symbol table.
742 * We do this to insure that all the requested work was done, and
743 * that no temporary names remain.
744 */
745checkrestore()
746{
747 register struct entry *ep;
748 register ino_t i;
749
750 vprintf(stdout, "Check the symbol table.\n");
751 for (i = ROOTINO; i < maxino; i++) {
752 for (ep = lookupino(i); ep != NIL; ep = ep->e_links) {
753 ep->e_flags &= ~KEEP;
754 if (ep->e_type == NODE)
755 ep->e_flags &= ~(NEW|EXISTED);
756 if (ep->e_flags != NULL)
757 badentry(ep, "incomplete operations");
758 }
759 }
760}
761
762/*
763 * Compare with the directory structure on the tape
764 * A paranoid check that things are as they should be.
765 */
766long
767verifyfile(name, ino, type)
768 char *name;
769 ino_t ino;
770 int type;
771{
772 struct entry *np, *ep;
773 long descend = GOOD;
774
775 ep = lookupname(name);
776 if (ep == NIL) {
777 fprintf(stderr, "Warning: missing name %s\n", name);
778 return (FAIL);
779 }
780 np = lookupino(ino);
781 if (np != ep)
782 descend = FAIL;
783 for ( ; np != NIL; np = np->e_links)
784 if (np == ep)
785 break;
786 if (np == NIL)
787 panic("missing inumber %d\n", ino);
788 if (ep->e_type == LEAF && type != LEAF)
789 badentry(ep, "type should be LEAF");
790 return (descend);
791}