walk.c revision 1.1
1/*	$NetBSD: walk.c,v 1.1 2001/10/26 06:16:19 lukem Exp $	*/
2
3/*
4 * Copyright 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/*
39 * The function link_check() was inspired from NetBSD's usr.bin/du/du.c,
40 * which has the following copyright notice:
41 *
42 *
43 * Copyright (c) 1989, 1993, 1994
44 *	The Regents of the University of California.  All rights reserved.
45 *
46 * This code is derived from software contributed to Berkeley by
47 * Chris Newcomb.
48 *
49 * Redistribution and use in source and binary forms, with or without
50 * modification, are permitted provided that the following conditions
51 * are met:
52 * 1. Redistributions of source code must retain the above copyright
53 *    notice, this list of conditions and the following disclaimer.
54 * 2. Redistributions in binary form must reproduce the above copyright
55 *    notice, this list of conditions and the following disclaimer in the
56 *    documentation and/or other materials provided with the distribution.
57 * 3. All advertising materials mentioning features or use of this software
58 *    must display the following acknowledgement:
59 *	This product includes software developed by the University of
60 *	California, Berkeley and its contributors.
61 * 4. Neither the name of the University nor the names of its contributors
62 *    may be used to endorse or promote products derived from this software
63 *    without specific prior written permission.
64 *
65 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
66 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
67 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
68 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
69 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
70 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
71 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
72 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
73 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
74 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
75 * SUCH DAMAGE.
76 */
77
78#include <sys/param.h>
79
80#include <assert.h>
81#include <err.h>
82#include <errno.h>
83#include <fcntl.h>
84#include <stdio.h>
85#include <dirent.h>
86#include <stdlib.h>
87#include <string.h>
88#include <unistd.h>
89
90#include "makefs.h"
91#include "mtree.h"
92
93static	void	 apply_specdir(const char *, NODE *, fsnode *);
94static	void	 apply_specentry(const char *, NODE *, fsnode *);
95static	fsnode	*create_fsnode(const char *, struct stat *);
96static	fsnode	*link_check(fsnode *);
97
98
99/*
100 * walk_dir --
101 *	build a tree of fsnodes from `dir', with a parent fsnode of `parent'
102 *	(which may be NULL for the root of the tree).
103 *	each "level" is a directory, with the "." entry guaranteed to be
104 *	at the start of the list, and without ".." entries.
105 */
106fsnode *
107walk_dir(const char *dir, fsnode *parent)
108{
109	fsnode		*first, *cur, *prev;
110	DIR		*dirp;
111	struct dirent	*dent;
112	char		path[MAXPATHLEN + 1];
113	struct stat	stbuf;
114
115	assert(dir != NULL);
116
117	if (debug & DEBUG_WALK_DIR)
118		printf("walk_dir: %s %p\n", dir, parent);
119	if ((dirp = opendir(dir)) == NULL)
120		err(1, "Can't opendir `%s'", dir);
121	first = prev = NULL;
122	while ((dent = readdir(dirp)) != NULL) {
123		if (strcmp(dent->d_name, "..") == 0)
124			continue;
125		if (debug & DEBUG_WALK_DIR_NODE)
126			printf("scanning %s/%s\n", dir, dent->d_name);
127		if (snprintf(path, sizeof(path), "%s/%s", dir, dent->d_name)
128		    >= sizeof(path))
129			errx(1, "Pathname too long.");
130		if (lstat(path, &stbuf) == -1)
131			err(1, "Can't lstat `%s'", path);
132		if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
133			if (debug & DEBUG_WALK_DIR_NODE)
134				printf("  skipping socket %s\n", path);
135			continue;
136		}
137
138		cur = create_fsnode(dent->d_name, &stbuf);
139		cur->parent = parent;
140		if (strcmp(dent->d_name, ".") == 0) {
141				/* ensure "." is at the start of the list */
142			cur->next = first;
143			first = cur;
144			if (! prev)
145				prev = cur;
146		} else {			/* not "." */
147			if (prev)
148				prev->next = cur;
149			prev = cur;
150			if (!first)
151				first = cur;
152			if (S_ISDIR(cur->type)) {
153				cur->child = walk_dir(path, cur);
154				continue;
155			}
156		}
157		if (cur->statbuf.st_nlink > 1) {
158			cur->dup = link_check(cur);
159			if (cur->dup)
160				cur->dup->nlink++;
161		}
162		if (S_ISLNK(cur->type)) {
163			char	slink[PATH_MAX+1];
164			int	llen;
165
166			llen = readlink(path, slink, PATH_MAX);
167			if (llen == -1)
168				err(1, "Readlink `%s'", path);
169			slink[llen] = '\0';
170			if ((cur->symlink = strdup(slink)) == NULL)
171				err(1, "Memory allocation error");
172		}
173	}
174	for (cur = first; cur != NULL; cur = cur->next)
175		cur->first = first;
176	if (closedir(dirp) == -1)
177		err(1, "Can't closedir `%s'", dir);
178	return (first);
179}
180
181static fsnode *
182create_fsnode(const char *name, struct stat *statbuf)
183{
184	fsnode *cur;
185
186	if ((cur = calloc(1, sizeof(fsnode))) == NULL ||
187	    (cur->name = strdup(name)) == NULL)
188		err(1, "Memory allocation error");
189	cur->statbuf = *statbuf;
190	cur->type = (cur->statbuf.st_mode & S_IFMT);
191	cur->nlink = 1;
192	return (cur);
193}
194
195
196/*
197 * apply_specfile --
198 *	read in the mtree(8) specfile, and apply it to the tree
199 *	at dir,parent. parameters in parent on equivalent types
200 *	will be changed to those found in specfile, and missing
201 *	entries will be added.
202 */
203void
204apply_specfile(const char *specfile, const char *dir, fsnode *parent)
205{
206	struct timeval	 start;
207	FILE	*fp;
208	NODE	*root;
209
210	assert(specfile != NULL);
211	assert(parent != NULL);
212
213	if (debug & DEBUG_APPLY_SPECFILE)
214		printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
215
216				/* read in the specfile */
217	if ((fp = fopen(specfile, "r")) == NULL)
218		err(1, "Can't open `%s'", specfile);
219	TIMER_START(start);
220	root = spec(fp);
221	TIMER_RESULTS(start, "spec");
222	if (fclose(fp) == EOF)
223		err(1, "Can't close `%s'", specfile);
224
225				/* perform some sanity checks */
226	if (root == NULL)
227		errx(1, "Specfile `%s' did not contain a tree", specfile);
228	assert(strcmp(root->name, ".") == 0);
229	assert(root->type == F_DIR);
230
231				/* merge in the changes */
232	apply_specdir(dir, root, parent);
233}
234
235static void
236apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode)
237{
238	char	 path[MAXPATHLEN + 1];
239	NODE	*curnode;
240	fsnode	*curfsnode;
241
242	assert(specnode != NULL);
243	assert(dirnode != NULL);
244
245	if (debug & DEBUG_APPLY_SPECFILE)
246		printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
247
248	if (specnode->type != F_DIR)
249		errx(1, "Specfile node `%s/%s' is not a directory",
250		    dir, specnode->name);
251	if (dirnode->type != S_IFDIR)
252		errx(1, "Directory node `%s/%s' is not a directory",
253		    dir, dirnode->name);
254
255	apply_specentry(dir, specnode, dirnode);
256
257			/* now walk specnode->child matching up with dirnode */
258	for (curnode = specnode->child; curnode != NULL;
259	    curnode = curnode->next) {
260		if (debug & DEBUG_APPLY_SPECENTRY)
261			printf("apply_specdir:  spec %s\n",
262			    curnode->name);
263		for (curfsnode = dirnode->next; curfsnode != NULL;
264		    curfsnode = curfsnode->next) {
265			if (debug & DEBUG_APPLY_SPECENTRY)
266				printf("apply_specdir:  dirent %s\n",
267				    curfsnode->name);
268			if (strcmp(curnode->name, curfsnode->name) == 0)
269				break;
270		}
271		if (curfsnode == NULL) {	/* need new entry */
272			struct stat	stbuf;
273
274					/* check that enough info is provided */
275#define NODETEST(t, m)							\
276			if (!(t))					\
277				errx(1, "`%s/%s': %s not provided",	\
278				    dir, curnode->name, m)
279			NODETEST(curnode->flags & F_TYPE, "type");
280			NODETEST(curnode->flags & F_MODE, "mode");
281				/* XXX: require F_TIME ? */
282			NODETEST(curnode->flags & F_GID ||
283			    curnode->flags & F_GNAME, "group");
284			NODETEST(curnode->flags & F_UID ||
285			    curnode->flags & F_UNAME, "user");
286			if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
287				NODETEST(curnode->flags & F_DEV,
288				    "device number");
289#undef NODETEST
290
291			if (debug & DEBUG_APPLY_SPECFILE)
292				printf("apply_specdir: adding %s\n",
293				    curnode->name);
294					/* build minimal fsnode */
295			memset(&stbuf, 0, sizeof(stbuf));
296			stbuf.st_mode = nodetoino(curnode->type);
297			stbuf.st_mtime = stbuf.st_atime =
298			    stbuf.st_ctime = start_time.tv_sec;
299			stbuf.st_mtimensec = stbuf.st_atimensec =
300			    stbuf.st_ctimensec = start_time.tv_nsec;
301			curfsnode = create_fsnode(curnode->name, &stbuf);
302			curfsnode->parent = dirnode->parent;
303			curfsnode->first = dirnode;
304			curfsnode->next = dirnode->next;
305			dirnode->next = curfsnode;
306			if (curfsnode->type == S_IFDIR) {
307					/* for dirs, make "." entry as well */
308				curfsnode->child = create_fsnode(".", &stbuf);
309				curfsnode->child->parent = curfsnode;
310				curfsnode->child->first = curfsnode->child;
311			}
312			if (curfsnode->type == S_IFLNK) {
313				assert(specnode->slink != NULL);
314					/* for symlinks, copy the target */
315				if ((curfsnode->symlink =
316				    strdup(curnode->slink)) == NULL)
317					err(1, "Memory allocation error");
318			}
319		}
320		apply_specentry(dir, curnode, curfsnode);
321		if (curnode->type == F_DIR) {
322			if (curfsnode->type != S_IFDIR)
323				errx(1, "`%s/%s' is not a directory",
324				    dir, curfsnode->name);
325			assert (curfsnode->child != NULL);
326			if (snprintf(path, sizeof(path), "%s/%s",
327			    dir, curnode->name) >= sizeof(path))
328				errx(1, "Pathname too long.");
329			apply_specdir(path, curnode, curfsnode->child);
330		}
331	}
332}
333
334static void
335apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
336{
337
338	assert(specnode != NULL);
339	assert(dirnode != NULL);
340
341	if (nodetoino(specnode->type) != dirnode->type)
342		errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
343		    dir, specnode->name, inode_type(nodetoino(specnode->type)),
344		    inode_type(dirnode->type));
345
346	if (debug & DEBUG_APPLY_SPECENTRY)
347		printf("apply_specentry: %s/%s\n", dir, dirnode->name);
348
349#define ASEPRINT(t, b, o, n) \
350		if (debug & DEBUG_APPLY_SPECENTRY) \
351			printf("\t\t\tchanging %s from " b " to " b "\n", \
352			    t, o, n)
353
354	if (specnode->flags & (F_GID | F_GNAME)) {
355		ASEPRINT("gid", "%d",
356		    dirnode->statbuf.st_gid, specnode->st_gid);
357		dirnode->statbuf.st_gid = specnode->st_gid;
358	}
359	if (specnode->flags & F_MODE) {
360		ASEPRINT("mode", "%#o",
361		    dirnode->statbuf.st_mode & ALLPERMS, specnode->st_mode);
362		dirnode->statbuf.st_mode &= ~ALLPERMS;
363		dirnode->statbuf.st_mode |= (specnode->st_mode & ALLPERMS);
364	}
365		/* XXX: ignoring F_NLINK for now */
366	if (specnode->flags & F_SIZE) {
367		ASEPRINT("size", "%lld",
368		    (long long)dirnode->statbuf.st_size,
369		    (long long)specnode->st_size);
370		dirnode->statbuf.st_size = specnode->st_size;
371	}
372	if (specnode->flags & F_SLINK) {
373		assert(dirnode->symlink != NULL);
374		assert(specnode->slink != NULL);
375		ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
376		free(dirnode->symlink);
377		if ((dirnode->symlink = strdup(specnode->slink)) == NULL)
378			err(1, "Memory allocation error");
379	}
380	if (specnode->flags & F_TIME) {
381		ASEPRINT("time", "%ld",
382		    dirnode->statbuf.st_mtime, specnode->st_mtime);
383		dirnode->statbuf.st_mtime =	specnode->st_mtime;
384		dirnode->statbuf.st_mtimensec =	specnode->st_mtimensec;
385		dirnode->statbuf.st_atime =	specnode->st_mtime;
386		dirnode->statbuf.st_atimensec =	specnode->st_mtimensec;
387		dirnode->statbuf.st_ctime =	start_time.tv_sec;
388		dirnode->statbuf.st_ctimensec =	start_time.tv_nsec;
389	}
390	if (specnode->flags & (F_UID | F_UNAME)) {
391		ASEPRINT("uid", "%d",
392		    dirnode->statbuf.st_uid, specnode->st_uid);
393		dirnode->statbuf.st_uid = specnode->st_uid;
394	}
395	if (specnode->flags & F_FLAGS) {
396		ASEPRINT("flags", "%#lX",
397		    (u_long)dirnode->statbuf.st_flags,
398		    (u_long)specnode->st_flags);
399		dirnode->statbuf.st_flags = specnode->st_flags;
400	}
401	if (specnode->flags & F_DEV) {
402		ASEPRINT("rdev", "%#x",
403		    dirnode->statbuf.st_rdev, specnode->st_rdev);
404		dirnode->statbuf.st_rdev = specnode->st_rdev;
405	}
406#undef ASEPRINT
407}
408
409
410/*
411 * dump_fsnodes --
412 *	dump the fsnodes from `cur', based in the directory `dir'
413 */
414void
415dump_fsnodes(const char *dir, fsnode *root)
416{
417	fsnode	*cur;
418	char	path[MAXPATHLEN + 1];
419
420	assert (dir != NULL);
421	printf("dump_fsnodes: %s %p\n", dir, root);
422	for (cur = root; cur != NULL; cur = cur->next) {
423		if (snprintf(path, sizeof(path), "%s/%s", dir, cur->name)
424		    >= sizeof(path))
425			errx(1, "Pathname too long.");
426
427		if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
428			printf("cur=%8p parent=%8p first=%8p ",
429			    cur, cur->parent, cur->first);
430		printf("%7s: %s", inode_type(cur->type), path);
431		if (S_ISLNK(cur->type)) {
432			assert(cur->symlink != NULL);
433			printf(" -> %s", cur->symlink);
434		} else {
435			assert (cur->symlink == NULL);
436		}
437		if (cur->dup != NULL) {
438			printf(", hard-linked to %s", cur->dup->name);
439		}
440		if (cur->nlink > 1)
441			printf(", nlinks=%d", cur->nlink);
442		putchar('\n');
443
444		if (cur->child) {
445			assert (cur->type == S_IFDIR);
446			dump_fsnodes(path, cur->child);
447		}
448	}
449	printf("dump_fsnodes: finished %s\n", dir);
450}
451
452
453/*
454 * inode_type --
455 *	for a given inode type `mode', return a descriptive string.
456 *	for most cases, uses inotype() from mtree/misc.c
457 */
458const char *
459inode_type(mode_t mode)
460{
461
462	if (mode & S_IFLNK)
463		return ("symlink");	/* inotype() returns "link"...  */
464	return (inotype(mode));
465}
466
467
468typedef struct {
469	int32_t	dev;
470	int32_t	ino;
471	fsnode	*dup;
472} dupnode;
473
474/*
475 * link_check --
476 *	return pointer to fsnode matching `entry's st_ino & st_dev if it exists,
477 *	otherwise add `entry' to table and return NULL
478 */
479static fsnode *
480link_check(fsnode *entry)
481{
482	static	dupnode	*dups;
483	static	int	ndups, maxdups;
484
485	int	i;
486
487	assert (entry != NULL);
488
489		/* XXX; maybe traverse in reverse for speed? */
490	for (i = 0; i < ndups; i++) {
491		if (dups[i].dev == entry->statbuf.st_dev &&
492		    dups[i].ino == entry->statbuf.st_ino) {
493			if (debug & DEBUG_WALK_DIR_LINKCHECK)
494				printf(
495				    "link_check: %s (%d,%d) linked to %s\n",
496				    entry->name, entry->statbuf.st_dev,
497				    entry->statbuf.st_ino, dups[i].dup->name);
498			return (dups[i].dup);
499		}
500	}
501
502	if (debug & DEBUG_WALK_DIR_LINKCHECK)
503		printf("link_check: no match for %s (%d, %d)\n",
504		    entry->name, entry->statbuf.st_dev, entry->statbuf.st_ino);
505	if (ndups == maxdups) {
506		maxdups += 128;
507		if ((dups = realloc(dups, sizeof(dupnode) * maxdups)) == NULL)
508			err(1, "Memory allocation error");
509	}
510	dups[ndups].dev = entry->statbuf.st_dev;
511	dups[ndups].ino = entry->statbuf.st_ino;
512	dups[ndups].dup = entry;
513	ndups++;
514
515	return (NULL);
516}
517