#include #include #include #include /* * path history */ struct Key { ino_t ino; dev_t dev; }; #define hash(k) ((int32)k.ino ^ (int32)k.dev) #define equal(k1, k2) (k1.ino == k2.ino && k1.dev == k2.dev) struct fs·History { SET_STRUCT_BODY(struct Key); }; 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); } static void delete(fs·History *h) { SET_FREE(h, sys·Memory, nil); } #undef hash #undef equal static char * strcpyn(char *dst, char *src, int n) { while(*src && n-- > 0) *dst++ = *src++; *dst = 0; return dst; } /* * main functions */ int fs·init(fs·Walker *fs, char *path) { fs->base = fs->end = fs->path; if (!path || !path[0]) { path = getcwd(fs->path, arrlen(fs->path)); if (!path) return 1; fs->end += strlen(path); } else fs->end = strcpyn(fs->base, path, arrlen(fs->path)); if (fs->path[0] != '/') fs->fd = AT_FDCWD; if (!fs->hist && !(fs->flags & fs·nolinks)) fs->hist = calloc(1, sizeof(*fs->hist)); return 0; } void fs·fini(fs·Walker *fs) { if (fs->hist) { delete(fs->hist); free(fs->hist); } } 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 = strcpyn(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); }