aboutsummaryrefslogtreecommitdiff
path: root/sys/base/fs/walk.c
blob: d528896dc0e517ce198768f19cdf184d10d9d55c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "internal.h"

#define hash(k) ((int32)k.ino ^ (int32)k.dev)
#define equal(k1, k2) (k1.ino == k2.ino && k1.dev == k2.dev)

static
int
morehistory(fs·History *h, int n)
{
    SET_GROW(h, struct Key, n, hash, sys·Memory, nil);
}

static
int
addentry(fs·History *h, struct Key key, int *err)
{
    SET_PUT(h, key, hash, equal, morehistory, err);
}

static
void
forget(fs·History *h)
{
    if (!h)
        return;

    SET_RESET(h);
}

void
fs·walk(fs·Walker *fs)
{
    char *e, *b;
    DIR *dir;
    int new, fd, ofd, flags;
    fs·History *h;
    struct dirent *d;
    io·Stat cwd;
    struct fs·Entry *it;

    flags = 0;
    if(fs->flags & fs·nolinks)
        flags |= AT_SYMLINK_NOFOLLOW;

    /* get info for base relative to current fd */
    if(fstatat(fs->fd, fs->base, &cwd, flags) < 0){
        if(fs->flags & fs·verbose)
            errorf("stat: %s", fs->path);
        return;
    }

    /* if we hit a file, finish! */
    if(!S_ISDIR(cwd.st_mode)) {
        fs->func(fs->data, fs->base, fs->path, &cwd);
        return;
    }

    /* have we been here before? (cycle detection) */
    /* if not, add to our path history */
    if (!(fs->flags & fs·nolinks)) {
        addentry(fs->hist, (struct Key){.dev=cwd.st_dev, .ino=cwd.st_ino}, &new);
        if (!new)
            return;
    }

    /* 
     * operate on directory first if preorder traversal 
     * truncate recursion if callback returns an error code
     */ 
    if (fs->flags & fs·preorder) {
        if (fs->func(fs->data, fs->base, fs->path, &cwd))
            return;
    }

    /* open directory */
    if(!fs->max || fs->lev + 1 < fs->max) {
        fd = openat(fs->fd, fs->base, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
        if (fd < 0)
            errorf("open %s:", fs->path);

        if (!(dir=fdopendir(fd))) {
            if(fs->flags & fs·verbose)
                errorf("fdopendir: %s", fs->path);
            return;
        }

        ofd = fs->fd, fs->fd = fd;

        /* traverse children */
        e = fs->end, b = fs->base;
        if (fs->end[-1] != '/')
            *fs->end++ = '/';

        fs->base = fs->end;
        while((d = readdir(dir))) {
            if(*d->d_name == '.')
            if(d->d_name[1] == 0 ||                       /* . */ 
               (d->d_name[1] == '.' && d->d_name[2] == 0)) /* .. */ 
                continue;

            fs->end = str·copyn(fs->base, d->d_name, arrend(fs->path) - fs->base);

            fs->lev++;
            fs·walk(fs);
            fs->lev--;
        }
        *e      = 0;
        fs->fd  = ofd;
        fs->end = e, fs->base = b;
        closedir(dir);
    }

    /* operate on directory if postorder (default) traversal */
    if (!(fs->flags & fs·preorder))
        fs->func(fs->data, fs->base, fs->path, &cwd);

    if (!fs->lev)
        forget(fs->hist);
}