xref: /openbsd/usr.sbin/makefs/walk.c (revision 4cfece93)
1 /*	$OpenBSD: walk.c,v 1.9 2016/12/17 16:12:15 krw Exp $	*/
2 /*	$NetBSD: walk.c,v 1.29 2015/11/25 00:48:49 christos Exp $	*/
3 
4 /*
5  * Copyright (c) 2001 Wasabi Systems, Inc.
6  * All rights reserved.
7  *
8  * Written by Luke Mewburn for Wasabi Systems, Inc.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *      This product includes software developed for the NetBSD Project by
21  *      Wasabi Systems, Inc.
22  * 4. The name of Wasabi Systems, Inc. may not be used to endorse
23  *    or promote products derived from this software without specific prior
24  *    written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 
42 #include <assert.h>
43 #include <stdio.h>
44 #include <dirent.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 
49 #include "makefs.h"
50 
51 static	fsnode	*create_fsnode(const char *, const char *, const char *,
52 			       struct stat *);
53 static	fsinode	*link_check(fsinode *);
54 
55 
56 /*
57  * walk_dir --
58  *	build a tree of fsnodes from `root' and `dir', with a parent
59  *	fsnode of `parent' (which may be NULL for the root of the tree).
60  *	append the tree to a fsnode of `join' if it is not NULL.
61  *	each "level" is a directory, with the "." entry guaranteed to be
62  *	at the start of the list, and without ".." entries.
63  */
64 fsnode *
65 walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join)
66 {
67 	fsnode		*first, *cur, *prev, *last;
68 	DIR		*dirp;
69 	struct dirent	*dent;
70 	char		path[MAXPATHLEN + 1];
71 	struct stat	stbuf;
72 	char		*name, *rp;
73 	int		dot, len;
74 
75 	assert(root != NULL);
76 	assert(dir != NULL);
77 
78 	len = snprintf(path, sizeof(path), "%s/%s", root, dir);
79 	if (len >= (int)sizeof(path))
80 		errx(1, "Pathname too long.");
81 	if ((dirp = opendir(path)) == NULL)
82 		err(1, "Can't opendir `%s'", path);
83 	rp = path + strlen(root) + 1;
84 	if (join != NULL) {
85 		first = cur = join;
86 		while (cur->next != NULL)
87 			cur = cur->next;
88 		prev = last = cur;
89 	} else
90 		last = first = prev = NULL;
91 	while ((dent = readdir(dirp)) != NULL) {
92 		name = dent->d_name;
93 		dot = 0;
94 		if (name[0] == '.')
95 			switch (name[1]) {
96 			case '\0':	/* "." */
97 				if (join != NULL)
98 					continue;
99 				dot = 1;
100 				break;
101 			case '.':	/* ".." */
102 				if (name[2] == '\0')
103 					continue;
104 				/* FALLTHROUGH */
105 			default:
106 				dot = 0;
107 			}
108 		if (snprintf(path + len, sizeof(path) - len, "/%s", name) >=
109 		    (int)sizeof(path) - len)
110 			errx(1, "Pathname too long.");
111 		if (lstat(path, &stbuf) == -1)
112 			err(1, "Can't lstat `%s'", path);
113 		if (S_ISSOCK(stbuf.st_mode & S_IFMT))
114 			continue;
115 
116 		if (join != NULL) {
117 			cur = join->next;
118 			for (;;) {
119 				if (cur == NULL || strcmp(cur->name, name) == 0)
120 					break;
121 				if (cur == last) {
122 					cur = NULL;
123 					break;
124 				}
125 				cur = cur->next;
126 			}
127 			if (cur != NULL) {
128 				if (S_ISDIR(cur->type) &&
129 				    S_ISDIR(stbuf.st_mode)) {
130 					cur->child = walk_dir(root, rp, cur,
131 					    cur->child);
132 					continue;
133 				}
134 				errx(1, "Can't merge %s `%s' with "
135 				    "existing %s",
136 				    inode_type(stbuf.st_mode), path,
137 				    inode_type(cur->type));
138 			}
139 		}
140 
141 		cur = create_fsnode(root, dir, name, &stbuf);
142 		cur->parent = parent;
143 		if (dot) {
144 				/* ensure "." is at the start of the list */
145 			cur->next = first;
146 			first = cur;
147 			if (! prev)
148 				prev = cur;
149 			cur->first = first;
150 		} else {			/* not "." */
151 			if (prev)
152 				prev->next = cur;
153 			prev = cur;
154 			if (!first)
155 				first = cur;
156 			cur->first = first;
157 			if (S_ISDIR(cur->type)) {
158 				cur->child = walk_dir(root, rp, cur, NULL);
159 				continue;
160 			}
161 		}
162 		if (stbuf.st_nlink > 1) {
163 			fsinode	*curino;
164 
165 			curino = link_check(cur->inode);
166 			if (curino != NULL) {
167 				free(cur->inode);
168 				cur->inode = curino;
169 				cur->inode->nlink++;
170 			}
171 		}
172 		if (S_ISLNK(cur->type)) {
173 			char	slink[PATH_MAX+1];
174 			int	llen;
175 
176 			llen = readlink(path, slink, sizeof(slink) - 1);
177 			if (llen == -1)
178 				err(1, "Readlink `%s'", path);
179 			slink[llen] = '\0';
180 			cur->symlink = estrdup(slink);
181 		}
182 	}
183 	assert(first != NULL);
184 	if (join == NULL)
185 		for (cur = first->next; cur != NULL; cur = cur->next)
186 			cur->first = first;
187 	if (closedir(dirp) == -1)
188 		err(1, "Can't closedir `%s/%s'", root, dir);
189 	return (first);
190 }
191 
192 static fsnode *
193 create_fsnode(const char *root, const char *path, const char *name,
194     struct stat *stbuf)
195 {
196 	fsnode *cur;
197 
198 	cur = ecalloc(1, sizeof(*cur));
199 	cur->path = estrdup(path);
200 	cur->name = estrdup(name);
201 	cur->inode = ecalloc(1, sizeof(*cur->inode));
202 	cur->root = root;
203 	cur->type = stbuf->st_mode & S_IFMT;
204 	cur->inode->nlink = 1;
205 	cur->inode->st = *stbuf;
206 	if (Tflag) {
207 		cur->inode->st.st_atime = stampts;
208 		cur->inode->st.st_mtime = stampts;
209 		cur->inode->st.st_ctime = stampts;
210 		cur->inode->st.st_atimensec = 0;
211 		cur->inode->st.st_mtimensec = 0;
212 		cur->inode->st.st_ctimensec = 0;
213 	}
214 	return (cur);
215 }
216 
217 /*
218  * free_fsnodes --
219  *	Removes node from tree and frees it and all of
220  *   its decendents.
221  */
222 void
223 free_fsnodes(fsnode *node)
224 {
225 	fsnode	*cur, *next;
226 
227 	assert(node != NULL);
228 
229 	/* for ".", start with actual parent node */
230 	if (node->first == node) {
231 		assert(node->name[0] == '.' && node->name[1] == '\0');
232 		if (node->parent) {
233 			assert(node->parent->child == node);
234 			node = node->parent;
235 		}
236 	}
237 
238 	/* Find ourselves in our sibling list and unlink */
239 	if (node->first != node) {
240 		for (cur = node->first; cur->next; cur = cur->next) {
241 			if (cur->next == node) {
242 				cur->next = node->next;
243 				node->next = NULL;
244 				break;
245 			}
246 		}
247 	}
248 
249 	for (cur = node; cur != NULL; cur = next) {
250 		next = cur->next;
251 		if (cur->child) {
252 			cur->child->parent = NULL;
253 			free_fsnodes(cur->child);
254 		}
255 		if (cur->inode->nlink-- == 1)
256 			free(cur->inode);
257 		if (cur->symlink)
258 			free(cur->symlink);
259 		free(cur->path);
260 		free(cur->name);
261 		free(cur);
262 	}
263 }
264 
265 
266 /*
267  * inode_type --
268  *	for a given inode type `mode', return a descriptive string.
269  *	for most cases, uses inotype() from mtree/misc.c
270  */
271 const char *
272 inode_type(mode_t mode)
273 {
274 	switch (mode & S_IFMT) {
275 	case S_IFBLK:
276 		return ("block");
277 	case S_IFCHR:
278 		return ("char");
279 	case S_IFDIR:
280 		return ("dir");
281 	case S_IFIFO:
282 		return ("fifo");
283 	case S_IFREG:
284 		return ("file");
285 	case S_IFLNK:
286 		return ("symlink");
287 	case S_IFSOCK:
288 		return ("socket");
289 	default:
290 		return ("unknown");
291 	}
292 }
293 
294 
295 /*
296  * link_check --
297  *	return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
298  *	otherwise add `entry' to table and return NULL
299  */
300 /* This was borrowed from du.c and tweaked to keep an fsnode
301  * pointer instead. -- dbj@netbsd.org
302  */
303 static fsinode *
304 link_check(fsinode *entry)
305 {
306 	static struct entry {
307 		fsinode *data;
308 	} *htable;
309 	static int htshift;  /* log(allocated size) */
310 	static int htmask;   /* allocated size - 1 */
311 	static int htused;   /* 2*number of insertions */
312 	int h, h2;
313 	uint64_t tmp;
314 	/* this constant is (1<<64)/((1+sqrt(5))/2)
315 	 * aka (word size)/(golden ratio)
316 	 */
317 	const uint64_t HTCONST = 11400714819323198485ULL;
318 	const int HTBITS = 64;
319 
320 	/* Never store zero in hashtable */
321 	assert(entry);
322 
323 	/* Extend hash table if necessary, keep load under 0.5 */
324 	if (htused<<1 >= htmask) {
325 		struct entry *ohtable;
326 
327 		if (!htable)
328 			htshift = 10;   /* starting hashtable size */
329 		else
330 			htshift++;   /* exponential hashtable growth */
331 
332 		htmask  = (1 << htshift) - 1;
333 		htused = 0;
334 
335 		ohtable = htable;
336 		htable = ecalloc(htmask+1, sizeof(*htable));
337 		/* populate newly allocated hashtable */
338 		if (ohtable) {
339 			int i;
340 			for (i = 0; i <= htmask>>1; i++)
341 				if (ohtable[i].data)
342 					link_check(ohtable[i].data);
343 			free(ohtable);
344 		}
345 	}
346 
347 	/* multiplicative hashing */
348 	tmp = entry->st.st_dev;
349 	tmp <<= HTBITS>>1;
350 	tmp |=  entry->st.st_ino;
351 	tmp *= HTCONST;
352 	h  = tmp >> (HTBITS - htshift);
353 	h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
354 
355 	/* open address hashtable search with double hash probing */
356 	while (htable[h].data) {
357 		if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
358 		    (htable[h].data->st.st_dev == entry->st.st_dev)) {
359 			return htable[h].data;
360 		}
361 		h = (h + h2) & htmask;
362 	}
363 
364 	/* Insert the current entry into hashtable */
365 	htable[h].data = entry;
366 	htused++;
367 	return NULL;
368 }
369