1 /* $NetBSD: walk.c,v 1.23 2006/10/10 01:55:45 dbj Exp $ */
4 * Copyright (c) 2001 Wasabi Systems, Inc.
7 * Written by Luke Mewburn for Wasabi Systems, Inc.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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
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.
38 #if HAVE_NBTOOL_CONFIG_H
39 #include "nbtool_config.h"
42 #include <sys/cdefs.h>
43 #if defined(__RCSID) && !defined(__lint)
44 __RCSID("$NetBSD: walk.c,v 1.23 2006/10/10 01:55:45 dbj Exp $");
47 #include <sys/param.h>
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
*);
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.
76 walk_dir(const char *dir
, fsnode
*parent
)
78 fsnode
*first
, *cur
, *prev
;
81 char path
[MAXPATHLEN
+ 1];
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
);
91 while ((dent
= readdir(dirp
)) != NULL
) {
92 if (strcmp(dent
->d_name
, "..") == 0)
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
)
98 errx(1, "Pathname too long.");
99 if (lstat(path
, &stbuf
) == -1)
100 err(1, "Can't lstat `%s'", path
);
102 if (S_ISSOCK(stbuf
.st_mode
& S_IFMT
)) {
103 if (debug
& DEBUG_WALK_DIR_NODE
)
104 printf(" skipping socket %s\n", path
);
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 */
117 } else { /* not "." */
123 if (S_ISDIR(cur
->type
)) {
124 cur
->child
= walk_dir(path
, cur
);
128 if (stbuf
.st_nlink
> 1) {
131 curino
= link_check(cur
->inode
);
132 if (curino
!= NULL
) {
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
);
142 if (S_ISLNK(cur
->type
)) {
143 char slink
[PATH_MAX
+1];
146 llen
= readlink(path
, slink
, sizeof(slink
) - 1);
148 err(1, "Readlink `%s'", path
);
150 if ((cur
->symlink
= strdup(slink
)) == NULL
)
151 err(1, "Memory allocation error");
154 for (cur
= first
; cur
!= NULL
; cur
= cur
->next
)
156 if (closedir(dirp
) == -1)
157 err(1, "Can't closedir `%s'", dir
);
162 create_fsnode(const char *name
, struct stat
*stbuf
)
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
;
178 * Removes node from tree and frees it and all of
182 free_fsnodes(fsnode
*node
)
186 assert(node
!= NULL
);
188 /* for ".", start with actual parent node */
189 if (node
->first
== node
) {
190 assert(node
->name
[0] == '.' && node
->name
[1] == '\0');
192 assert(node
->parent
->child
== node
);
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
;
208 for (cur
= node
; cur
!= NULL
; cur
= next
) {
211 cur
->child
->parent
= NULL
;
212 free_fsnodes(cur
->child
);
214 if (cur
->inode
->nlink
-- == 1)
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.
231 apply_specfile(const char *specfile
, const char *dir
, fsnode
*parent
, int speconly
)
233 struct timeval start
;
237 assert(specfile
!= NULL
);
238 assert(parent
!= NULL
);
240 if (debug
& DEBUG_APPLY_SPECFILE
)
241 printf("apply_specfile: %s, %s %p\n", specfile
, dir
, parent
);
243 /* read in the specfile */
244 if ((fp
= fopen(specfile
, "r")) == NULL
)
245 err(1, "Can't open `%s'", specfile
);
248 TIMER_RESULTS(start
, "spec");
249 if (fclose(fp
) == EOF
)
250 err(1, "Can't close `%s'", specfile
);
252 /* perform some sanity checks */
254 errx(1, "Specfile `%s' did not contain a tree", specfile
);
255 assert(strcmp(root
->name
, ".") == 0);
256 assert(root
->type
== F_DIR
);
258 /* merge in the changes */
259 apply_specdir(dir
, root
, parent
, speconly
);
265 apply_specdir(const char *dir
, NODE
*specnode
, fsnode
*dirnode
, int speconly
)
267 char path
[MAXPATHLEN
+ 1];
271 assert(specnode
!= NULL
);
272 assert(dirnode
!= NULL
);
274 if (debug
& DEBUG_APPLY_SPECFILE
)
275 printf("apply_specdir: %s %p %p\n", dir
, specnode
, dirnode
);
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",
284 apply_specentry(dir
, specnode
, dirnode
);
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
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)
301 if (curnode
== NULL
) {
302 if (debug
& DEBUG_APPLY_SPECONLY
) {
303 printf("apply_specdir: trimming %s/%s %p\n", dir
, curfsnode
->name
, curfsnode
);
305 free_fsnodes(curfsnode
);
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",
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",
323 if (strcmp(curnode
->name
, curfsnode
->name
) == 0)
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 */
333 * don't add optional spec entries
334 * that lack an existing fs entry
336 if ((curnode
->flags
& F_OPT
) &&
337 lstat(path
, &stbuf
) == -1)
340 /* check that enough info is provided */
341 #define NODETEST(t, m) \
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
,
356 if (debug
& DEBUG_APPLY_SPECFILE
)
357 printf("apply_specdir: adding %s\n",
359 /* build minimal fsnode */
360 memset(&stbuf
, 0, sizeof(stbuf
));
361 stbuf
.st_mode
= nodetoino(curnode
->type
);
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
;
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
;
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");
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
);
399 apply_specentry(const char *dir
, NODE
*specnode
, fsnode
*dirnode
)
402 assert(specnode
!= NULL
);
403 assert(dirnode
!= NULL
);
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
));
410 if (debug
& DEBUG_APPLY_SPECENTRY
)
411 printf("apply_specentry: %s/%s\n", dir
, dirnode
->name
);
413 #define ASEPRINT(t, b, o, n) \
414 if (debug & DEBUG_APPLY_SPECENTRY) \
415 printf("\t\t\tchanging %s from " b " to " b "\n", \
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
;
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
);
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
;
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");
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
;
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
;
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
;
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
;
478 dirnode
->flags
|= FSNODE_F_HASSPEC
;
484 * dump the fsnodes from `cur', based in the directory `dir'
487 dump_fsnodes(const char *dir
, fsnode
*root
)
490 char path
[MAXPATHLEN
+ 1];
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
)
497 errx(1, "Pathname too long.");
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
);
507 assert (cur
->symlink
== NULL
);
509 if (cur
->inode
->nlink
> 1)
510 printf(", nlinks=%d", cur
->inode
->nlink
);
514 assert (cur
->type
== S_IFDIR
);
515 dump_fsnodes(path
, cur
->child
);
518 printf("dump_fsnodes: finished %s\n", dir
);
524 * for a given inode type `mode', return a descriptive string.
525 * for most cases, uses inotype() from mtree/misc.c
528 inode_type(mode_t mode
)
532 return ("symlink"); /* inotype() returns "link"... */
533 return (inotype(mode
));
539 * return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
540 * otherwise add `entry' to table and return NULL
542 /* This was borrowed from du.c and tweaked to keep an fsnode
543 * pointer instead. -- dbj@netbsd.org
546 link_check(fsinode
*entry
)
548 static struct entry
{
551 static int htshift
; /* log(allocated size) */
552 static int htmask
; /* allocated size - 1 */
553 static int htused
; /* 2*number of insertions */
556 /* this constant is (1<<64)/((1+sqrt(5))/2)
557 * aka (word size)/(golden ratio)
559 const uint64_t HTCONST
= 11400714819323198485ULL;
560 const int HTBITS
= 64;
562 /* Never store zero in hashtable */
565 /* Extend hash table if necessary, keep load under 0.5 */
566 if (htused
<<1 >= htmask
) {
567 struct entry
*ohtable
;
570 htshift
= 10; /* starting hashtable size */
572 htshift
++; /* exponential hashtable growth */
574 htmask
= (1 << htshift
) - 1;
578 htable
= calloc(htmask
+1, sizeof(*htable
));
580 err(1, "Memory allocation error");
582 /* populate newly allocated hashtable */
585 for (i
= 0; i
<= htmask
>>1; i
++)
587 link_check(ohtable
[i
].data
);
592 /* multiplicative hashing */
593 tmp
= entry
->st
.st_dev
;
595 tmp
|= entry
->st
.st_ino
;
597 h
= tmp
>> (HTBITS
- htshift
);
598 h2
= 1 | ( tmp
>> (HTBITS
- (htshift
<<1) - 1)); /* must be odd */
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
;
606 h
= (h
+ h2
) & htmask
;
609 /* Insert the current entry into hashtable */
610 htable
[h
].data
= entry
;