aboutsummaryrefslogtreecommitdiff
path: root/src/base/fs/walk.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/fs/walk.c')
-rw-r--r--src/base/fs/walk.c119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/base/fs/walk.c b/src/base/fs/walk.c
new file mode 100644
index 0000000..d528896
--- /dev/null
+++ b/src/base/fs/walk.c
@@ -0,0 +1,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);
+}