1#include <dirent.h>
2#include <errno.h>
3#include <ftw.h>
4#include <limits.h>
5#include <string.h>
6#include <sys/stat.h>
7#include <unistd.h>
8
9struct history {
10    struct history* chain;
11    dev_t dev;
12    ino_t ino;
13    int level;
14    int base;
15};
16
17#undef dirfd
18#define dirfd(d) (*(int*)d)
19
20static int do_nftw(char* path, int (*fn)(const char*, const struct stat*, int, struct FTW*),
21                   int fd_limit, int flags, struct history* h) {
22    size_t l = strlen(path), j = l && path[l - 1] == '/' ? l - 1 : l;
23    struct stat st;
24    struct history new;
25    int type;
26    int r;
27    struct FTW lev;
28    char* name;
29
30    if ((flags & FTW_PHYS) ? lstat(path, &st) : stat(path, &st) < 0) {
31        if (!(flags & FTW_PHYS) && errno == ENOENT && !lstat(path, &st))
32            type = FTW_SLN;
33        else if (errno != EACCES)
34            return -1;
35        else
36            type = FTW_NS;
37    } else if (S_ISDIR(st.st_mode)) {
38        if (access(path, R_OK) < 0)
39            type = FTW_DNR;
40        else if (flags & FTW_DEPTH)
41            type = FTW_DP;
42        else
43            type = FTW_D;
44    } else if (S_ISLNK(st.st_mode)) {
45        if (flags & FTW_PHYS)
46            type = FTW_SL;
47        else
48            type = FTW_SLN;
49    } else {
50        type = FTW_F;
51    }
52
53    if ((flags & FTW_MOUNT) && h && st.st_dev != h->dev)
54        return 0;
55
56    new.chain = h;
57    new.dev = st.st_dev;
58    new.ino = st.st_ino;
59    new.level = h ? h->level + 1 : 0;
60    new.base = l + 1;
61
62    lev.level = new.level;
63    lev.base = h ? h->base : (name = strrchr(path, '/')) ? name - path : 0;
64
65    if (!(flags & FTW_DEPTH) && (r = fn(path, &st, type, &lev)))
66        return r;
67
68    for (; h; h = h->chain)
69        if (h->dev == st.st_dev && h->ino == st.st_ino)
70            return 0;
71
72    if ((type == FTW_D || type == FTW_DP) && fd_limit) {
73        DIR* d = opendir(path);
74        if (d) {
75            struct dirent* de;
76            while ((de = readdir(d))) {
77                if (de->d_name[0] == '.' &&
78                    (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2])))
79                    continue;
80                if (strlen(de->d_name) >= PATH_MAX - l) {
81                    errno = ENAMETOOLONG;
82                    closedir(d);
83                    return -1;
84                }
85                path[j] = '/';
86                strcpy(path + j + 1, de->d_name);
87                if ((r = do_nftw(path, fn, fd_limit - 1, flags, &new))) {
88                    closedir(d);
89                    return r;
90                }
91            }
92            closedir(d);
93        } else if (errno != EACCES) {
94            return -1;
95        }
96    }
97
98    path[l] = 0;
99    if ((flags & FTW_DEPTH) && (r = fn(path, &st, type, &lev)))
100        return r;
101
102    return 0;
103}
104
105int nftw(const char* path, int (*fn)(const char*, const struct stat*, int, struct FTW*),
106         int fd_limit, int flags) {
107    size_t l;
108    char pathbuf[PATH_MAX + 1];
109
110    if (fd_limit <= 0)
111        return 0;
112
113    l = strlen(path);
114    if (l > PATH_MAX) {
115        errno = ENAMETOOLONG;
116        return -1;
117    }
118    memcpy(pathbuf, path, l + 1);
119
120    return do_nftw(pathbuf, fn, fd_limit, flags, NULL);
121}
122