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