walk.c revision 1.35 1 /* $NetBSD: walk.c,v 1.35 2024/04/23 22:12:48 christos Exp $ */
2
3 /*
4 * Copyright (c) 2001 Wasabi Systems, Inc.
5 * All rights reserved.
6 *
7 * Written by Luke Mewburn for Wasabi Systems, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed for the NetBSD Project by
20 * Wasabi Systems, Inc.
21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
22 * or promote products derived from this software without specific prior
23 * written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #if HAVE_NBTOOL_CONFIG_H
39 #include "nbtool_config.h"
40 #endif
41
42 #include <sys/cdefs.h>
43 #if defined(__RCSID) && !defined(__lint)
44 __RCSID("$NetBSD: walk.c,v 1.35 2024/04/23 22:12:48 christos Exp $");
45 #endif /* !__lint */
46
47 #include <sys/param.h>
48 #include <sys/stat.h>
49
50 #include <assert.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <stdio.h>
54 #include <dirent.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58 #include <util.h>
59
60 #include "makefs.h"
61 #include "mtree.h"
62
63 static void apply_specdir(const char *, NODE *, fsnode *, int);
64 static void apply_specentry(const char *, NODE *, fsnode *);
65 static fsnode *create_fsnode(const char *, const char *, const char *,
66 struct stat *);
67 static fsinode *link_check(fsinode *);
68
69 /*
70 * fsnode_cmp --
71 * This function is used by `qsort` so sort one directory's
72 * entries. `.` is always first, sollowed by anything else
73 * as compared by `strcmp()`.
74 */
75 static int
76 fsnode_cmp (const void *_left, const void *_right)
77 {
78 const fsnode * const left = *(const fsnode * const *)_left;
79 const fsnode * const right = *(const fsnode * const *)_right;
80
81 if (strcmp (left->name, ".") == 0)
82 return -1;
83 if (strcmp (right->name, ".") == 0)
84 return 1;
85 return strcmp (left->name, right->name);
86 }
87
88 /*
89 * walk_dir --
90 * build a tree of fsnodes from `root' and `dir', with a parent
91 * fsnode of `parent' (which may be NULL for the root of the tree).
92 * append the tree to a fsnode of `join' if it is not NULL.
93 * each "level" is a directory, with the "." entry guaranteed to be
94 * at the start of the list, and without ".." entries.
95 */
96 fsnode *
97 walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join,
98 int replace, int follow)
99 {
100 fsnode *first, *cur, *prev, *last;
101 DIR *dirp;
102 struct dirent *dent;
103 char path[MAXPATHLEN + 1];
104 struct stat stbuf;
105 char *name, *rp;
106 int dot, len;
107
108 fsnode **list, **listptr;
109 int num = 0;
110
111 assert(root != NULL);
112 assert(dir != NULL);
113
114 len = snprintf(path, sizeof(path), "%s/%s", root, dir);
115 if (len >= (int)sizeof(path))
116 errx(EXIT_FAILURE, "Pathname too long.");
117 if (debug & DEBUG_WALK_DIR)
118 printf("walk_dir: %s %p\n", path, parent);
119 if ((dirp = opendir(path)) == NULL)
120 err(EXIT_FAILURE, "Can't opendir `%s'", path);
121 rp = path + strlen(root) + 1;
122 if (join != NULL) {
123 first = cur = join;
124 while (cur->next != NULL)
125 cur = cur->next;
126 prev = last = cur;
127 } else
128 last = first = prev = NULL;
129 while ((dent = readdir(dirp)) != NULL) {
130 name = dent->d_name;
131 dot = 0;
132 if (name[0] == '.')
133 switch (name[1]) {
134 case '\0': /* "." */
135 if (join != NULL)
136 continue;
137 dot = 1;
138 break;
139 case '.': /* ".." */
140 if (name[2] == '\0')
141 continue;
142 /* FALLTHROUGH */
143 default:
144 dot = 0;
145 }
146 if (debug & DEBUG_WALK_DIR_NODE)
147 printf("scanning %s/%s/%s\n", root, dir, name);
148 if (snprintf(path + len, sizeof(path) - len, "/%s", name) >=
149 (int)sizeof(path) - len)
150 errx(EXIT_FAILURE, "Pathname too long.");
151 if (follow) {
152 if (stat(path, &stbuf) == -1)
153 err(EXIT_FAILURE, "Can't stat `%s'", path);
154 } else {
155 if (lstat(path, &stbuf) == -1)
156 err(EXIT_FAILURE, "Can't lstat `%s'", path);
157 /* As symlink permission bits vary between filesystems
158 (ie. 0755 on FFS/NetBSD, 0777 for ext[234]/Linux),
159 force them to 0755. */
160 if (S_ISLNK(stbuf.st_mode)) {
161 stbuf.st_mode &= ~(S_IRWXU | S_IRWXG | S_IRWXO);
162 stbuf.st_mode |= S_IRWXU
163 | S_IRGRP | S_IXGRP
164 | S_IROTH | S_IXOTH;
165 }
166 }
167 #ifdef S_ISSOCK
168 if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
169 if (debug & DEBUG_WALK_DIR_NODE)
170 printf(" skipping socket %s\n", path);
171 continue;
172 }
173 #endif
174
175 if (join != NULL) {
176 cur = join->next;
177 for (;;) {
178 if (cur == NULL || strcmp(cur->name, name) == 0)
179 break;
180 if (cur == last) {
181 cur = NULL;
182 break;
183 }
184 cur = cur->next;
185 }
186 if (cur != NULL) {
187 if (S_ISDIR(cur->type) &&
188 S_ISDIR(stbuf.st_mode)) {
189 if (debug & DEBUG_WALK_DIR_NODE)
190 printf("merging %s with %p\n",
191 path, cur->child);
192 cur->child = walk_dir(root, rp, cur,
193 cur->child, replace, follow);
194 continue;
195 }
196 if (!replace)
197 errx(EXIT_FAILURE,
198 "Can't merge %s `%s' with "
199 "existing %s",
200 inode_type(stbuf.st_mode), path,
201 inode_type(cur->type));
202 else {
203 if (debug & DEBUG_WALK_DIR_NODE)
204 printf("replacing %s %s\n",
205 inode_type(stbuf.st_mode),
206 path);
207 if (cur == join->next)
208 join->next = cur->next;
209 else {
210 fsnode *p;
211 for (p = join->next;
212 p->next != cur; p = p->next)
213 continue;
214 p->next = cur->next;
215 }
216 free(cur);
217 }
218 }
219 }
220
221 cur = create_fsnode(root, dir, name, &stbuf);
222 cur->parent = parent;
223 if (dot) {
224 /* ensure "." is at the start of the list */
225 cur->next = first;
226 first = cur;
227 if (! prev)
228 prev = cur;
229 cur->first = first;
230 } else { /* not "." */
231 if (prev)
232 prev->next = cur;
233 prev = cur;
234 if (!first)
235 first = cur;
236 cur->first = first;
237 if (S_ISDIR(cur->type)) {
238 cur->child = walk_dir(root, rp, cur, NULL,
239 replace, follow);
240 continue;
241 }
242 }
243 if (stbuf.st_nlink > 1) {
244 fsinode *curino;
245
246 curino = link_check(cur->inode);
247 if (curino != NULL) {
248 free(cur->inode);
249 cur->inode = curino;
250 cur->inode->nlink++;
251 if (debug & DEBUG_WALK_DIR_LINKCHECK)
252 printf("link_check: found [%llu, %llu]\n",
253 (unsigned long long)curino->st.st_dev,
254 (unsigned long long)curino->st.st_ino);
255 }
256 }
257 if (S_ISLNK(cur->type)) {
258 char slink[PATH_MAX+1];
259 int llen;
260
261 llen = readlink(path, slink, sizeof(slink) - 1);
262 if (llen == -1)
263 err(EXIT_FAILURE, "Readlink `%s'", path);
264 slink[llen] = '\0';
265 cur->symlink = estrdup(slink);
266 }
267 }
268 assert(first != NULL);
269 if (join == NULL)
270 for (cur = first->next; cur != NULL; cur = cur->next)
271 cur->first = first;
272 if (closedir(dirp) == -1)
273 err(EXIT_FAILURE, "Can't closedir `%s/%s'", root, dir);
274
275 /*
276 * Sort entries.
277 */
278 /* Create a plain list: Count, alloc, add. */
279 for (fsnode *tmp = first; tmp; tmp = tmp->next) {
280 num++;
281 if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
282 printf ("pre sort: %s %s %s\n", root, dir, tmp->name);
283 }
284 list = listptr = ecalloc (num, sizeof (*list));
285 for (fsnode *tmp = first; tmp; tmp = tmp->next)
286 *listptr++ = tmp;
287 /* Sort plain list. */
288 qsort (list, num, sizeof (*list), &fsnode_cmp);
289 /* Rewire. */
290 for (int i = 0; i < num - 1; ++i)
291 list[i]->next = list[i+1];
292 list[num - 1]->next = NULL;
293 first = list[0];
294 /* Check `first` to be ".". */
295 assert (strcmp (first->name, ".") == 0);
296 /* Free. */
297 free (list);
298 /* Dump sorted state. */
299 if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
300 for (fsnode *tmp = first; tmp; tmp = tmp->next)
301 printf ("post sort: %s %s %s\n", root, dir, tmp->name);
302
303 return first;
304 }
305
306 static fsnode *
307 create_fsnode(const char *root, const char *path, const char *name,
308 struct stat *stbuf)
309 {
310 fsnode *cur;
311
312 cur = ecalloc(1, sizeof(*cur));
313 cur->path = estrdup(path);
314 cur->name = estrdup(name);
315 cur->inode = ecalloc(1, sizeof(*cur->inode));
316 cur->root = root;
317 cur->type = stbuf->st_mode & S_IFMT;
318 cur->inode->nlink = 1;
319 cur->inode->st = *stbuf;
320 if (stampst.st_ino) {
321 cur->inode->st.st_atime = stampst.st_atime;
322 cur->inode->st.st_mtime = stampst.st_mtime;
323 cur->inode->st.st_ctime = stampst.st_ctime;
324 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
325 cur->inode->st.st_atimensec = stampst.st_atimensec;
326 cur->inode->st.st_mtimensec = stampst.st_mtimensec;
327 cur->inode->st.st_ctimensec = stampst.st_ctimensec;
328 #endif
329 #if HAVE_STRUCT_STAT_BIRTHTIME
330 cur->inode->st.st_birthtime = stampst.st_birthtime;
331 cur->inode->st.st_birthtimensec = stampst.st_birthtimensec;
332 #endif
333 }
334 return (cur);
335 }
336
337 /*
338 * free_fsnodes --
339 * Removes node from tree and frees it and all of
340 * its descendents.
341 */
342 void
343 free_fsnodes(fsnode *node)
344 {
345 fsnode *cur, *next;
346
347 assert(node != NULL);
348
349 /* for ".", start with actual parent node */
350 if (node->first == node) {
351 assert(node->name[0] == '.' && node->name[1] == '\0');
352 if (node->parent) {
353 assert(node->parent->child == node);
354 node = node->parent;
355 }
356 }
357
358 /* Find ourselves in our sibling list and unlink */
359 if (node->first != node) {
360 for (cur = node->first; cur->next; cur = cur->next) {
361 if (cur->next == node) {
362 cur->next = node->next;
363 node->next = NULL;
364 break;
365 }
366 }
367 }
368
369 for (cur = node; cur != NULL; cur = next) {
370 next = cur->next;
371 if (cur->child) {
372 cur->child->parent = NULL;
373 free_fsnodes(cur->child);
374 }
375 if (cur->inode->nlink-- == 1)
376 free(cur->inode);
377 if (cur->symlink)
378 free(cur->symlink);
379 free(cur->path);
380 free(cur->name);
381 free(cur);
382 }
383 }
384
385 /*
386 * apply_specfile --
387 * read in the mtree(8) specfile, and apply it to the tree
388 * at dir,parent. parameters in parent on equivalent types
389 * will be changed to those found in specfile, and missing
390 * entries will be added.
391 */
392 void
393 apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
394 {
395 struct timeval start;
396 FILE *fp;
397 NODE *root;
398
399 assert(specfile != NULL);
400 assert(parent != NULL);
401
402 if (debug & DEBUG_APPLY_SPECFILE)
403 printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
404
405 /* read in the specfile */
406 if ((fp = fopen(specfile, "r")) == NULL)
407 err(EXIT_FAILURE, "Can't open `%s'", specfile);
408 TIMER_START(start);
409 root = spec(fp);
410 TIMER_RESULTS(start, "spec");
411 if (fclose(fp) == EOF)
412 err(EXIT_FAILURE, "Can't close `%s'", specfile);
413
414 /* perform some sanity checks */
415 if (root == NULL)
416 errx(EXIT_FAILURE,
417 "Specfile `%s' did not contain a tree", specfile);
418 assert(strcmp(root->name, ".") == 0);
419 assert(root->type == F_DIR);
420
421 /* merge in the changes */
422 apply_specdir(dir, root, parent, speconly);
423
424 free_nodes(root);
425 }
426
427 static void
428 apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
429 {
430 char path[MAXPATHLEN + 1];
431 NODE *curnode;
432 fsnode *curfsnode;
433
434 assert(specnode != NULL);
435 assert(dirnode != NULL);
436
437 if (debug & DEBUG_APPLY_SPECFILE)
438 printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
439
440 if (specnode->type != F_DIR)
441 errx(EXIT_FAILURE, "Specfile node `%s/%s' is not a directory",
442 dir, specnode->name);
443 if (dirnode->type != S_IFDIR)
444 errx(EXIT_FAILURE, "Directory node `%s/%s' is not a directory",
445 dir, dirnode->name);
446
447 apply_specentry(dir, specnode, dirnode);
448
449 /* Remove any filesystem nodes not found in specfile */
450 /* XXX inefficient. This is O^2 in each dir and it would
451 * have been better never to have walked this part of the tree
452 * to begin with
453 */
454 if (speconly) {
455 fsnode *next;
456 assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
457 for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
458 next = curfsnode->next;
459 for (curnode = specnode->child; curnode != NULL;
460 curnode = curnode->next) {
461 if (strcmp(curnode->name, curfsnode->name) == 0)
462 break;
463 }
464 if (curnode == NULL) {
465 if (debug & DEBUG_APPLY_SPECONLY) {
466 printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode);
467 }
468 free_fsnodes(curfsnode);
469 }
470 }
471 }
472
473 /* now walk specnode->child matching up with dirnode */
474 for (curnode = specnode->child; curnode != NULL;
475 curnode = curnode->next) {
476 if (debug & DEBUG_APPLY_SPECENTRY)
477 printf("apply_specdir: spec %s\n",
478 curnode->name);
479 for (curfsnode = dirnode->next; curfsnode != NULL;
480 curfsnode = curfsnode->next) {
481 #if 0 /* too verbose for now */
482 if (debug & DEBUG_APPLY_SPECENTRY)
483 printf("apply_specdir: dirent %s\n",
484 curfsnode->name);
485 #endif
486 if (strcmp(curnode->name, curfsnode->name) == 0)
487 break;
488 }
489 if ((size_t)snprintf(path, sizeof(path), "%s/%s",
490 dir, curnode->name) >= sizeof(path))
491 errx(EXIT_FAILURE, "Pathname too long.");
492 if (curfsnode == NULL) { /* need new entry */
493 struct stat stbuf;
494
495 /*
496 * don't add optional spec entries
497 * that lack an existing fs entry
498 */
499 if ((curnode->flags & F_OPT) &&
500 lstat(path, &stbuf) == -1)
501 continue;
502
503 /* check that enough info is provided */
504 #define NODETEST(t, m) \
505 if (!(t)) \
506 errx(EXIT_FAILURE, \
507 "`%s': %s not provided", path, m)
508 NODETEST(curnode->flags & F_TYPE, "type");
509 NODETEST(curnode->flags & F_MODE, "mode");
510 /* XXX: require F_TIME ? */
511 NODETEST(curnode->flags & F_GID ||
512 curnode->flags & F_GNAME, "group");
513 NODETEST(curnode->flags & F_UID ||
514 curnode->flags & F_UNAME, "user");
515 if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
516 NODETEST(curnode->flags & F_DEV,
517 "device number");
518 #undef NODETEST
519
520 if (debug & DEBUG_APPLY_SPECFILE)
521 printf("apply_specdir: adding %s\n",
522 curnode->name);
523 /* build minimal fsnode */
524 memset(&stbuf, 0, sizeof(stbuf));
525 stbuf.st_mode = nodetoino(curnode->type);
526 stbuf.st_nlink = 1;
527 stbuf.st_mtime = stbuf.st_atime =
528 stbuf.st_ctime = start_time.tv_sec;
529 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
530 stbuf.st_mtimensec = stbuf.st_atimensec =
531 stbuf.st_ctimensec = start_time.tv_nsec;
532 #endif
533 curfsnode = create_fsnode(".", ".", curnode->name,
534 &stbuf);
535 curfsnode->parent = dirnode->parent;
536 curfsnode->first = dirnode;
537 curfsnode->next = dirnode->next;
538 dirnode->next = curfsnode;
539 if (curfsnode->type == S_IFDIR) {
540 /* for dirs, make "." entry as well */
541 curfsnode->child = create_fsnode(".", ".", ".",
542 &stbuf);
543 curfsnode->child->parent = curfsnode;
544 curfsnode->child->first = curfsnode->child;
545 }
546 if (curfsnode->type == S_IFLNK) {
547 assert(curnode->slink != NULL);
548 /* for symlinks, copy the target */
549 curfsnode->symlink = estrdup(curnode->slink);
550 }
551 }
552 apply_specentry(dir, curnode, curfsnode);
553 if (curnode->type == F_DIR) {
554 if (curfsnode->type != S_IFDIR)
555 errx(EXIT_FAILURE,
556 "`%s' is not a directory", path);
557 assert (curfsnode->child != NULL);
558 apply_specdir(path, curnode, curfsnode->child, speconly);
559 }
560 }
561 }
562
563 static void
564 apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
565 {
566
567 assert(specnode != NULL);
568 assert(dirnode != NULL);
569
570 if (nodetoino(specnode->type) != dirnode->type)
571 errx(EXIT_FAILURE,
572 "`%s/%s' type mismatch: specfile %s, tree %s",
573 dir, specnode->name, inode_type(nodetoino(specnode->type)),
574 inode_type(dirnode->type));
575
576 if (debug & DEBUG_APPLY_SPECENTRY)
577 printf("apply_specentry: %s/%s\n", dir, dirnode->name);
578
579 #define ASEPRINT(t, b, o, n) \
580 if (debug & DEBUG_APPLY_SPECENTRY) \
581 printf("\t\t\tchanging %s from " b " to " b "\n", \
582 t, o, n)
583
584 if (specnode->flags & (F_GID | F_GNAME)) {
585 ASEPRINT("gid", "%d",
586 dirnode->inode->st.st_gid, specnode->st_gid);
587 dirnode->inode->st.st_gid = specnode->st_gid;
588 }
589 if (specnode->flags & F_MODE) {
590 ASEPRINT("mode", "%#o",
591 dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
592 dirnode->inode->st.st_mode &= ~ALLPERMS;
593 dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
594 }
595 /* XXX: ignoring F_NLINK for now */
596 if (specnode->flags & F_SIZE) {
597 ASEPRINT("size", "%lld",
598 (long long)dirnode->inode->st.st_size,
599 (long long)specnode->st_size);
600 dirnode->inode->st.st_size = specnode->st_size;
601 }
602 if (specnode->flags & F_SLINK) {
603 assert(dirnode->symlink != NULL);
604 assert(specnode->slink != NULL);
605 ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
606 free(dirnode->symlink);
607 dirnode->symlink = estrdup(specnode->slink);
608 }
609 if (specnode->flags & F_TIME) {
610 ASEPRINT("time", "%ld",
611 (long)dirnode->inode->st.st_mtime,
612 (long)specnode->st_mtimespec.tv_sec);
613 dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec;
614 dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec;
615 dirnode->inode->st.st_ctime = start_time.tv_sec;
616 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
617 dirnode->inode->st.st_mtimensec = specnode->st_mtimespec.tv_nsec;
618 dirnode->inode->st.st_atimensec = specnode->st_mtimespec.tv_nsec;
619 dirnode->inode->st.st_ctimensec = start_time.tv_nsec;
620 #endif
621 }
622 if (specnode->flags & (F_UID | F_UNAME)) {
623 ASEPRINT("uid", "%d",
624 dirnode->inode->st.st_uid, specnode->st_uid);
625 dirnode->inode->st.st_uid = specnode->st_uid;
626 }
627 #if HAVE_STRUCT_STAT_ST_FLAGS
628 if (specnode->flags & F_FLAGS) {
629 ASEPRINT("flags", "%#lX",
630 (unsigned long)dirnode->inode->st.st_flags,
631 (unsigned long)specnode->st_flags);
632 dirnode->inode->st.st_flags = specnode->st_flags;
633 }
634 #endif
635 if (specnode->flags & F_DEV) {
636 ASEPRINT("rdev", "%#llx",
637 (unsigned long long)dirnode->inode->st.st_rdev,
638 (unsigned long long)specnode->st_rdev);
639 dirnode->inode->st.st_rdev = specnode->st_rdev;
640 }
641 #undef ASEPRINT
642
643 dirnode->flags |= FSNODE_F_HASSPEC;
644 }
645
646
647 /*
648 * dump_fsnodes --
649 * dump the fsnodes from `cur'
650 */
651 void
652 dump_fsnodes(fsnode *root)
653 {
654 fsnode *cur;
655 char path[MAXPATHLEN + 1];
656
657 printf("dump_fsnodes: %s %p\n", root->path, root);
658 for (cur = root; cur != NULL; cur = cur->next) {
659 if (snprintf(path, sizeof(path), "%s/%s", cur->path,
660 cur->name) >= (int)sizeof(path))
661 errx(EXIT_FAILURE, "Pathname too long.");
662
663 if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
664 printf("cur=%8p parent=%8p first=%8p ",
665 cur, cur->parent, cur->first);
666 printf("%7s: %s", inode_type(cur->type), path);
667 if (S_ISLNK(cur->type)) {
668 assert(cur->symlink != NULL);
669 printf(" -> %s", cur->symlink);
670 } else {
671 assert (cur->symlink == NULL);
672 }
673 if (cur->inode->nlink > 1)
674 printf(", nlinks=%d", cur->inode->nlink);
675 putchar('\n');
676
677 if (cur->child) {
678 assert (cur->type == S_IFDIR);
679 dump_fsnodes(cur->child);
680 }
681 }
682 printf("dump_fsnodes: finished %s/%s\n", root->path, root->name);
683 }
684
685
686 /*
687 * inode_type --
688 * for a given inode type `mode', return a descriptive string.
689 * for most cases, uses inotype() from mtree/misc.c
690 */
691 const char *
692 inode_type(mode_t mode)
693 {
694
695 if (S_ISLNK(mode))
696 return ("symlink"); /* inotype() returns "link"... */
697 return (inotype(mode));
698 }
699
700
701 /*
702 * link_check --
703 * return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
704 * otherwise add `entry' to table and return NULL
705 */
706 /* This was borrowed from du.c and tweaked to keep an fsnode
707 * pointer instead. -- dbj (at) netbsd.org
708 */
709 static fsinode *
710 link_check(fsinode *entry)
711 {
712 static struct entry {
713 fsinode *data;
714 } *htable;
715 static int htshift; /* log(allocated size) */
716 static int htmask; /* allocated size - 1 */
717 static int htused; /* 2*number of insertions */
718 int h, h2;
719 uint64_t tmp;
720 /* this constant is (1<<64)/((1+sqrt(5))/2)
721 * aka (word size)/(golden ratio)
722 */
723 const uint64_t HTCONST = 11400714819323198485ULL;
724 const int HTBITS = 64;
725
726 /* Never store zero in hashtable */
727 assert(entry);
728
729 /* Extend hash table if necessary, keep load under 0.5 */
730 if (htused<<1 >= htmask) {
731 struct entry *ohtable;
732
733 if (!htable)
734 htshift = 10; /* starting hashtable size */
735 else
736 htshift++; /* exponential hashtable growth */
737
738 htmask = (1 << htshift) - 1;
739 htused = 0;
740
741 ohtable = htable;
742 htable = ecalloc(htmask+1, sizeof(*htable));
743 /* populate newly allocated hashtable */
744 if (ohtable) {
745 int i;
746 for (i = 0; i <= htmask>>1; i++)
747 if (ohtable[i].data)
748 link_check(ohtable[i].data);
749 free(ohtable);
750 }
751 }
752
753 /* multiplicative hashing */
754 tmp = entry->st.st_dev;
755 tmp <<= HTBITS>>1;
756 tmp |= entry->st.st_ino;
757 tmp *= HTCONST;
758 h = tmp >> (HTBITS - htshift);
759 h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
760
761 /* open address hashtable search with double hash probing */
762 while (htable[h].data) {
763 if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
764 (htable[h].data->st.st_dev == entry->st.st_dev)) {
765 return htable[h].data;
766 }
767 h = (h + h2) & htmask;
768 }
769
770 /* Insert the current entry into hashtable */
771 htable[h].data = entry;
772 htused++;
773 return NULL;
774 }
775