xref: /netbsd-src/usr.sbin/makefs/walk.c (revision b79065bd09e2dc7b7c47c1e9ae5a2d0c55578405)
1*b79065bdSchristos /*	$NetBSD: walk.c,v 1.41 2024/10/18 23:28:03 christos Exp $	*/
26325773eSlukem 
36325773eSlukem /*
41226af2bSlukem  * Copyright (c) 2001 Wasabi Systems, Inc.
56325773eSlukem  * All rights reserved.
66325773eSlukem  *
76325773eSlukem  * Written by Luke Mewburn for Wasabi Systems, Inc.
86325773eSlukem  *
96325773eSlukem  * Redistribution and use in source and binary forms, with or without
106325773eSlukem  * modification, are permitted provided that the following conditions
116325773eSlukem  * are met:
126325773eSlukem  * 1. Redistributions of source code must retain the above copyright
136325773eSlukem  *    notice, this list of conditions and the following disclaimer.
146325773eSlukem  * 2. Redistributions in binary form must reproduce the above copyright
156325773eSlukem  *    notice, this list of conditions and the following disclaimer in the
166325773eSlukem  *    documentation and/or other materials provided with the distribution.
176325773eSlukem  * 3. All advertising materials mentioning features or use of this software
186325773eSlukem  *    must display the following acknowledgement:
196325773eSlukem  *      This product includes software developed for the NetBSD Project by
206325773eSlukem  *      Wasabi Systems, Inc.
216325773eSlukem  * 4. The name of Wasabi Systems, Inc. may not be used to endorse
226325773eSlukem  *    or promote products derived from this software without specific prior
236325773eSlukem  *    written permission.
246325773eSlukem  *
256325773eSlukem  * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
266325773eSlukem  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
276325773eSlukem  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
286325773eSlukem  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
296325773eSlukem  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
306325773eSlukem  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
316325773eSlukem  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
326325773eSlukem  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
336325773eSlukem  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
346325773eSlukem  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
356325773eSlukem  * POSSIBILITY OF SUCH DAMAGE.
366325773eSlukem  */
376325773eSlukem 
38b2f78261Sjmc #if HAVE_NBTOOL_CONFIG_H
39b2f78261Sjmc #include "nbtool_config.h"
40b2f78261Sjmc #endif
41b2f78261Sjmc 
42cafb53fcSlukem #include <sys/cdefs.h>
439fbd8888Stv #if defined(__RCSID) && !defined(__lint)
44*b79065bdSchristos __RCSID("$NetBSD: walk.c,v 1.41 2024/10/18 23:28:03 christos Exp $");
45cafb53fcSlukem #endif	/* !__lint */
46cafb53fcSlukem 
476325773eSlukem #include <sys/param.h>
48e4989541Schristos #include <sys/stat.h>
496325773eSlukem 
506325773eSlukem #include <assert.h>
516325773eSlukem #include <errno.h>
526325773eSlukem #include <fcntl.h>
536325773eSlukem #include <stdio.h>
546325773eSlukem #include <dirent.h>
556325773eSlukem #include <stdlib.h>
566325773eSlukem #include <string.h>
576325773eSlukem #include <unistd.h>
58e4989541Schristos #include <util.h>
596325773eSlukem 
606325773eSlukem #include "makefs.h"
616325773eSlukem #include "mtree.h"
626325773eSlukem 
63d0c4ff45Sdbj static	void	 apply_specdir(const char *, NODE *, fsnode *, int);
646325773eSlukem static	void	 apply_specentry(const char *, NODE *, fsnode *);
65f1cc0951Schristos static	fsnode	*create_fsnode(const char *, const char *, const char *,
66f1cc0951Schristos 			       struct stat *);
67ebbf3dddSlukem static	fsinode	*link_check(fsinode *);
680fe82aa4Schristos static size_t missing = 0;
696325773eSlukem 
70c50dd23eSchristos /*
71c50dd23eSchristos  * fsnode_cmp --
72c50dd23eSchristos  *	This function is used by `qsort` so sort one directory's
73c50dd23eSchristos  *	entries.  `.` is always first, sollowed by anything else
74c50dd23eSchristos  *	as compared by `strcmp()`.
75c50dd23eSchristos  */
76c50dd23eSchristos static int
77a5d2beeeSchristos fsnode_cmp(const void *vleft, const void *vright)
78c50dd23eSchristos {
79a5d2beeeSchristos 	const fsnode * const *left  = vleft;
80a5d2beeeSchristos 	const fsnode * const *right = vright;
81a5d2beeeSchristos 	const char *lname = (*left)->name, *rname = (*right)->name;
82c50dd23eSchristos 
83a5d2beeeSchristos 	if (strcmp(lname, ".") == 0)
84c50dd23eSchristos 		return -1;
85a5d2beeeSchristos 	if (strcmp(rname, ".") == 0)
86c50dd23eSchristos 		return 1;
87a5d2beeeSchristos 	return strcmp(lname, rname);
88c50dd23eSchristos }
896325773eSlukem 
90c7e1aa68Schristos static fsnode *
91c7e1aa68Schristos fsnode_sort(fsnode *first, const char *root, const char *dir)
92c7e1aa68Schristos {
93c7e1aa68Schristos 	fsnode **list, **listptr;
94c7e1aa68Schristos 	size_t num = 0;
95c7e1aa68Schristos 
96c7e1aa68Schristos 	for (fsnode *tmp = first; tmp; tmp = tmp->next, num++) {
97c7e1aa68Schristos 		if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
98514871bdSchristos 			printf("%s: pre sort: %s %s %s\n",
99514871bdSchristos 			    __func__, root, dir, tmp->name);
100c7e1aa68Schristos 	}
101c7e1aa68Schristos 
102c7e1aa68Schristos 	list = listptr = ecalloc(num, sizeof(*list));
103c7e1aa68Schristos 	for (fsnode *tmp = first; tmp; tmp = tmp->next)
104c7e1aa68Schristos 		*listptr++ = tmp;
105c7e1aa68Schristos 
106c7e1aa68Schristos 	qsort(list, num, sizeof(*list), fsnode_cmp);
107c7e1aa68Schristos 
108c7e1aa68Schristos 	for (size_t i = 0; i < num - 1; ++i)
109c7e1aa68Schristos 		list[i]->next = list[i + 1];
110c7e1aa68Schristos 	list[num - 1]->next = NULL;
111c7e1aa68Schristos 	first = list[0];
112c7e1aa68Schristos 	assert(strcmp(first->name, ".") == 0);
113c7e1aa68Schristos 	free(list);
114c7e1aa68Schristos 	if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
115c7e1aa68Schristos 		for (fsnode *tmp = first; tmp; tmp = tmp->next)
116514871bdSchristos 			printf("%s: post sort: %s %s %s\n",
117514871bdSchristos 			    __func__, root, dir, tmp->name);
118c7e1aa68Schristos 
119c7e1aa68Schristos 	return first;
120c7e1aa68Schristos }
121c7e1aa68Schristos 
1226325773eSlukem /*
123*b79065bdSchristos  * join current entry with the list. Return the current entry to replace
124*b79065bdSchristos  * in cur, and 1 if it is a directory and we need to add or 0 if we need
125*b79065bdSchristos  * to replace it.
126*b79065bdSchristos  */
127*b79065bdSchristos static int
128*b79065bdSchristos fsnode_join(fsnode **curp, fsnode *join, fsnode *last, const char *path,
129*b79065bdSchristos     const char *name, const struct stat *st, int replace)
130*b79065bdSchristos {
131*b79065bdSchristos 	fsnode *cur;
132*b79065bdSchristos 
133*b79065bdSchristos 	/* Look for the entry to replace by name */
134*b79065bdSchristos 	cur = join->next;
135*b79065bdSchristos 	for (;;) {
136*b79065bdSchristos 		if (cur == NULL || strcmp(cur->name, name) == 0)
137*b79065bdSchristos 			break;
138*b79065bdSchristos 		if (cur == last) {
139*b79065bdSchristos 			cur = NULL;
140*b79065bdSchristos 			break;
141*b79065bdSchristos 		}
142*b79065bdSchristos 		cur = cur->next;
143*b79065bdSchristos 	}
144*b79065bdSchristos 	if (cur == NULL) {
145*b79065bdSchristos 		/* Not found */
146*b79065bdSchristos 		*curp = NULL;
147*b79065bdSchristos 		return 0;
148*b79065bdSchristos 	}
149*b79065bdSchristos 	if (S_ISDIR(cur->type) && S_ISDIR(st->st_mode)) {
150*b79065bdSchristos 		/*
151*b79065bdSchristos                  * both the entry to join and this entry are directories
152*b79065bdSchristos 		 * need to merge the two directories
153*b79065bdSchristos 		 */
154*b79065bdSchristos 		if (debug & DEBUG_WALK_DIR_NODE)
155*b79065bdSchristos 			printf("%s: merging %s with %p\n",
156*b79065bdSchristos 			    __func__, path, cur->child);
157*b79065bdSchristos 		*curp = cur;
158*b79065bdSchristos 		return 1;
159*b79065bdSchristos 	}
160*b79065bdSchristos 	if (!replace) {
161*b79065bdSchristos 		/*
162*b79065bdSchristos 		 * if they are not both directories and replace is not
163*b79065bdSchristos 		 * specified, bail out
164*b79065bdSchristos 		 */
165*b79065bdSchristos 		errx(EXIT_FAILURE, "Can't merge %s `%s' with existing %s",
166*b79065bdSchristos 		    inode_type(st->st_mode), path, inode_type(cur->type));
167*b79065bdSchristos 	}
168*b79065bdSchristos 
169*b79065bdSchristos 	if (debug & DEBUG_WALK_DIR_NODE)
170*b79065bdSchristos 		printf("%s: replacing %s %s\n",
171*b79065bdSchristos 		    __func__, inode_type(st->st_mode), path);
172*b79065bdSchristos 
173*b79065bdSchristos 	/* merge the join list */
174*b79065bdSchristos 	if (cur == join->next)
175*b79065bdSchristos 		join->next = cur->next;
176*b79065bdSchristos 	else {
177*b79065bdSchristos 		fsnode *p;
178*b79065bdSchristos 		for (p = join->next;
179*b79065bdSchristos 		    p->next != cur; p = p->next)
180*b79065bdSchristos 			continue;
181*b79065bdSchristos 		p->next = cur->next;
182*b79065bdSchristos 	}
183*b79065bdSchristos 	/* return the entry to be replaced */
184*b79065bdSchristos 	*curp = cur;
185*b79065bdSchristos 	return 0;
186*b79065bdSchristos }
187*b79065bdSchristos 
188*b79065bdSchristos /*
1896325773eSlukem  * walk_dir --
190f1cc0951Schristos  *	build a tree of fsnodes from `root' and `dir', with a parent
191f1cc0951Schristos  *	fsnode of `parent' (which may be NULL for the root of the tree).
192f1cc0951Schristos  *	append the tree to a fsnode of `join' if it is not NULL.
1936325773eSlukem  *	each "level" is a directory, with the "." entry guaranteed to be
1946325773eSlukem  *	at the start of the list, and without ".." entries.
1956325773eSlukem  */
1966325773eSlukem fsnode *
197c06c93b2Schristos walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join,
198ba42c78dSsimonb     int replace, int follow)
1996325773eSlukem {
200f1cc0951Schristos 	fsnode		*first, *cur, *prev, *last;
2016325773eSlukem 	DIR		*dirp;
2026325773eSlukem 	struct dirent	*dent;
2036325773eSlukem 	char		path[MAXPATHLEN + 1];
2046325773eSlukem 	struct stat	stbuf;
205f1cc0951Schristos 	char		*name, *rp;
206f1cc0951Schristos 	int		dot, len;
2076325773eSlukem 
208f1cc0951Schristos 	assert(root != NULL);
2096325773eSlukem 	assert(dir != NULL);
2106325773eSlukem 
211f1cc0951Schristos 	len = snprintf(path, sizeof(path), "%s/%s", root, dir);
212c7e1aa68Schristos 	if ((size_t)len >= sizeof(path))
213ec5d1d82Stsutsui 		errx(EXIT_FAILURE, "Pathname too long.");
2146325773eSlukem 	if (debug & DEBUG_WALK_DIR)
215514871bdSchristos 		printf("%s: %s %p\n", __func__, path, parent);
216f1cc0951Schristos 	if ((dirp = opendir(path)) == NULL)
217ec5d1d82Stsutsui 		err(EXIT_FAILURE, "Can't opendir `%s'", path);
218f1cc0951Schristos 	rp = path + strlen(root) + 1;
219f1cc0951Schristos 	if (join != NULL) {
220f1cc0951Schristos 		first = cur = join;
221f1cc0951Schristos 		while (cur->next != NULL)
222f1cc0951Schristos 			cur = cur->next;
223f1cc0951Schristos 		prev = last = cur;
224f1cc0951Schristos 	} else
225f1cc0951Schristos 		last = first = prev = NULL;
2266325773eSlukem 	while ((dent = readdir(dirp)) != NULL) {
227f1cc0951Schristos 		name = dent->d_name;
228f1cc0951Schristos 		dot = 0;
229f1cc0951Schristos 		if (name[0] == '.')
230f1cc0951Schristos 			switch (name[1]) {
231f1cc0951Schristos 			case '\0':	/* "." */
232f1cc0951Schristos 				if (join != NULL)
2336325773eSlukem 					continue;
234f1cc0951Schristos 				dot = 1;
235f1cc0951Schristos 				break;
236f1cc0951Schristos 			case '.':	/* ".." */
237f1cc0951Schristos 				if (name[2] == '\0')
238f1cc0951Schristos 					continue;
239f1cc0951Schristos 				/* FALLTHROUGH */
240f1cc0951Schristos 			default:
241f1cc0951Schristos 				dot = 0;
242f1cc0951Schristos 			}
2436325773eSlukem 		if (debug & DEBUG_WALK_DIR_NODE)
244514871bdSchristos 			printf("%s: scanning %s/%s/%s\n",
245514871bdSchristos 			    __func__, root, dir, name);
246f1cc0951Schristos 		if (snprintf(path + len, sizeof(path) - len, "/%s", name) >=
247f1cc0951Schristos 		    (int)sizeof(path) - len)
248ec5d1d82Stsutsui 			errx(EXIT_FAILURE, "Pathname too long.");
249ba42c78dSsimonb 		if (follow) {
250ba42c78dSsimonb 			if (stat(path, &stbuf) == -1)
251ec5d1d82Stsutsui 				err(EXIT_FAILURE, "Can't stat `%s'", path);
252ba42c78dSsimonb 		} else {
2536325773eSlukem 			if (lstat(path, &stbuf) == -1)
254ec5d1d82Stsutsui 				err(EXIT_FAILURE, "Can't lstat `%s'", path);
255c7e1aa68Schristos 			/*
256c7e1aa68Schristos 			 * Symlink permission bits vary between filesystems/OSs
257c7e1aa68Schristos 			 * (ie. 0755 on FFS/NetBSD, 0777 for ext[234]/Linux),
258c7e1aa68Schristos 			 * force them to 0755.
259c7e1aa68Schristos 			 */
260c7526c4eSchristos 			if (S_ISLNK(stbuf.st_mode)) {
261c7526c4eSchristos 				stbuf.st_mode &= ~(S_IRWXU | S_IRWXG | S_IRWXO);
262c7526c4eSchristos 				stbuf.st_mode |= S_IRWXU
263c7526c4eSchristos 				    | S_IRGRP | S_IXGRP
264c7526c4eSchristos 				    | S_IROTH | S_IXOTH;
265c7526c4eSchristos 			}
266ba42c78dSsimonb 		}
267b2f78261Sjmc #ifdef S_ISSOCK
2686325773eSlukem 		if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
2696325773eSlukem 			if (debug & DEBUG_WALK_DIR_NODE)
270*b79065bdSchristos 				printf("%s: skipping socket %s\n", __func__,
271*b79065bdSchristos 				     path);
2726325773eSlukem 			continue;
2736325773eSlukem 		}
274b2f78261Sjmc #endif
2756325773eSlukem 
276f1cc0951Schristos 		if (join != NULL) {
277*b79065bdSchristos 			if (fsnode_join(&cur, join, last, path, name, &stbuf,
278*b79065bdSchristos 			    replace)) {
279f1cc0951Schristos 				cur->child = walk_dir(root, rp, cur,
280ba42c78dSsimonb 				    cur->child, replace, follow);
281f1cc0951Schristos 				continue;
282*b79065bdSchristos 			} else if (cur) {
283*b79065bdSchristos 				if (prev == cur) {
284*b79065bdSchristos 					fsnode *p = join;
285*b79065bdSchristos 					while (p->next != NULL)
286*b79065bdSchristos 					     p = p->next;
287*b79065bdSchristos 					prev = p;
288f1cc0951Schristos 				}
289*b79065bdSchristos 				free(cur->name);
290*b79065bdSchristos 				free(cur->path);
291c06c93b2Schristos 				free(cur);
292c06c93b2Schristos 			}
293f1cc0951Schristos 		}
294f1cc0951Schristos 
295f1cc0951Schristos 		cur = create_fsnode(root, dir, name, &stbuf);
2966325773eSlukem 		cur->parent = parent;
297f1cc0951Schristos 		if (dot) {
2986325773eSlukem 				/* ensure "." is at the start of the list */
2996325773eSlukem 			cur->next = first;
3006325773eSlukem 			first = cur;
3016325773eSlukem 			if (! prev)
3026325773eSlukem 				prev = cur;
303f1cc0951Schristos 			cur->first = first;
3046325773eSlukem 		} else {			/* not "." */
3056325773eSlukem 			if (prev)
3066325773eSlukem 				prev->next = cur;
3076325773eSlukem 			prev = cur;
3086325773eSlukem 			if (!first)
3096325773eSlukem 				first = cur;
310f1cc0951Schristos 			cur->first = first;
3116325773eSlukem 			if (S_ISDIR(cur->type)) {
312c06c93b2Schristos 				cur->child = walk_dir(root, rp, cur, NULL,
313ba42c78dSsimonb 				    replace, follow);
3146325773eSlukem 				continue;
3156325773eSlukem 			}
3166325773eSlukem 		}
317ebbf3dddSlukem 		if (stbuf.st_nlink > 1) {
318ebbf3dddSlukem 			fsinode	*curino;
319ebbf3dddSlukem 
320ebbf3dddSlukem 			curino = link_check(cur->inode);
321ebbf3dddSlukem 			if (curino != NULL) {
322ebbf3dddSlukem 				free(cur->inode);
323ebbf3dddSlukem 				cur->inode = curino;
324ebbf3dddSlukem 				cur->inode->nlink++;
3251ca1523dSdbj 				if (debug & DEBUG_WALK_DIR_LINKCHECK)
326514871bdSchristos 					printf("%s: link check found [%ju, %ju]\n",
327514871bdSchristos 					    __func__,
328a5d2beeeSchristos 					    (uintmax_t)curino->st.st_dev,
329a5d2beeeSchristos 					    (uintmax_t)curino->st.st_ino);
330ebbf3dddSlukem 			}
3316325773eSlukem 		}
3326325773eSlukem 		if (S_ISLNK(cur->type)) {
3336325773eSlukem 			char	slink[PATH_MAX+1];
334a5d2beeeSchristos 			ssize_t	llen;
3356325773eSlukem 
3362ab2b66eSitojun 			llen = readlink(path, slink, sizeof(slink) - 1);
3376325773eSlukem 			if (llen == -1)
338ec5d1d82Stsutsui 				err(EXIT_FAILURE, "Readlink `%s'", path);
3396325773eSlukem 			slink[llen] = '\0';
340e4989541Schristos 			cur->symlink = estrdup(slink);
3416325773eSlukem 		}
3426325773eSlukem 	}
343f1cc0951Schristos 	assert(first != NULL);
344f1cc0951Schristos 	if (join == NULL)
345f1cc0951Schristos 		for (cur = first->next; cur != NULL; cur = cur->next)
3466325773eSlukem 			cur->first = first;
3476325773eSlukem 	if (closedir(dirp) == -1)
348ec5d1d82Stsutsui 		err(EXIT_FAILURE, "Can't closedir `%s/%s'", root, dir);
349c50dd23eSchristos 
350c7e1aa68Schristos 	return fsnode_sort(first, root, dir);
3516325773eSlukem }
3526325773eSlukem 
3536325773eSlukem static fsnode *
354f1cc0951Schristos create_fsnode(const char *root, const char *path, const char *name,
355f1cc0951Schristos     struct stat *stbuf)
3566325773eSlukem {
3576325773eSlukem 	fsnode *cur;
3586325773eSlukem 
359e4989541Schristos 	cur = ecalloc(1, sizeof(*cur));
360e4989541Schristos 	cur->path = estrdup(path);
361e4989541Schristos 	cur->name = estrdup(name);
362e4989541Schristos 	cur->inode = ecalloc(1, sizeof(*cur->inode));
363f1cc0951Schristos 	cur->root = root;
364ebbf3dddSlukem 	cur->type = stbuf->st_mode & S_IFMT;
3655647b7dfSlukem 	cur->inode->nlink = 1;
3665647b7dfSlukem 	cur->inode->st = *stbuf;
367799916c0Schristos 	if (stampst.st_ino) {
368799916c0Schristos 		cur->inode->st.st_atime = stampst.st_atime;
369799916c0Schristos 		cur->inode->st.st_mtime = stampst.st_mtime;
370799916c0Schristos 		cur->inode->st.st_ctime = stampst.st_ctime;
371799916c0Schristos #if HAVE_STRUCT_STAT_ST_MTIMENSEC
372799916c0Schristos 		cur->inode->st.st_atimensec = stampst.st_atimensec;
373799916c0Schristos 		cur->inode->st.st_mtimensec = stampst.st_mtimensec;
374799916c0Schristos 		cur->inode->st.st_ctimensec = stampst.st_ctimensec;
375799916c0Schristos #endif
376799916c0Schristos #if HAVE_STRUCT_STAT_BIRTHTIME
377799916c0Schristos 		cur->inode->st.st_birthtime = stampst.st_birthtime;
378799916c0Schristos 		cur->inode->st.st_birthtimensec = stampst.st_birthtimensec;
379799916c0Schristos #endif
380799916c0Schristos 	}
381ebbf3dddSlukem 	return (cur);
382ebbf3dddSlukem }
3836325773eSlukem 
3846325773eSlukem /*
3850e392af9Sdbj  * free_fsnodes --
3860e392af9Sdbj  *	Removes node from tree and frees it and all of
387374cb9beSwiz  *   its descendents.
3880e392af9Sdbj  */
3890e392af9Sdbj void
3900e392af9Sdbj free_fsnodes(fsnode *node)
3910e392af9Sdbj {
3920e392af9Sdbj 	fsnode	*cur, *next;
3930e392af9Sdbj 
3940e392af9Sdbj 	assert(node != NULL);
3950e392af9Sdbj 
3960e392af9Sdbj 	/* for ".", start with actual parent node */
3970e392af9Sdbj 	if (node->first == node) {
3980e392af9Sdbj 		assert(node->name[0] == '.' && node->name[1] == '\0');
3990e392af9Sdbj 		if (node->parent) {
4000e392af9Sdbj 			assert(node->parent->child == node);
4010e392af9Sdbj 			node = node->parent;
4020e392af9Sdbj 		}
4030e392af9Sdbj 	}
4040e392af9Sdbj 
4050e392af9Sdbj 	/* Find ourselves in our sibling list and unlink */
4060e392af9Sdbj 	if (node->first != node) {
4070e392af9Sdbj 		for (cur = node->first; cur->next; cur = cur->next) {
4080e392af9Sdbj 			if (cur->next == node) {
4090e392af9Sdbj 				cur->next = node->next;
4100e392af9Sdbj 				node->next = NULL;
4110e392af9Sdbj 				break;
4120e392af9Sdbj 			}
4130e392af9Sdbj 		}
4140e392af9Sdbj 	}
4150e392af9Sdbj 
4160e392af9Sdbj 	for (cur = node; cur != NULL; cur = next) {
4170e392af9Sdbj 		next = cur->next;
4180e392af9Sdbj 		if (cur->child) {
4190e392af9Sdbj 			cur->child->parent = NULL;
4200e392af9Sdbj 			free_fsnodes(cur->child);
4210e392af9Sdbj 		}
4220e392af9Sdbj 		if (cur->inode->nlink-- == 1)
4230e392af9Sdbj 			free(cur->inode);
4240e392af9Sdbj 		if (cur->symlink)
4250e392af9Sdbj 			free(cur->symlink);
426f1cc0951Schristos 		free(cur->path);
4270e392af9Sdbj 		free(cur->name);
4280e392af9Sdbj 		free(cur);
4290e392af9Sdbj 	}
4300e392af9Sdbj }
4310e392af9Sdbj 
4320e392af9Sdbj /*
4336325773eSlukem  * apply_specfile --
4346325773eSlukem  *	read in the mtree(8) specfile, and apply it to the tree
4356325773eSlukem  *	at dir,parent. parameters in parent on equivalent types
4366325773eSlukem  *	will be changed to those found in specfile, and missing
4376325773eSlukem  *	entries will be added.
4386325773eSlukem  */
4396325773eSlukem void
440d0c4ff45Sdbj apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
4416325773eSlukem {
4426325773eSlukem 	struct timeval	 start;
4436325773eSlukem 	FILE	*fp;
4446325773eSlukem 	NODE	*root;
4456325773eSlukem 
4466325773eSlukem 	assert(specfile != NULL);
4476325773eSlukem 	assert(parent != NULL);
4486325773eSlukem 
4496325773eSlukem 	if (debug & DEBUG_APPLY_SPECFILE)
450514871bdSchristos 		printf("%s: %s, %s %p\n", __func__, specfile, dir, parent);
4516325773eSlukem 
4526325773eSlukem 				/* read in the specfile */
4536325773eSlukem 	if ((fp = fopen(specfile, "r")) == NULL)
454ec5d1d82Stsutsui 		err(EXIT_FAILURE, "Can't open `%s'", specfile);
4556325773eSlukem 	TIMER_START(start);
4566325773eSlukem 	root = spec(fp);
4576325773eSlukem 	TIMER_RESULTS(start, "spec");
4586325773eSlukem 	if (fclose(fp) == EOF)
459ec5d1d82Stsutsui 		err(EXIT_FAILURE, "Can't close `%s'", specfile);
4606325773eSlukem 
4616325773eSlukem 				/* perform some sanity checks */
4626325773eSlukem 	if (root == NULL)
463ec5d1d82Stsutsui 		errx(EXIT_FAILURE,
464ec5d1d82Stsutsui 		    "Specfile `%s' did not contain a tree", specfile);
4656325773eSlukem 	assert(strcmp(root->name, ".") == 0);
4666325773eSlukem 	assert(root->type == F_DIR);
4676325773eSlukem 
4686325773eSlukem 				/* merge in the changes */
469d0c4ff45Sdbj 	apply_specdir(dir, root, parent, speconly);
4702d7375ceSdbj 
4712d7375ceSdbj 	free_nodes(root);
4720fe82aa4Schristos 	if (missing)
4730fe82aa4Schristos 		errx(EXIT_FAILURE, "Add %zu missing entries in `%s'",
4740fe82aa4Schristos 		    missing, specfile);
4756325773eSlukem }
4766325773eSlukem 
4776325773eSlukem static void
478d0c4ff45Sdbj apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
4796325773eSlukem {
4806325773eSlukem 	char	 path[MAXPATHLEN + 1];
4816325773eSlukem 	NODE	*curnode;
4826325773eSlukem 	fsnode	*curfsnode;
4836325773eSlukem 
4846325773eSlukem 	assert(specnode != NULL);
4856325773eSlukem 	assert(dirnode != NULL);
4866325773eSlukem 
4876325773eSlukem 	if (debug & DEBUG_APPLY_SPECFILE)
488514871bdSchristos 		printf("%s: %s %p %p\n", __func__, dir, specnode, dirnode);
4896325773eSlukem 
4906325773eSlukem 	if (specnode->type != F_DIR)
491ec5d1d82Stsutsui 		errx(EXIT_FAILURE, "Specfile node `%s/%s' is not a directory",
4926325773eSlukem 		    dir, specnode->name);
4936325773eSlukem 	if (dirnode->type != S_IFDIR)
494ec5d1d82Stsutsui 		errx(EXIT_FAILURE, "Directory node `%s/%s' is not a directory",
4956325773eSlukem 		    dir, dirnode->name);
4966325773eSlukem 
4976325773eSlukem 	apply_specentry(dir, specnode, dirnode);
4986325773eSlukem 
499d0c4ff45Sdbj 	/* Remove any filesystem nodes not found in specfile */
500d0c4ff45Sdbj 	/* XXX inefficient.  This is O^2 in each dir and it would
501d0c4ff45Sdbj 	 * have been better never to have walked this part of the tree
502d0c4ff45Sdbj 	 * to begin with
503d0c4ff45Sdbj 	 */
504d0c4ff45Sdbj 	if (speconly) {
505d0c4ff45Sdbj 		fsnode *next;
506d0c4ff45Sdbj 		assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
507d0c4ff45Sdbj 		for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
508d0c4ff45Sdbj 			next = curfsnode->next;
509d0c4ff45Sdbj 			for (curnode = specnode->child; curnode != NULL;
510d0c4ff45Sdbj 			     curnode = curnode->next) {
511d0c4ff45Sdbj 				if (strcmp(curnode->name, curfsnode->name) == 0)
512d0c4ff45Sdbj 					break;
513d0c4ff45Sdbj 			}
514d0c4ff45Sdbj 			if (curnode == NULL) {
5150fe82aa4Schristos 				if (speconly > 1) {
5160fe82aa4Schristos 					warnx("missing specfile entry for %s/%s",
5170fe82aa4Schristos 					    dir, curfsnode->name);
5180fe82aa4Schristos 					missing++;
5190fe82aa4Schristos 				}
520d0c4ff45Sdbj 				if (debug & DEBUG_APPLY_SPECONLY) {
521514871bdSchristos 					printf("%s: trimming %s/%s %p\n",
522514871bdSchristos 					    __func__, dir, curfsnode->name,
523514871bdSchristos 					    curfsnode);
524d0c4ff45Sdbj 				}
525d0c4ff45Sdbj 				free_fsnodes(curfsnode);
526d0c4ff45Sdbj 			}
527d0c4ff45Sdbj 		}
528d0c4ff45Sdbj 	}
529d0c4ff45Sdbj 
5306325773eSlukem 			/* now walk specnode->child matching up with dirnode */
5316325773eSlukem 	for (curnode = specnode->child; curnode != NULL;
5326325773eSlukem 	    curnode = curnode->next) {
5336325773eSlukem 		if (debug & DEBUG_APPLY_SPECENTRY)
534514871bdSchristos 			printf("%s:  spec %s\n", __func__, curnode->name);
5356325773eSlukem 		for (curfsnode = dirnode->next; curfsnode != NULL;
5366325773eSlukem 		    curfsnode = curfsnode->next) {
537d1270f6dSlukem #if 0	/* too verbose for now */
5386325773eSlukem 			if (debug & DEBUG_APPLY_SPECENTRY)
539514871bdSchristos 				printf("%s: dirent %s\n", __func__,
5406325773eSlukem 				    curfsnode->name);
541d1270f6dSlukem #endif
5426325773eSlukem 			if (strcmp(curnode->name, curfsnode->name) == 0)
5436325773eSlukem 				break;
5446325773eSlukem 		}
545b825b96bSchristos 		if ((size_t)snprintf(path, sizeof(path), "%s/%s",
5462afe4e83Slukem 		    dir, curnode->name) >= sizeof(path))
547ec5d1d82Stsutsui 			errx(EXIT_FAILURE, "Pathname too long.");
5486325773eSlukem 		if (curfsnode == NULL) {	/* need new entry */
5496325773eSlukem 			struct stat	stbuf;
5506325773eSlukem 
5512afe4e83Slukem 					    /*
5522afe4e83Slukem 					     * don't add optional spec entries
5532afe4e83Slukem 					     * that lack an existing fs entry
5542afe4e83Slukem 					     */
5552afe4e83Slukem 			if ((curnode->flags & F_OPT) &&
5562afe4e83Slukem 			    lstat(path, &stbuf) == -1)
5572afe4e83Slukem 					continue;
5582afe4e83Slukem 
5596325773eSlukem 					/* check that enough info is provided */
5606325773eSlukem #define NODETEST(t, m)							\
5616325773eSlukem 			if (!(t))					\
562ec5d1d82Stsutsui 				errx(EXIT_FAILURE,			\
563ec5d1d82Stsutsui 				    "`%s': %s not provided", path, m)
5646325773eSlukem 			NODETEST(curnode->flags & F_TYPE, "type");
5656325773eSlukem 			NODETEST(curnode->flags & F_MODE, "mode");
5666325773eSlukem 				/* XXX: require F_TIME ? */
5676325773eSlukem 			NODETEST(curnode->flags & F_GID ||
5686325773eSlukem 			    curnode->flags & F_GNAME, "group");
5696325773eSlukem 			NODETEST(curnode->flags & F_UID ||
5706325773eSlukem 			    curnode->flags & F_UNAME, "user");
5716325773eSlukem 			if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
5726325773eSlukem 				NODETEST(curnode->flags & F_DEV,
5736325773eSlukem 				    "device number");
5746325773eSlukem #undef NODETEST
5756325773eSlukem 
5766325773eSlukem 			if (debug & DEBUG_APPLY_SPECFILE)
577514871bdSchristos 				printf("%s: adding %s\n", __func__, curnode->name);
5786325773eSlukem 					/* build minimal fsnode */
5796325773eSlukem 			memset(&stbuf, 0, sizeof(stbuf));
5806325773eSlukem 			stbuf.st_mode = nodetoino(curnode->type);
581ebbf3dddSlukem 			stbuf.st_nlink = 1;
5826325773eSlukem 			stbuf.st_mtime = stbuf.st_atime =
5836325773eSlukem 			    stbuf.st_ctime = start_time.tv_sec;
5849fbd8888Stv #if HAVE_STRUCT_STAT_ST_MTIMENSEC
5856325773eSlukem 			stbuf.st_mtimensec = stbuf.st_atimensec =
5866325773eSlukem 			    stbuf.st_ctimensec = start_time.tv_nsec;
5879fbd8888Stv #endif
588f1cc0951Schristos 			curfsnode = create_fsnode(".", ".", curnode->name,
589f1cc0951Schristos 			    &stbuf);
5906325773eSlukem 			curfsnode->parent = dirnode->parent;
5916325773eSlukem 			curfsnode->first = dirnode;
5926325773eSlukem 			curfsnode->next = dirnode->next;
5936325773eSlukem 			dirnode->next = curfsnode;
5946325773eSlukem 			if (curfsnode->type == S_IFDIR) {
5956325773eSlukem 					/* for dirs, make "." entry as well */
596f1cc0951Schristos 				curfsnode->child = create_fsnode(".", ".", ".",
597f1cc0951Schristos 				    &stbuf);
5986325773eSlukem 				curfsnode->child->parent = curfsnode;
5996325773eSlukem 				curfsnode->child->first = curfsnode->child;
6006325773eSlukem 			}
6016325773eSlukem 			if (curfsnode->type == S_IFLNK) {
602d1270f6dSlukem 				assert(curnode->slink != NULL);
6036325773eSlukem 					/* for symlinks, copy the target */
604e4989541Schristos 				curfsnode->symlink = estrdup(curnode->slink);
6056325773eSlukem 			}
6066325773eSlukem 		}
6076325773eSlukem 		apply_specentry(dir, curnode, curfsnode);
6086325773eSlukem 		if (curnode->type == F_DIR) {
6096325773eSlukem 			if (curfsnode->type != S_IFDIR)
610ec5d1d82Stsutsui 				errx(EXIT_FAILURE,
611ec5d1d82Stsutsui 				    "`%s' is not a directory", path);
6126325773eSlukem 			assert(curfsnode->child != NULL);
613d0c4ff45Sdbj 			apply_specdir(path, curnode, curfsnode->child, speconly);
6146325773eSlukem 		}
6156325773eSlukem 	}
6166325773eSlukem }
6176325773eSlukem 
6186325773eSlukem static void
6196325773eSlukem apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
6206325773eSlukem {
6216325773eSlukem 
6226325773eSlukem 	assert(specnode != NULL);
6236325773eSlukem 	assert(dirnode != NULL);
6246325773eSlukem 
6256325773eSlukem 	if (nodetoino(specnode->type) != dirnode->type)
626ec5d1d82Stsutsui 		errx(EXIT_FAILURE,
627ec5d1d82Stsutsui 		    "`%s/%s' type mismatch: specfile %s, tree %s",
6286325773eSlukem 		    dir, specnode->name, inode_type(nodetoino(specnode->type)),
6296325773eSlukem 		    inode_type(dirnode->type));
6306325773eSlukem 
6316325773eSlukem 	if (debug & DEBUG_APPLY_SPECENTRY)
632514871bdSchristos 		printf("%s: %s/%s\n", dir, __func__, dirnode->name);
6336325773eSlukem 
6346325773eSlukem #define ASEPRINT(t, b, o, n) \
6356325773eSlukem 		if (debug & DEBUG_APPLY_SPECENTRY) \
6366325773eSlukem 			printf("\t\t\tchanging %s from " b " to " b "\n", \
6376325773eSlukem 			    t, o, n)
6386325773eSlukem 
6396325773eSlukem 	if (specnode->flags & (F_GID | F_GNAME)) {
6406325773eSlukem 		ASEPRINT("gid", "%d",
641ebbf3dddSlukem 		    dirnode->inode->st.st_gid, specnode->st_gid);
642ebbf3dddSlukem 		dirnode->inode->st.st_gid = specnode->st_gid;
6436325773eSlukem 	}
6446325773eSlukem 	if (specnode->flags & F_MODE) {
6456325773eSlukem 		ASEPRINT("mode", "%#o",
646ebbf3dddSlukem 		    dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
647ebbf3dddSlukem 		dirnode->inode->st.st_mode &= ~ALLPERMS;
648ebbf3dddSlukem 		dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
6496325773eSlukem 	}
6506325773eSlukem 		/* XXX: ignoring F_NLINK for now */
6516325773eSlukem 	if (specnode->flags & F_SIZE) {
652a5d2beeeSchristos 		ASEPRINT("size", "%jd",
653a5d2beeeSchristos 		    (intmax_t)dirnode->inode->st.st_size,
654a5d2beeeSchristos 		    (intmax_t)specnode->st_size);
655ebbf3dddSlukem 		dirnode->inode->st.st_size = specnode->st_size;
6566325773eSlukem 	}
6576325773eSlukem 	if (specnode->flags & F_SLINK) {
6586325773eSlukem 		assert(dirnode->symlink != NULL);
6596325773eSlukem 		assert(specnode->slink != NULL);
6606325773eSlukem 		ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
6616325773eSlukem 		free(dirnode->symlink);
662e4989541Schristos 		dirnode->symlink = estrdup(specnode->slink);
6636325773eSlukem 	}
6646325773eSlukem 	if (specnode->flags & F_TIME) {
6656325773eSlukem 		ASEPRINT("time", "%ld",
666ebbf3dddSlukem 		    (long)dirnode->inode->st.st_mtime,
6679fbd8888Stv 		    (long)specnode->st_mtimespec.tv_sec);
6689fbd8888Stv 		dirnode->inode->st.st_mtime =		specnode->st_mtimespec.tv_sec;
6699fbd8888Stv 		dirnode->inode->st.st_atime =		specnode->st_mtimespec.tv_sec;
670ebbf3dddSlukem 		dirnode->inode->st.st_ctime =		start_time.tv_sec;
6719fbd8888Stv #if HAVE_STRUCT_STAT_ST_MTIMENSEC
672b2f78261Sjmc 		dirnode->inode->st.st_mtimensec =	specnode->st_mtimespec.tv_nsec;
673b2f78261Sjmc 		dirnode->inode->st.st_atimensec =	specnode->st_mtimespec.tv_nsec;
674ebbf3dddSlukem 		dirnode->inode->st.st_ctimensec =	start_time.tv_nsec;
6759fbd8888Stv #endif
6766325773eSlukem 	}
6776325773eSlukem 	if (specnode->flags & (F_UID | F_UNAME)) {
6786325773eSlukem 		ASEPRINT("uid", "%d",
679ebbf3dddSlukem 		    dirnode->inode->st.st_uid, specnode->st_uid);
680ebbf3dddSlukem 		dirnode->inode->st.st_uid = specnode->st_uid;
6816325773eSlukem 	}
6829fbd8888Stv #if HAVE_STRUCT_STAT_ST_FLAGS
6836325773eSlukem 	if (specnode->flags & F_FLAGS) {
6846325773eSlukem 		ASEPRINT("flags", "%#lX",
68585b406edSuwe 		    (unsigned long)dirnode->inode->st.st_flags,
68685b406edSuwe 		    (unsigned long)specnode->st_flags);
687a5d2beeeSchristos 		dirnode->inode->st.st_flags = (unsigned int)specnode->st_flags;
6886325773eSlukem 	}
6899fbd8888Stv #endif
6906325773eSlukem 	if (specnode->flags & F_DEV) {
691a5d2beeeSchristos 		ASEPRINT("rdev", "%#jx",
692a5d2beeeSchristos 		    (uintmax_t)dirnode->inode->st.st_rdev,
693a5d2beeeSchristos 		    (uintmax_t)specnode->st_rdev);
694ebbf3dddSlukem 		dirnode->inode->st.st_rdev = specnode->st_rdev;
6956325773eSlukem 	}
6966325773eSlukem #undef ASEPRINT
6971bcb9d76Sthorpej 
6981bcb9d76Sthorpej 	dirnode->flags |= FSNODE_F_HASSPEC;
6996325773eSlukem }
7006325773eSlukem 
7016325773eSlukem 
7026325773eSlukem /*
7036325773eSlukem  * dump_fsnodes --
704f1cc0951Schristos  *	dump the fsnodes from `cur'
7056325773eSlukem  */
7066325773eSlukem void
707f1cc0951Schristos dump_fsnodes(fsnode *root)
7086325773eSlukem {
7096325773eSlukem 	fsnode	*cur;
7106325773eSlukem 	char	path[MAXPATHLEN + 1];
7116325773eSlukem 
712514871bdSchristos 	printf("%s: %s %p\n", __func__, root->path, root);
7136325773eSlukem 	for (cur = root; cur != NULL; cur = cur->next) {
714f1cc0951Schristos 		if (snprintf(path, sizeof(path), "%s/%s", cur->path,
715f1cc0951Schristos 		    cur->name) >= (int)sizeof(path))
716ec5d1d82Stsutsui 			errx(EXIT_FAILURE, "Pathname too long.");
7176325773eSlukem 
7186325773eSlukem 		if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
719514871bdSchristos 			printf("%s: cur=%8p parent=%8p first=%8p ", __func__,
7206325773eSlukem 			    cur, cur->parent, cur->first);
7216325773eSlukem 		printf("%7s: %s", inode_type(cur->type), path);
7226325773eSlukem 		if (S_ISLNK(cur->type)) {
7236325773eSlukem 			assert(cur->symlink != NULL);
7246325773eSlukem 			printf(" -> %s", cur->symlink);
7256325773eSlukem 		} else {
7266325773eSlukem 			assert(cur->symlink == NULL);
7276325773eSlukem 		}
728ebbf3dddSlukem 		if (cur->inode->nlink > 1)
729ebbf3dddSlukem 			printf(", nlinks=%d", cur->inode->nlink);
7306325773eSlukem 		putchar('\n');
7316325773eSlukem 
7326325773eSlukem 		if (cur->child) {
7336325773eSlukem 			assert(cur->type == S_IFDIR);
734f1cc0951Schristos 			dump_fsnodes(cur->child);
7356325773eSlukem 		}
7366325773eSlukem 	}
737514871bdSchristos 	printf("%s: finished %s/%s\n", __func__, root->path, root->name);
7386325773eSlukem }
7396325773eSlukem 
7406325773eSlukem 
7416325773eSlukem /*
7426325773eSlukem  * inode_type --
7436325773eSlukem  *	for a given inode type `mode', return a descriptive string.
7446325773eSlukem  *	for most cases, uses inotype() from mtree/misc.c
7456325773eSlukem  */
7466325773eSlukem const char *
7476325773eSlukem inode_type(mode_t mode)
7486325773eSlukem {
7496325773eSlukem 
750d1270f6dSlukem 	if (S_ISLNK(mode))
7516325773eSlukem 		return ("symlink");	/* inotype() returns "link"...  */
7526325773eSlukem 	return (inotype(mode));
7536325773eSlukem }
7546325773eSlukem 
7556325773eSlukem 
7566325773eSlukem /*
7576325773eSlukem  * link_check --
7581ca1523dSdbj  *	return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
7596325773eSlukem  *	otherwise add `entry' to table and return NULL
7606325773eSlukem  */
7611ca1523dSdbj /* This was borrowed from du.c and tweaked to keep an fsnode
7621ca1523dSdbj  * pointer instead. -- dbj@netbsd.org
7631ca1523dSdbj  */
764ebbf3dddSlukem static fsinode *
765ebbf3dddSlukem link_check(fsinode *entry)
7666325773eSlukem {
7671ca1523dSdbj 	static struct entry {
7681ca1523dSdbj 		fsinode *data;
7691ca1523dSdbj 	} *htable;
770a5d2beeeSchristos 	static size_t htshift;  /* log(allocated size) */
771a5d2beeeSchristos 	static size_t htmask;   /* allocated size - 1 */
772a5d2beeeSchristos 	static size_t htused;   /* 2*number of insertions */
773a5d2beeeSchristos 	size_t h, h2;
7741ca1523dSdbj 	uint64_t tmp;
7751ca1523dSdbj 	/* this constant is (1<<64)/((1+sqrt(5))/2)
7761ca1523dSdbj 	 * aka (word size)/(golden ratio)
7771ca1523dSdbj 	 */
7781ca1523dSdbj 	const uint64_t HTCONST = 11400714819323198485ULL;
779a5d2beeeSchristos 	const size_t HTBITS = 64;
7806325773eSlukem 
7811ca1523dSdbj 	/* Never store zero in hashtable */
7821ca1523dSdbj 	assert(entry);
7836325773eSlukem 
7841ca1523dSdbj 	/* Extend hash table if necessary, keep load under 0.5 */
7851ca1523dSdbj 	if (htused<<1 >= htmask) {
7861ca1523dSdbj 		struct entry *ohtable;
7876325773eSlukem 
7881ca1523dSdbj 		if (!htable)
7891ca1523dSdbj 			htshift = 10;   /* starting hashtable size */
7901ca1523dSdbj 		else
7911ca1523dSdbj 			htshift++;   /* exponential hashtable growth */
7926325773eSlukem 
7931ca1523dSdbj 		htmask  = (1 << htshift) - 1;
7941ca1523dSdbj 		htused = 0;
7951ca1523dSdbj 
7961ca1523dSdbj 		ohtable = htable;
797e4989541Schristos 		htable = ecalloc(htmask+1, sizeof(*htable));
7981ca1523dSdbj 		/* populate newly allocated hashtable */
7991ca1523dSdbj 		if (ohtable) {
800a5d2beeeSchristos 			for (size_t i = 0; i <= htmask>>1; i++)
8011ca1523dSdbj 				if (ohtable[i].data)
8021ca1523dSdbj 					link_check(ohtable[i].data);
8031ca1523dSdbj 			free(ohtable);
8041ca1523dSdbj 		}
8051ca1523dSdbj 	}
8061ca1523dSdbj 
8071ca1523dSdbj 	/* multiplicative hashing */
8081ca1523dSdbj 	tmp = entry->st.st_dev;
8091ca1523dSdbj 	tmp <<= HTBITS>>1;
8101ca1523dSdbj 	tmp |=  entry->st.st_ino;
8111ca1523dSdbj 	tmp *= HTCONST;
8121ca1523dSdbj 	h  = tmp >> (HTBITS - htshift);
8131ca1523dSdbj 	h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
8141ca1523dSdbj 
8151ca1523dSdbj 	/* open address hashtable search with double hash probing */
8161ca1523dSdbj 	while (htable[h].data) {
8171ca1523dSdbj 		if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
8181ca1523dSdbj 		    (htable[h].data->st.st_dev == entry->st.st_dev)) {
8191ca1523dSdbj 			return htable[h].data;
8201ca1523dSdbj 		}
8211ca1523dSdbj 		h = (h + h2) & htmask;
8221ca1523dSdbj 	}
8231ca1523dSdbj 
8241ca1523dSdbj 	/* Insert the current entry into hashtable */
8251ca1523dSdbj 	htable[h].data = entry;
8261ca1523dSdbj 	htused++;
8271ca1523dSdbj 	return NULL;
8286325773eSlukem }
829