1 /* $NetBSD: walk.c,v 1.28 2013/02/03 06:16:53 christos 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.28 2013/02/03 06:16:53 christos Exp $");
47 #include <sys/param.h>
63 static void apply_specdir(const char *, NODE
*, fsnode
*, int);
64 static void apply_specentry(const char *, NODE
*, fsnode
*);
65 static fsnode
*create_fsnode(const char *, const char *, const char *,
67 static fsinode
*link_check(fsinode
*);
72 * build a tree of fsnodes from `root' and `dir', with a parent
73 * fsnode of `parent' (which may be NULL for the root of the tree).
74 * append the tree to a fsnode of `join' if it is not NULL.
75 * each "level" is a directory, with the "." entry guaranteed to be
76 * at the start of the list, and without ".." entries.
79 walk_dir(const char *root
, const char *dir
, fsnode
*parent
, fsnode
*join
,
82 fsnode
*first
, *cur
, *prev
, *last
;
85 char path
[MAXPATHLEN
+ 1];
93 len
= snprintf(path
, sizeof(path
), "%s/%s", root
, dir
);
94 if (len
>= (int)sizeof(path
))
95 errx(1, "Pathname too long.");
96 if (debug
& DEBUG_WALK_DIR
)
97 printf("walk_dir: %s %p\n", path
, parent
);
98 if ((dirp
= opendir(path
)) == NULL
)
99 err(1, "Can't opendir `%s'", path
);
100 rp
= path
+ strlen(root
) + 1;
103 while (cur
->next
!= NULL
)
107 last
= first
= prev
= NULL
;
108 while ((dent
= readdir(dirp
)) != NULL
) {
125 if (debug
& DEBUG_WALK_DIR_NODE
)
126 printf("scanning %s/%s/%s\n", root
, dir
, name
);
127 if (snprintf(path
+ len
, sizeof(path
) - len
, "/%s", name
) >=
128 (int)sizeof(path
) - len
)
129 errx(1, "Pathname too long.");
130 if (lstat(path
, &stbuf
) == -1)
131 err(1, "Can't lstat `%s'", path
);
133 if (S_ISSOCK(stbuf
.st_mode
& S_IFMT
)) {
134 if (debug
& DEBUG_WALK_DIR_NODE
)
135 printf(" skipping socket %s\n", path
);
143 if (cur
== NULL
|| strcmp(cur
->name
, name
) == 0)
152 if (S_ISDIR(cur
->type
) &&
153 S_ISDIR(stbuf
.st_mode
)) {
154 if (debug
& DEBUG_WALK_DIR_NODE
)
155 printf("merging %s with %p\n",
157 cur
->child
= walk_dir(root
, rp
, cur
,
158 cur
->child
, replace
);
162 errx(1, "Can't merge %s `%s' with "
164 inode_type(stbuf
.st_mode
), path
,
165 inode_type(cur
->type
));
167 if (debug
& DEBUG_WALK_DIR_NODE
)
168 printf("replacing %s %s\n",
169 inode_type(stbuf
.st_mode
),
171 if (cur
== join
->next
)
172 join
->next
= cur
->next
;
176 p
->next
!= cur
; p
= p
->next
)
185 cur
= create_fsnode(root
, dir
, name
, &stbuf
);
186 cur
->parent
= parent
;
188 /* ensure "." is at the start of the list */
194 } else { /* not "." */
201 if (S_ISDIR(cur
->type
)) {
202 cur
->child
= walk_dir(root
, rp
, cur
, NULL
,
207 if (stbuf
.st_nlink
> 1) {
210 curino
= link_check(cur
->inode
);
211 if (curino
!= NULL
) {
215 if (debug
& DEBUG_WALK_DIR_LINKCHECK
)
216 printf("link_check: found [%llu, %llu]\n",
217 (unsigned long long)curino
->st
.st_dev
,
218 (unsigned long long)curino
->st
.st_ino
);
221 if (S_ISLNK(cur
->type
)) {
222 char slink
[PATH_MAX
+1];
225 llen
= readlink(path
, slink
, sizeof(slink
) - 1);
227 err(1, "Readlink `%s'", path
);
229 cur
->symlink
= estrdup(slink
);
232 assert(first
!= NULL
);
234 for (cur
= first
->next
; cur
!= NULL
; cur
= cur
->next
)
236 if (closedir(dirp
) == -1)
237 err(1, "Can't closedir `%s/%s'", root
, dir
);
242 create_fsnode(const char *root
, const char *path
, const char *name
,
247 cur
= ecalloc(1, sizeof(*cur
));
248 cur
->path
= estrdup(path
);
249 cur
->name
= estrdup(name
);
250 cur
->inode
= ecalloc(1, sizeof(*cur
->inode
));
252 cur
->type
= stbuf
->st_mode
& S_IFMT
;
253 cur
->inode
->nlink
= 1;
254 cur
->inode
->st
= *stbuf
;
260 * Removes node from tree and frees it and all of
264 free_fsnodes(fsnode
*node
)
268 assert(node
!= NULL
);
270 /* for ".", start with actual parent node */
271 if (node
->first
== node
) {
272 assert(node
->name
[0] == '.' && node
->name
[1] == '\0');
274 assert(node
->parent
->child
== node
);
279 /* Find ourselves in our sibling list and unlink */
280 if (node
->first
!= node
) {
281 for (cur
= node
->first
; cur
->next
; cur
= cur
->next
) {
282 if (cur
->next
== node
) {
283 cur
->next
= node
->next
;
290 for (cur
= node
; cur
!= NULL
; cur
= next
) {
293 cur
->child
->parent
= NULL
;
294 free_fsnodes(cur
->child
);
296 if (cur
->inode
->nlink
-- == 1)
308 * read in the mtree(8) specfile, and apply it to the tree
309 * at dir,parent. parameters in parent on equivalent types
310 * will be changed to those found in specfile, and missing
311 * entries will be added.
314 apply_specfile(const char *specfile
, const char *dir
, fsnode
*parent
, int speconly
)
316 struct timeval start
;
320 assert(specfile
!= NULL
);
321 assert(parent
!= NULL
);
323 if (debug
& DEBUG_APPLY_SPECFILE
)
324 printf("apply_specfile: %s, %s %p\n", specfile
, dir
, parent
);
326 /* read in the specfile */
327 if ((fp
= fopen(specfile
, "r")) == NULL
)
328 err(1, "Can't open `%s'", specfile
);
331 TIMER_RESULTS(start
, "spec");
332 if (fclose(fp
) == EOF
)
333 err(1, "Can't close `%s'", specfile
);
335 /* perform some sanity checks */
337 errx(1, "Specfile `%s' did not contain a tree", specfile
);
338 assert(strcmp(root
->name
, ".") == 0);
339 assert(root
->type
== F_DIR
);
341 /* merge in the changes */
342 apply_specdir(dir
, root
, parent
, speconly
);
348 apply_specdir(const char *dir
, NODE
*specnode
, fsnode
*dirnode
, int speconly
)
350 char path
[MAXPATHLEN
+ 1];
354 assert(specnode
!= NULL
);
355 assert(dirnode
!= NULL
);
357 if (debug
& DEBUG_APPLY_SPECFILE
)
358 printf("apply_specdir: %s %p %p\n", dir
, specnode
, dirnode
);
360 if (specnode
->type
!= F_DIR
)
361 errx(1, "Specfile node `%s/%s' is not a directory",
362 dir
, specnode
->name
);
363 if (dirnode
->type
!= S_IFDIR
)
364 errx(1, "Directory node `%s/%s' is not a directory",
367 apply_specentry(dir
, specnode
, dirnode
);
369 /* Remove any filesystem nodes not found in specfile */
370 /* XXX inefficient. This is O^2 in each dir and it would
371 * have been better never to have walked this part of the tree
376 assert(dirnode
->name
[0] == '.' && dirnode
->name
[1] == '\0');
377 for (curfsnode
= dirnode
->next
; curfsnode
!= NULL
; curfsnode
= next
) {
378 next
= curfsnode
->next
;
379 for (curnode
= specnode
->child
; curnode
!= NULL
;
380 curnode
= curnode
->next
) {
381 if (strcmp(curnode
->name
, curfsnode
->name
) == 0)
384 if (curnode
== NULL
) {
385 if (debug
& DEBUG_APPLY_SPECONLY
) {
386 printf("apply_specdir: trimming %s/%s %p\n", dir
, curfsnode
->name
, curfsnode
);
388 free_fsnodes(curfsnode
);
393 /* now walk specnode->child matching up with dirnode */
394 for (curnode
= specnode
->child
; curnode
!= NULL
;
395 curnode
= curnode
->next
) {
396 if (debug
& DEBUG_APPLY_SPECENTRY
)
397 printf("apply_specdir: spec %s\n",
399 for (curfsnode
= dirnode
->next
; curfsnode
!= NULL
;
400 curfsnode
= curfsnode
->next
) {
401 #if 0 /* too verbose for now */
402 if (debug
& DEBUG_APPLY_SPECENTRY
)
403 printf("apply_specdir: dirent %s\n",
406 if (strcmp(curnode
->name
, curfsnode
->name
) == 0)
409 if ((size_t)snprintf(path
, sizeof(path
), "%s/%s",
410 dir
, curnode
->name
) >= sizeof(path
))
411 errx(1, "Pathname too long.");
412 if (curfsnode
== NULL
) { /* need new entry */
416 * don't add optional spec entries
417 * that lack an existing fs entry
419 if ((curnode
->flags
& F_OPT
) &&
420 lstat(path
, &stbuf
) == -1)
423 /* check that enough info is provided */
424 #define NODETEST(t, m) \
426 errx(1, "`%s': %s not provided", path, m)
427 NODETEST(curnode
->flags
& F_TYPE
, "type");
428 NODETEST(curnode
->flags
& F_MODE
, "mode");
429 /* XXX: require F_TIME ? */
430 NODETEST(curnode
->flags
& F_GID
||
431 curnode
->flags
& F_GNAME
, "group");
432 NODETEST(curnode
->flags
& F_UID
||
433 curnode
->flags
& F_UNAME
, "user");
434 if (curnode
->type
== F_BLOCK
|| curnode
->type
== F_CHAR
)
435 NODETEST(curnode
->flags
& F_DEV
,
439 if (debug
& DEBUG_APPLY_SPECFILE
)
440 printf("apply_specdir: adding %s\n",
442 /* build minimal fsnode */
443 memset(&stbuf
, 0, sizeof(stbuf
));
444 stbuf
.st_mode
= nodetoino(curnode
->type
);
446 stbuf
.st_mtime
= stbuf
.st_atime
=
447 stbuf
.st_ctime
= start_time
.tv_sec
;
448 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
449 stbuf
.st_mtimensec
= stbuf
.st_atimensec
=
450 stbuf
.st_ctimensec
= start_time
.tv_nsec
;
452 curfsnode
= create_fsnode(".", ".", curnode
->name
,
454 curfsnode
->parent
= dirnode
->parent
;
455 curfsnode
->first
= dirnode
;
456 curfsnode
->next
= dirnode
->next
;
457 dirnode
->next
= curfsnode
;
458 if (curfsnode
->type
== S_IFDIR
) {
459 /* for dirs, make "." entry as well */
460 curfsnode
->child
= create_fsnode(".", ".", ".",
462 curfsnode
->child
->parent
= curfsnode
;
463 curfsnode
->child
->first
= curfsnode
->child
;
465 if (curfsnode
->type
== S_IFLNK
) {
466 assert(curnode
->slink
!= NULL
);
467 /* for symlinks, copy the target */
468 curfsnode
->symlink
= estrdup(curnode
->slink
);
471 apply_specentry(dir
, curnode
, curfsnode
);
472 if (curnode
->type
== F_DIR
) {
473 if (curfsnode
->type
!= S_IFDIR
)
474 errx(1, "`%s' is not a directory", path
);
475 assert (curfsnode
->child
!= NULL
);
476 apply_specdir(path
, curnode
, curfsnode
->child
, speconly
);
482 apply_specentry(const char *dir
, NODE
*specnode
, fsnode
*dirnode
)
485 assert(specnode
!= NULL
);
486 assert(dirnode
!= NULL
);
488 if (nodetoino(specnode
->type
) != dirnode
->type
)
489 errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
490 dir
, specnode
->name
, inode_type(nodetoino(specnode
->type
)),
491 inode_type(dirnode
->type
));
493 if (debug
& DEBUG_APPLY_SPECENTRY
)
494 printf("apply_specentry: %s/%s\n", dir
, dirnode
->name
);
496 #define ASEPRINT(t, b, o, n) \
497 if (debug & DEBUG_APPLY_SPECENTRY) \
498 printf("\t\t\tchanging %s from " b " to " b "\n", \
501 if (specnode
->flags
& (F_GID
| F_GNAME
)) {
502 ASEPRINT("gid", "%d",
503 dirnode
->inode
->st
.st_gid
, specnode
->st_gid
);
504 dirnode
->inode
->st
.st_gid
= specnode
->st_gid
;
506 if (specnode
->flags
& F_MODE
) {
507 ASEPRINT("mode", "%#o",
508 dirnode
->inode
->st
.st_mode
& ALLPERMS
, specnode
->st_mode
);
509 dirnode
->inode
->st
.st_mode
&= ~ALLPERMS
;
510 dirnode
->inode
->st
.st_mode
|= (specnode
->st_mode
& ALLPERMS
);
512 /* XXX: ignoring F_NLINK for now */
513 if (specnode
->flags
& F_SIZE
) {
514 ASEPRINT("size", "%lld",
515 (long long)dirnode
->inode
->st
.st_size
,
516 (long long)specnode
->st_size
);
517 dirnode
->inode
->st
.st_size
= specnode
->st_size
;
519 if (specnode
->flags
& F_SLINK
) {
520 assert(dirnode
->symlink
!= NULL
);
521 assert(specnode
->slink
!= NULL
);
522 ASEPRINT("symlink", "%s", dirnode
->symlink
, specnode
->slink
);
523 free(dirnode
->symlink
);
524 dirnode
->symlink
= estrdup(specnode
->slink
);
526 if (specnode
->flags
& F_TIME
) {
527 ASEPRINT("time", "%ld",
528 (long)dirnode
->inode
->st
.st_mtime
,
529 (long)specnode
->st_mtimespec
.tv_sec
);
530 dirnode
->inode
->st
.st_mtime
= specnode
->st_mtimespec
.tv_sec
;
531 dirnode
->inode
->st
.st_atime
= specnode
->st_mtimespec
.tv_sec
;
532 dirnode
->inode
->st
.st_ctime
= start_time
.tv_sec
;
533 #if HAVE_STRUCT_STAT_ST_MTIMENSEC
534 dirnode
->inode
->st
.st_mtimensec
= specnode
->st_mtimespec
.tv_nsec
;
535 dirnode
->inode
->st
.st_atimensec
= specnode
->st_mtimespec
.tv_nsec
;
536 dirnode
->inode
->st
.st_ctimensec
= start_time
.tv_nsec
;
539 if (specnode
->flags
& (F_UID
| F_UNAME
)) {
540 ASEPRINT("uid", "%d",
541 dirnode
->inode
->st
.st_uid
, specnode
->st_uid
);
542 dirnode
->inode
->st
.st_uid
= specnode
->st_uid
;
544 #if HAVE_STRUCT_STAT_ST_FLAGS
545 if (specnode
->flags
& F_FLAGS
) {
546 ASEPRINT("flags", "%#lX",
547 (unsigned long)dirnode
->inode
->st
.st_flags
,
548 (unsigned long)specnode
->st_flags
);
549 dirnode
->inode
->st
.st_flags
= specnode
->st_flags
;
552 if (specnode
->flags
& F_DEV
) {
553 ASEPRINT("rdev", "%#llx",
554 (unsigned long long)dirnode
->inode
->st
.st_rdev
,
555 (unsigned long long)specnode
->st_rdev
);
556 dirnode
->inode
->st
.st_rdev
= specnode
->st_rdev
;
560 dirnode
->flags
|= FSNODE_F_HASSPEC
;
566 * dump the fsnodes from `cur'
569 dump_fsnodes(fsnode
*root
)
572 char path
[MAXPATHLEN
+ 1];
574 printf("dump_fsnodes: %s %p\n", root
->path
, root
);
575 for (cur
= root
; cur
!= NULL
; cur
= cur
->next
) {
576 if (snprintf(path
, sizeof(path
), "%s/%s", cur
->path
,
577 cur
->name
) >= (int)sizeof(path
))
578 errx(1, "Pathname too long.");
580 if (debug
& DEBUG_DUMP_FSNODES_VERBOSE
)
581 printf("cur=%8p parent=%8p first=%8p ",
582 cur
, cur
->parent
, cur
->first
);
583 printf("%7s: %s", inode_type(cur
->type
), path
);
584 if (S_ISLNK(cur
->type
)) {
585 assert(cur
->symlink
!= NULL
);
586 printf(" -> %s", cur
->symlink
);
588 assert (cur
->symlink
== NULL
);
590 if (cur
->inode
->nlink
> 1)
591 printf(", nlinks=%d", cur
->inode
->nlink
);
595 assert (cur
->type
== S_IFDIR
);
596 dump_fsnodes(cur
->child
);
599 printf("dump_fsnodes: finished %s/%s\n", root
->path
, root
->name
);
605 * for a given inode type `mode', return a descriptive string.
606 * for most cases, uses inotype() from mtree/misc.c
609 inode_type(mode_t mode
)
613 return ("symlink"); /* inotype() returns "link"... */
614 return (inotype(mode
));
620 * return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
621 * otherwise add `entry' to table and return NULL
623 /* This was borrowed from du.c and tweaked to keep an fsnode
624 * pointer instead. -- dbj@netbsd.org
627 link_check(fsinode
*entry
)
629 static struct entry
{
632 static int htshift
; /* log(allocated size) */
633 static int htmask
; /* allocated size - 1 */
634 static int htused
; /* 2*number of insertions */
637 /* this constant is (1<<64)/((1+sqrt(5))/2)
638 * aka (word size)/(golden ratio)
640 const uint64_t HTCONST
= 11400714819323198485ULL;
641 const int HTBITS
= 64;
643 /* Never store zero in hashtable */
646 /* Extend hash table if necessary, keep load under 0.5 */
647 if (htused
<<1 >= htmask
) {
648 struct entry
*ohtable
;
651 htshift
= 10; /* starting hashtable size */
653 htshift
++; /* exponential hashtable growth */
655 htmask
= (1 << htshift
) - 1;
659 htable
= ecalloc(htmask
+1, sizeof(*htable
));
660 /* populate newly allocated hashtable */
663 for (i
= 0; i
<= htmask
>>1; i
++)
665 link_check(ohtable
[i
].data
);
670 /* multiplicative hashing */
671 tmp
= entry
->st
.st_dev
;
673 tmp
|= entry
->st
.st_ino
;
675 h
= tmp
>> (HTBITS
- htshift
);
676 h2
= 1 | ( tmp
>> (HTBITS
- (htshift
<<1) - 1)); /* must be odd */
678 /* open address hashtable search with double hash probing */
679 while (htable
[h
].data
) {
680 if ((htable
[h
].data
->st
.st_ino
== entry
->st
.st_ino
) &&
681 (htable
[h
].data
->st
.st_dev
== entry
->st
.st_dev
)) {
682 return htable
[h
].data
;
684 h
= (h
+ h2
) & htmask
;
687 /* Insert the current entry into hashtable */
688 htable
[h
].data
= entry
;