walk.c revision 1.15 1 /* $NetBSD: walk.c,v 1.15 2003/09/19 06:11:35 itojun 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 /*
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. Neither the name of the University nor the names of its contributors
58 * may be used to endorse or promote products derived from this software
59 * without specific prior written permission.
60 *
61 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
62 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
63 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
64 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
65 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
66 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
67 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
68 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
69 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
70 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
71 * SUCH DAMAGE.
72 */
73
74 #include <sys/cdefs.h>
75 #if defined(__RCSID) && !defined(__lint)
76 __RCSID("$NetBSD: walk.c,v 1.15 2003/09/19 06:11:35 itojun Exp $");
77 #endif /* !__lint */
78
79 #include <sys/param.h>
80
81 #include <assert.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
93 static void apply_specdir(const char *, NODE *, fsnode *);
94 static void apply_specentry(const char *, NODE *, fsnode *);
95 static fsnode *create_fsnode(const char *, struct stat *);
96 static fsinode *link_check(fsinode *);
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 */
106 fsnode *
107 walk_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 (stbuf.st_nlink > 1) {
158 fsinode *curino;
159
160 curino = link_check(cur->inode);
161 if (curino != NULL) {
162 free(cur->inode);
163 cur->inode = curino;
164 cur->inode->nlink++;
165 }
166 }
167 if (S_ISLNK(cur->type)) {
168 char slink[PATH_MAX+1];
169 int llen;
170
171 llen = readlink(path, slink, sizeof(slink) - 1);
172 if (llen == -1)
173 err(1, "Readlink `%s'", path);
174 slink[llen] = '\0';
175 if ((cur->symlink = strdup(slink)) == NULL)
176 err(1, "Memory allocation error");
177 }
178 }
179 for (cur = first; cur != NULL; cur = cur->next)
180 cur->first = first;
181 if (closedir(dirp) == -1)
182 err(1, "Can't closedir `%s'", dir);
183 return (first);
184 }
185
186 static fsnode *
187 create_fsnode(const char *name, struct stat *stbuf)
188 {
189 fsnode *cur;
190
191 if ((cur = calloc(1, sizeof(fsnode))) == NULL ||
192 (cur->name = strdup(name)) == NULL ||
193 (cur->inode = calloc(1, sizeof(fsinode))) == NULL)
194 err(1, "Memory allocation error");
195 cur->type = stbuf->st_mode & S_IFMT;
196 cur->inode->nlink = 1;
197 cur->inode->st = *stbuf;
198 return (cur);
199 }
200
201 /*
202 * apply_specfile --
203 * read in the mtree(8) specfile, and apply it to the tree
204 * at dir,parent. parameters in parent on equivalent types
205 * will be changed to those found in specfile, and missing
206 * entries will be added.
207 */
208 void
209 apply_specfile(const char *specfile, const char *dir, fsnode *parent)
210 {
211 struct timeval start;
212 FILE *fp;
213 NODE *root;
214
215 assert(specfile != NULL);
216 assert(parent != NULL);
217
218 if (debug & DEBUG_APPLY_SPECFILE)
219 printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
220
221 /* read in the specfile */
222 if ((fp = fopen(specfile, "r")) == NULL)
223 err(1, "Can't open `%s'", specfile);
224 TIMER_START(start);
225 root = spec(fp);
226 TIMER_RESULTS(start, "spec");
227 if (fclose(fp) == EOF)
228 err(1, "Can't close `%s'", specfile);
229
230 /* perform some sanity checks */
231 if (root == NULL)
232 errx(1, "Specfile `%s' did not contain a tree", specfile);
233 assert(strcmp(root->name, ".") == 0);
234 assert(root->type == F_DIR);
235
236 /* merge in the changes */
237 apply_specdir(dir, root, parent);
238 }
239
240 static void
241 apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode)
242 {
243 char path[MAXPATHLEN + 1];
244 NODE *curnode;
245 fsnode *curfsnode;
246
247 assert(specnode != NULL);
248 assert(dirnode != NULL);
249
250 if (debug & DEBUG_APPLY_SPECFILE)
251 printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
252
253 if (specnode->type != F_DIR)
254 errx(1, "Specfile node `%s/%s' is not a directory",
255 dir, specnode->name);
256 if (dirnode->type != S_IFDIR)
257 errx(1, "Directory node `%s/%s' is not a directory",
258 dir, dirnode->name);
259
260 apply_specentry(dir, specnode, dirnode);
261
262 /* now walk specnode->child matching up with dirnode */
263 for (curnode = specnode->child; curnode != NULL;
264 curnode = curnode->next) {
265 if (debug & DEBUG_APPLY_SPECENTRY)
266 printf("apply_specdir: spec %s\n",
267 curnode->name);
268 for (curfsnode = dirnode->next; curfsnode != NULL;
269 curfsnode = curfsnode->next) {
270 #if 0 /* too verbose for now */
271 if (debug & DEBUG_APPLY_SPECENTRY)
272 printf("apply_specdir: dirent %s\n",
273 curfsnode->name);
274 #endif
275 if (strcmp(curnode->name, curfsnode->name) == 0)
276 break;
277 }
278 if (snprintf(path, sizeof(path), "%s/%s",
279 dir, curnode->name) >= sizeof(path))
280 errx(1, "Pathname too long.");
281 if (curfsnode == NULL) { /* need new entry */
282 struct stat stbuf;
283
284 /*
285 * don't add optional spec entries
286 * that lack an existing fs entry
287 */
288 if ((curnode->flags & F_OPT) &&
289 lstat(path, &stbuf) == -1)
290 continue;
291
292 /* check that enough info is provided */
293 #define NODETEST(t, m) \
294 if (!(t)) \
295 errx(1, "`%s': %s not provided", path, m)
296 NODETEST(curnode->flags & F_TYPE, "type");
297 NODETEST(curnode->flags & F_MODE, "mode");
298 /* XXX: require F_TIME ? */
299 NODETEST(curnode->flags & F_GID ||
300 curnode->flags & F_GNAME, "group");
301 NODETEST(curnode->flags & F_UID ||
302 curnode->flags & F_UNAME, "user");
303 if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
304 NODETEST(curnode->flags & F_DEV,
305 "device number");
306 #undef NODETEST
307
308 if (debug & DEBUG_APPLY_SPECFILE)
309 printf("apply_specdir: adding %s\n",
310 curnode->name);
311 /* build minimal fsnode */
312 memset(&stbuf, 0, sizeof(stbuf));
313 stbuf.st_mode = nodetoino(curnode->type);
314 stbuf.st_nlink = 1;
315 stbuf.st_mtime = stbuf.st_atime =
316 stbuf.st_ctime = start_time.tv_sec;
317 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
318 stbuf.st_mtimensec = stbuf.st_atimensec =
319 stbuf.st_ctimensec = start_time.tv_nsec;
320 #endif
321 curfsnode = create_fsnode(curnode->name, &stbuf);
322 curfsnode->parent = dirnode->parent;
323 curfsnode->first = dirnode;
324 curfsnode->next = dirnode->next;
325 dirnode->next = curfsnode;
326 if (curfsnode->type == S_IFDIR) {
327 /* for dirs, make "." entry as well */
328 curfsnode->child = create_fsnode(".", &stbuf);
329 curfsnode->child->parent = curfsnode;
330 curfsnode->child->first = curfsnode->child;
331 }
332 if (curfsnode->type == S_IFLNK) {
333 assert(curnode->slink != NULL);
334 /* for symlinks, copy the target */
335 if ((curfsnode->symlink =
336 strdup(curnode->slink)) == NULL)
337 err(1, "Memory allocation error");
338 }
339 }
340 apply_specentry(dir, curnode, curfsnode);
341 if (curnode->type == F_DIR) {
342 if (curfsnode->type != S_IFDIR)
343 errx(1, "`%s' is not a directory", path);
344 assert (curfsnode->child != NULL);
345 apply_specdir(path, curnode, curfsnode->child);
346 }
347 }
348 }
349
350 static void
351 apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
352 {
353
354 assert(specnode != NULL);
355 assert(dirnode != NULL);
356
357 if (nodetoino(specnode->type) != dirnode->type)
358 errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
359 dir, specnode->name, inode_type(nodetoino(specnode->type)),
360 inode_type(dirnode->type));
361
362 if (debug & DEBUG_APPLY_SPECENTRY)
363 printf("apply_specentry: %s/%s\n", dir, dirnode->name);
364
365 #define ASEPRINT(t, b, o, n) \
366 if (debug & DEBUG_APPLY_SPECENTRY) \
367 printf("\t\t\tchanging %s from " b " to " b "\n", \
368 t, o, n)
369
370 if (specnode->flags & (F_GID | F_GNAME)) {
371 ASEPRINT("gid", "%d",
372 dirnode->inode->st.st_gid, specnode->st_gid);
373 dirnode->inode->st.st_gid = specnode->st_gid;
374 }
375 if (specnode->flags & F_MODE) {
376 ASEPRINT("mode", "%#o",
377 dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
378 dirnode->inode->st.st_mode &= ~ALLPERMS;
379 dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
380 }
381 /* XXX: ignoring F_NLINK for now */
382 if (specnode->flags & F_SIZE) {
383 ASEPRINT("size", "%lld",
384 (long long)dirnode->inode->st.st_size,
385 (long long)specnode->st_size);
386 dirnode->inode->st.st_size = specnode->st_size;
387 }
388 if (specnode->flags & F_SLINK) {
389 assert(dirnode->symlink != NULL);
390 assert(specnode->slink != NULL);
391 ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
392 free(dirnode->symlink);
393 if ((dirnode->symlink = strdup(specnode->slink)) == NULL)
394 err(1, "Memory allocation error");
395 }
396 if (specnode->flags & F_TIME) {
397 ASEPRINT("time", "%ld",
398 (long)dirnode->inode->st.st_mtime,
399 (long)specnode->st_mtimespec.tv_sec);
400 dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec;
401 dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec;
402 dirnode->inode->st.st_ctime = start_time.tv_sec;
403 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
404 dirnode->inode->st.st_mtimensec = specnode->st_mtimensec;
405 dirnode->inode->st.st_atimensec = specnode->st_mtimensec;
406 dirnode->inode->st.st_ctimensec = start_time.tv_nsec;
407 #endif
408 }
409 if (specnode->flags & (F_UID | F_UNAME)) {
410 ASEPRINT("uid", "%d",
411 dirnode->inode->st.st_uid, specnode->st_uid);
412 dirnode->inode->st.st_uid = specnode->st_uid;
413 }
414 #if HAVE_STRUCT_STAT_ST_FLAGS
415 if (specnode->flags & F_FLAGS) {
416 ASEPRINT("flags", "%#lX",
417 (unsigned long)dirnode->inode->st.st_flags,
418 (unsigned long)specnode->st_flags);
419 dirnode->inode->st.st_flags = specnode->st_flags;
420 }
421 #endif
422 if (specnode->flags & F_DEV) {
423 ASEPRINT("rdev", "%#x",
424 dirnode->inode->st.st_rdev, specnode->st_rdev);
425 dirnode->inode->st.st_rdev = specnode->st_rdev;
426 }
427 #undef ASEPRINT
428
429 dirnode->flags |= FSNODE_F_HASSPEC;
430 }
431
432
433 /*
434 * dump_fsnodes --
435 * dump the fsnodes from `cur', based in the directory `dir'
436 */
437 void
438 dump_fsnodes(const char *dir, fsnode *root)
439 {
440 fsnode *cur;
441 char path[MAXPATHLEN + 1];
442
443 assert (dir != NULL);
444 printf("dump_fsnodes: %s %p\n", dir, root);
445 for (cur = root; cur != NULL; cur = cur->next) {
446 if (snprintf(path, sizeof(path), "%s/%s", dir, cur->name)
447 >= sizeof(path))
448 errx(1, "Pathname too long.");
449
450 if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
451 printf("cur=%8p parent=%8p first=%8p ",
452 cur, cur->parent, cur->first);
453 printf("%7s: %s", inode_type(cur->type), path);
454 if (S_ISLNK(cur->type)) {
455 assert(cur->symlink != NULL);
456 printf(" -> %s", cur->symlink);
457 } else {
458 assert (cur->symlink == NULL);
459 }
460 if (cur->inode->nlink > 1)
461 printf(", nlinks=%d", cur->inode->nlink);
462 putchar('\n');
463
464 if (cur->child) {
465 assert (cur->type == S_IFDIR);
466 dump_fsnodes(path, cur->child);
467 }
468 }
469 printf("dump_fsnodes: finished %s\n", dir);
470 }
471
472
473 /*
474 * inode_type --
475 * for a given inode type `mode', return a descriptive string.
476 * for most cases, uses inotype() from mtree/misc.c
477 */
478 const char *
479 inode_type(mode_t mode)
480 {
481
482 if (S_ISLNK(mode))
483 return ("symlink"); /* inotype() returns "link"... */
484 return (inotype(mode));
485 }
486
487
488 /*
489 * link_check --
490 * return pointer to fsnode matching `entry's st_ino & st_dev if it exists,
491 * otherwise add `entry' to table and return NULL
492 */
493 static fsinode *
494 link_check(fsinode *entry)
495 {
496 static struct dupnode {
497 uint32_t dev;
498 uint32_t ino;
499 fsinode *dup;
500 } *dups, *newdups;
501 static int ndups, maxdups;
502
503 int i;
504
505 assert (entry != NULL);
506
507 /* XXX; maybe traverse in reverse for speed? */
508 for (i = 0; i < ndups; i++) {
509 if (dups[i].dev == entry->st.st_dev &&
510 dups[i].ino == entry->st.st_ino) {
511 if (debug & DEBUG_WALK_DIR_LINKCHECK)
512 printf("link_check: found [%d,%d]\n",
513 entry->st.st_dev, entry->st.st_ino);
514 return (dups[i].dup);
515 }
516 }
517
518 if (debug & DEBUG_WALK_DIR_LINKCHECK)
519 printf("link_check: no match for [%d, %d]\n",
520 entry->st.st_dev, entry->st.st_ino);
521 if (ndups == maxdups) {
522 if ((newdups = realloc(dups, sizeof(struct dupnode) * (maxdups + 128)))
523 == NULL)
524 err(1, "Memory allocation error");
525 dups = newdups;
526 maxdups += 128;
527 }
528 dups[ndups].dev = entry->st.st_dev;
529 dups[ndups].ino = entry->st.st_ino;
530 dups[ndups].dup = entry;
531 ndups++;
532
533 return (NULL);
534 }
535