xref: /netbsd-src/usr.sbin/makefs/walk.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: walk.c,v 1.24 2008/12/28 21:51:46 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.24 2008/12/28 21:51:46 christos Exp $");
45 #endif	/* !__lint */
46 
47 #include <sys/param.h>
48 
49 #include <assert.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <stdio.h>
53 #include <dirent.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <sys/stat.h>
58 
59 #include "makefs.h"
60 #include "mtree.h"
61 
62 static	void	 apply_specdir(const char *, NODE *, fsnode *, int);
63 static	void	 apply_specentry(const char *, NODE *, fsnode *);
64 static	fsnode	*create_fsnode(const char *, struct stat *);
65 static	fsinode	*link_check(fsinode *);
66 
67 
68 /*
69  * walk_dir --
70  *	build a tree of fsnodes from `dir', with a parent fsnode of `parent'
71  *	(which may be NULL for the root of the tree).
72  *	each "level" is a directory, with the "." entry guaranteed to be
73  *	at the start of the list, and without ".." entries.
74  */
75 fsnode *
76 walk_dir(const char *dir, fsnode *parent)
77 {
78 	fsnode		*first, *cur, *prev;
79 	DIR		*dirp;
80 	struct dirent	*dent;
81 	char		path[MAXPATHLEN + 1];
82 	struct stat	stbuf;
83 
84 	assert(dir != NULL);
85 
86 	if (debug & DEBUG_WALK_DIR)
87 		printf("walk_dir: %s %p\n", dir, parent);
88 	if ((dirp = opendir(dir)) == NULL)
89 		err(1, "Can't opendir `%s'", dir);
90 	first = prev = NULL;
91 	while ((dent = readdir(dirp)) != NULL) {
92 		if (strcmp(dent->d_name, "..") == 0)
93 			continue;
94 		if (debug & DEBUG_WALK_DIR_NODE)
95 			printf("scanning %s/%s\n", dir, dent->d_name);
96 		if (snprintf(path, sizeof(path), "%s/%s", dir, dent->d_name)
97 		    >= sizeof(path))
98 			errx(1, "Pathname too long.");
99 		if (lstat(path, &stbuf) == -1)
100 			err(1, "Can't lstat `%s'", path);
101 #ifdef S_ISSOCK
102 		if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
103 			if (debug & DEBUG_WALK_DIR_NODE)
104 				printf("  skipping socket %s\n", path);
105 			continue;
106 		}
107 #endif
108 
109 		cur = create_fsnode(dent->d_name, &stbuf);
110 		cur->parent = parent;
111 		if (strcmp(dent->d_name, ".") == 0) {
112 				/* ensure "." is at the start of the list */
113 			cur->next = first;
114 			first = cur;
115 			if (! prev)
116 				prev = cur;
117 		} else {			/* not "." */
118 			if (prev)
119 				prev->next = cur;
120 			prev = cur;
121 			if (!first)
122 				first = cur;
123 			if (S_ISDIR(cur->type)) {
124 				cur->child = walk_dir(path, cur);
125 				continue;
126 			}
127 		}
128 		if (stbuf.st_nlink > 1) {
129 			fsinode	*curino;
130 
131 			curino = link_check(cur->inode);
132 			if (curino != NULL) {
133 				free(cur->inode);
134 				cur->inode = curino;
135 				cur->inode->nlink++;
136 				if (debug & DEBUG_WALK_DIR_LINKCHECK)
137 					printf("link_check: found [%llu, %llu]\n",
138 					    (unsigned long long)curino->st.st_dev,
139 					    (unsigned long long)curino->st.st_ino);
140 			}
141 		}
142 		if (S_ISLNK(cur->type)) {
143 			char	slink[PATH_MAX+1];
144 			int	llen;
145 
146 			llen = readlink(path, slink, sizeof(slink) - 1);
147 			if (llen == -1)
148 				err(1, "Readlink `%s'", path);
149 			slink[llen] = '\0';
150 			if ((cur->symlink = strdup(slink)) == NULL)
151 				err(1, "Memory allocation error");
152 		}
153 	}
154 	for (cur = first; cur != NULL; cur = cur->next)
155 		cur->first = first;
156 	if (closedir(dirp) == -1)
157 		err(1, "Can't closedir `%s'", dir);
158 	return (first);
159 }
160 
161 static fsnode *
162 create_fsnode(const char *name, struct stat *stbuf)
163 {
164 	fsnode *cur;
165 
166 	if ((cur = calloc(1, sizeof(fsnode))) == NULL ||
167 	    (cur->name = strdup(name)) == NULL ||
168 	    (cur->inode = calloc(1, sizeof(fsinode))) == NULL)
169 		err(1, "Memory allocation error");
170 	cur->type = stbuf->st_mode & S_IFMT;
171 	cur->inode->nlink = 1;
172 	cur->inode->st = *stbuf;
173 	return (cur);
174 }
175 
176 /*
177  * free_fsnodes --
178  *	Removes node from tree and frees it and all of
179  *   its decendents.
180  */
181 void
182 free_fsnodes(fsnode *node)
183 {
184 	fsnode	*cur, *next;
185 
186 	assert(node != NULL);
187 
188 	/* for ".", start with actual parent node */
189 	if (node->first == node) {
190 		assert(node->name[0] == '.' && node->name[1] == '\0');
191 		if (node->parent) {
192 			assert(node->parent->child == node);
193 			node = node->parent;
194 		}
195 	}
196 
197 	/* Find ourselves in our sibling list and unlink */
198 	if (node->first != node) {
199 		for (cur = node->first; cur->next; cur = cur->next) {
200 			if (cur->next == node) {
201 				cur->next = node->next;
202 				node->next = NULL;
203 				break;
204 			}
205 		}
206 	}
207 
208 	for (cur = node; cur != NULL; cur = next) {
209 		next = cur->next;
210 		if (cur->child) {
211 			cur->child->parent = NULL;
212 			free_fsnodes(cur->child);
213 		}
214 		if (cur->inode->nlink-- == 1)
215 			free(cur->inode);
216 		if (cur->symlink)
217 			free(cur->symlink);
218 		free(cur->name);
219 		free(cur);
220 	}
221 }
222 
223 /*
224  * apply_specfile --
225  *	read in the mtree(8) specfile, and apply it to the tree
226  *	at dir,parent. parameters in parent on equivalent types
227  *	will be changed to those found in specfile, and missing
228  *	entries will be added.
229  */
230 void
231 apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
232 {
233 	struct timeval	 start;
234 	FILE	*fp;
235 	NODE	*root;
236 
237 	assert(specfile != NULL);
238 	assert(parent != NULL);
239 
240 	if (debug & DEBUG_APPLY_SPECFILE)
241 		printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
242 
243 				/* read in the specfile */
244 	if ((fp = fopen(specfile, "r")) == NULL)
245 		err(1, "Can't open `%s'", specfile);
246 	TIMER_START(start);
247 	root = spec(fp);
248 	TIMER_RESULTS(start, "spec");
249 	if (fclose(fp) == EOF)
250 		err(1, "Can't close `%s'", specfile);
251 
252 				/* perform some sanity checks */
253 	if (root == NULL)
254 		errx(1, "Specfile `%s' did not contain a tree", specfile);
255 	assert(strcmp(root->name, ".") == 0);
256 	assert(root->type == F_DIR);
257 
258 				/* merge in the changes */
259 	apply_specdir(dir, root, parent, speconly);
260 
261 	free_nodes(root);
262 }
263 
264 static void
265 apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
266 {
267 	char	 path[MAXPATHLEN + 1];
268 	NODE	*curnode;
269 	fsnode	*curfsnode;
270 
271 	assert(specnode != NULL);
272 	assert(dirnode != NULL);
273 
274 	if (debug & DEBUG_APPLY_SPECFILE)
275 		printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
276 
277 	if (specnode->type != F_DIR)
278 		errx(1, "Specfile node `%s/%s' is not a directory",
279 		    dir, specnode->name);
280 	if (dirnode->type != S_IFDIR)
281 		errx(1, "Directory node `%s/%s' is not a directory",
282 		    dir, dirnode->name);
283 
284 	apply_specentry(dir, specnode, dirnode);
285 
286 	/* Remove any filesystem nodes not found in specfile */
287 	/* XXX inefficient.  This is O^2 in each dir and it would
288 	 * have been better never to have walked this part of the tree
289 	 * to begin with
290 	 */
291 	if (speconly) {
292 		fsnode *next;
293 		assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
294 		for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
295 			next = curfsnode->next;
296 			for (curnode = specnode->child; curnode != NULL;
297 			     curnode = curnode->next) {
298 				if (strcmp(curnode->name, curfsnode->name) == 0)
299 					break;
300 			}
301 			if (curnode == NULL) {
302 				if (debug & DEBUG_APPLY_SPECONLY) {
303 					printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode);
304 				}
305 				free_fsnodes(curfsnode);
306 			}
307 		}
308 	}
309 
310 			/* now walk specnode->child matching up with dirnode */
311 	for (curnode = specnode->child; curnode != NULL;
312 	    curnode = curnode->next) {
313 		if (debug & DEBUG_APPLY_SPECENTRY)
314 			printf("apply_specdir:  spec %s\n",
315 			    curnode->name);
316 		for (curfsnode = dirnode->next; curfsnode != NULL;
317 		    curfsnode = curfsnode->next) {
318 #if 0	/* too verbose for now */
319 			if (debug & DEBUG_APPLY_SPECENTRY)
320 				printf("apply_specdir:  dirent %s\n",
321 				    curfsnode->name);
322 #endif
323 			if (strcmp(curnode->name, curfsnode->name) == 0)
324 				break;
325 		}
326 		if (snprintf(path, sizeof(path), "%s/%s",
327 		    dir, curnode->name) >= sizeof(path))
328 			errx(1, "Pathname too long.");
329 		if (curfsnode == NULL) {	/* need new entry */
330 			struct stat	stbuf;
331 
332 					    /*
333 					     * don't add optional spec entries
334 					     * that lack an existing fs entry
335 					     */
336 			if ((curnode->flags & F_OPT) &&
337 			    lstat(path, &stbuf) == -1)
338 					continue;
339 
340 					/* check that enough info is provided */
341 #define NODETEST(t, m)							\
342 			if (!(t))					\
343 				errx(1, "`%s': %s not provided", path, m)
344 			NODETEST(curnode->flags & F_TYPE, "type");
345 			NODETEST(curnode->flags & F_MODE, "mode");
346 				/* XXX: require F_TIME ? */
347 			NODETEST(curnode->flags & F_GID ||
348 			    curnode->flags & F_GNAME, "group");
349 			NODETEST(curnode->flags & F_UID ||
350 			    curnode->flags & F_UNAME, "user");
351 			if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
352 				NODETEST(curnode->flags & F_DEV,
353 				    "device number");
354 #undef NODETEST
355 
356 			if (debug & DEBUG_APPLY_SPECFILE)
357 				printf("apply_specdir: adding %s\n",
358 				    curnode->name);
359 					/* build minimal fsnode */
360 			memset(&stbuf, 0, sizeof(stbuf));
361 			stbuf.st_mode = nodetoino(curnode->type);
362 			stbuf.st_nlink = 1;
363 			stbuf.st_mtime = stbuf.st_atime =
364 			    stbuf.st_ctime = start_time.tv_sec;
365 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
366 			stbuf.st_mtimensec = stbuf.st_atimensec =
367 			    stbuf.st_ctimensec = start_time.tv_nsec;
368 #endif
369 			curfsnode = create_fsnode(curnode->name, &stbuf);
370 			curfsnode->parent = dirnode->parent;
371 			curfsnode->first = dirnode;
372 			curfsnode->next = dirnode->next;
373 			dirnode->next = curfsnode;
374 			if (curfsnode->type == S_IFDIR) {
375 					/* for dirs, make "." entry as well */
376 				curfsnode->child = create_fsnode(".", &stbuf);
377 				curfsnode->child->parent = curfsnode;
378 				curfsnode->child->first = curfsnode->child;
379 			}
380 			if (curfsnode->type == S_IFLNK) {
381 				assert(curnode->slink != NULL);
382 					/* for symlinks, copy the target */
383 				if ((curfsnode->symlink =
384 				    strdup(curnode->slink)) == NULL)
385 					err(1, "Memory allocation error");
386 			}
387 		}
388 		apply_specentry(dir, curnode, curfsnode);
389 		if (curnode->type == F_DIR) {
390 			if (curfsnode->type != S_IFDIR)
391 				errx(1, "`%s' is not a directory", path);
392 			assert (curfsnode->child != NULL);
393 			apply_specdir(path, curnode, curfsnode->child, speconly);
394 		}
395 	}
396 }
397 
398 static void
399 apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
400 {
401 
402 	assert(specnode != NULL);
403 	assert(dirnode != NULL);
404 
405 	if (nodetoino(specnode->type) != dirnode->type)
406 		errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
407 		    dir, specnode->name, inode_type(nodetoino(specnode->type)),
408 		    inode_type(dirnode->type));
409 
410 	if (debug & DEBUG_APPLY_SPECENTRY)
411 		printf("apply_specentry: %s/%s\n", dir, dirnode->name);
412 
413 #define ASEPRINT(t, b, o, n) \
414 		if (debug & DEBUG_APPLY_SPECENTRY) \
415 			printf("\t\t\tchanging %s from " b " to " b "\n", \
416 			    t, o, n)
417 
418 	if (specnode->flags & (F_GID | F_GNAME)) {
419 		ASEPRINT("gid", "%d",
420 		    dirnode->inode->st.st_gid, specnode->st_gid);
421 		dirnode->inode->st.st_gid = specnode->st_gid;
422 	}
423 	if (specnode->flags & F_MODE) {
424 		ASEPRINT("mode", "%#o",
425 		    dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
426 		dirnode->inode->st.st_mode &= ~ALLPERMS;
427 		dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
428 	}
429 		/* XXX: ignoring F_NLINK for now */
430 	if (specnode->flags & F_SIZE) {
431 		ASEPRINT("size", "%lld",
432 		    (long long)dirnode->inode->st.st_size,
433 		    (long long)specnode->st_size);
434 		dirnode->inode->st.st_size = specnode->st_size;
435 	}
436 	if (specnode->flags & F_SLINK) {
437 		assert(dirnode->symlink != NULL);
438 		assert(specnode->slink != NULL);
439 		ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
440 		free(dirnode->symlink);
441 		if ((dirnode->symlink = strdup(specnode->slink)) == NULL)
442 			err(1, "Memory allocation error");
443 	}
444 	if (specnode->flags & F_TIME) {
445 		ASEPRINT("time", "%ld",
446 		    (long)dirnode->inode->st.st_mtime,
447 		    (long)specnode->st_mtimespec.tv_sec);
448 		dirnode->inode->st.st_mtime =		specnode->st_mtimespec.tv_sec;
449 		dirnode->inode->st.st_atime =		specnode->st_mtimespec.tv_sec;
450 		dirnode->inode->st.st_ctime =		start_time.tv_sec;
451 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
452 		dirnode->inode->st.st_mtimensec =	specnode->st_mtimespec.tv_nsec;
453 		dirnode->inode->st.st_atimensec =	specnode->st_mtimespec.tv_nsec;
454 		dirnode->inode->st.st_ctimensec =	start_time.tv_nsec;
455 #endif
456 	}
457 	if (specnode->flags & (F_UID | F_UNAME)) {
458 		ASEPRINT("uid", "%d",
459 		    dirnode->inode->st.st_uid, specnode->st_uid);
460 		dirnode->inode->st.st_uid = specnode->st_uid;
461 	}
462 #if HAVE_STRUCT_STAT_ST_FLAGS
463 	if (specnode->flags & F_FLAGS) {
464 		ASEPRINT("flags", "%#lX",
465 		    (unsigned long)dirnode->inode->st.st_flags,
466 		    (unsigned long)specnode->st_flags);
467 		dirnode->inode->st.st_flags = specnode->st_flags;
468 	}
469 #endif
470 	if (specnode->flags & F_DEV) {
471 		ASEPRINT("rdev", "%#llx",
472 		    (unsigned long long)dirnode->inode->st.st_rdev,
473 		    (unsigned long long)specnode->st_rdev);
474 		dirnode->inode->st.st_rdev = specnode->st_rdev;
475 	}
476 #undef ASEPRINT
477 
478 	dirnode->flags |= FSNODE_F_HASSPEC;
479 }
480 
481 
482 /*
483  * dump_fsnodes --
484  *	dump the fsnodes from `cur', based in the directory `dir'
485  */
486 void
487 dump_fsnodes(const char *dir, fsnode *root)
488 {
489 	fsnode	*cur;
490 	char	path[MAXPATHLEN + 1];
491 
492 	assert (dir != NULL);
493 	printf("dump_fsnodes: %s %p\n", dir, root);
494 	for (cur = root; cur != NULL; cur = cur->next) {
495 		if (snprintf(path, sizeof(path), "%s/%s", dir, cur->name)
496 		    >= sizeof(path))
497 			errx(1, "Pathname too long.");
498 
499 		if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
500 			printf("cur=%8p parent=%8p first=%8p ",
501 			    cur, cur->parent, cur->first);
502 		printf("%7s: %s", inode_type(cur->type), path);
503 		if (S_ISLNK(cur->type)) {
504 			assert(cur->symlink != NULL);
505 			printf(" -> %s", cur->symlink);
506 		} else {
507 			assert (cur->symlink == NULL);
508 		}
509 		if (cur->inode->nlink > 1)
510 			printf(", nlinks=%d", cur->inode->nlink);
511 		putchar('\n');
512 
513 		if (cur->child) {
514 			assert (cur->type == S_IFDIR);
515 			dump_fsnodes(path, cur->child);
516 		}
517 	}
518 	printf("dump_fsnodes: finished %s\n", dir);
519 }
520 
521 
522 /*
523  * inode_type --
524  *	for a given inode type `mode', return a descriptive string.
525  *	for most cases, uses inotype() from mtree/misc.c
526  */
527 const char *
528 inode_type(mode_t mode)
529 {
530 
531 	if (S_ISLNK(mode))
532 		return ("symlink");	/* inotype() returns "link"...  */
533 	return (inotype(mode));
534 }
535 
536 
537 /*
538  * link_check --
539  *	return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
540  *	otherwise add `entry' to table and return NULL
541  */
542 /* This was borrowed from du.c and tweaked to keep an fsnode
543  * pointer instead. -- dbj@netbsd.org
544  */
545 static fsinode *
546 link_check(fsinode *entry)
547 {
548 	static struct entry {
549 		fsinode *data;
550 	} *htable;
551 	static int htshift;  /* log(allocated size) */
552 	static int htmask;   /* allocated size - 1 */
553 	static int htused;   /* 2*number of insertions */
554 	int h, h2;
555 	uint64_t tmp;
556 	/* this constant is (1<<64)/((1+sqrt(5))/2)
557 	 * aka (word size)/(golden ratio)
558 	 */
559 	const uint64_t HTCONST = 11400714819323198485ULL;
560 	const int HTBITS = 64;
561 
562 	/* Never store zero in hashtable */
563 	assert(entry);
564 
565 	/* Extend hash table if necessary, keep load under 0.5 */
566 	if (htused<<1 >= htmask) {
567 		struct entry *ohtable;
568 
569 		if (!htable)
570 			htshift = 10;   /* starting hashtable size */
571 		else
572 			htshift++;   /* exponential hashtable growth */
573 
574 		htmask  = (1 << htshift) - 1;
575 		htused = 0;
576 
577 		ohtable = htable;
578 		htable = calloc(htmask+1, sizeof(*htable));
579 		if (!htable)
580 			err(1, "Memory allocation error");
581 
582 		/* populate newly allocated hashtable */
583 		if (ohtable) {
584 			int i;
585 			for (i = 0; i <= htmask>>1; i++)
586 				if (ohtable[i].data)
587 					link_check(ohtable[i].data);
588 			free(ohtable);
589 		}
590 	}
591 
592 	/* multiplicative hashing */
593 	tmp = entry->st.st_dev;
594 	tmp <<= HTBITS>>1;
595 	tmp |=  entry->st.st_ino;
596 	tmp *= HTCONST;
597 	h  = tmp >> (HTBITS - htshift);
598 	h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
599 
600 	/* open address hashtable search with double hash probing */
601 	while (htable[h].data) {
602 		if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
603 		    (htable[h].data->st.st_dev == entry->st.st_dev)) {
604 			return htable[h].data;
605 		}
606 		h = (h + h2) & htmask;
607 	}
608 
609 	/* Insert the current entry into hashtable */
610 	htable[h].data = entry;
611 	htused++;
612 	return NULL;
613 }
614