1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
5 * The bftw() implementation consists of the following components:
7 * - struct bftw_file: A file that has been encountered during the traversal.
8 * They have reference-counted links to their parents in the directory tree.
10 * - struct bftw_list: A linked list of bftw_file's.
12 * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors,
13 * used for openat() to minimize the amount of path re-traversals.
15 * - struct bftw_state: Represents the current state of the traversal, allowing
16 * various helper functions to take fewer parameters.
37 /** Caching bfs_stat(). */
38 static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) {
42 } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) {
43 cache->buf = &cache->storage;
45 } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) {
46 // This matches the behavior of FTS_WHITEOUT on BSD
47 memset(&cache->storage, 0, sizeof(cache->storage));
48 cache->storage.mode = S_IFWHT;
49 cache->buf = &cache->storage;
59 const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
60 struct BFTW *mutbuf = (struct BFTW *)ftwbuf;
61 const struct bfs_stat *ret;
63 if (flags & BFS_STAT_NOFOLLOW) {
64 ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW);
65 if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) {
66 // Non-link, so share stat info
67 mutbuf->stat_cache.buf = ret;
70 ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW);
71 if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) {
72 ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW);
79 const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
80 if (flags & BFS_STAT_NOFOLLOW) {
81 return ftwbuf->lstat_cache.buf;
82 } else if (ftwbuf->stat_cache.buf) {
83 return ftwbuf->stat_cache.buf;
84 } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) {
85 return ftwbuf->lstat_cache.buf;
91 enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) {
92 if (flags & BFS_STAT_NOFOLLOW) {
93 if (ftwbuf->type == BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) {
96 } else if (flags & BFS_STAT_TRYFOLLOW) {
97 if (ftwbuf->type != BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW)) {
101 if (ftwbuf->type != BFS_LNK) {
103 } else if (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW) {
108 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags);
110 return bfs_mode_to_type(statbuf->mode);
120 /** The parent directory, if any. */
121 struct bftw_file *parent;
122 /** The root under which this file was found. */
123 struct bftw_file *root;
125 /** The next file to open/close/visit. */
126 struct bftw_file *next;
127 /** The next directory to read. */
128 struct { struct bftw_file *next; } to_read;
130 /** LRU list node. */
132 struct bftw_file *prev;
133 struct bftw_file *next;
136 /** This file's depth in the walk. */
138 /** Reference count (for ->parent). */
141 /** Pin count (for ->fd). */
143 /** An open descriptor to this file, or -1. */
145 /** Whether this file has a pending ioq request. */
147 /** An open directory for this file, if any. */
150 /** This file's type, if known. */
152 /** The device number, for cycle detection. */
154 /** The inode number, for cycle detection. */
157 /** The offset of this file in the full path. */
159 /** The length of the file's name. */
161 /** The file's name. */
166 * A linked list of bftw_file's.
169 struct bftw_file *head;
170 struct bftw_file **tail;
174 * A cache of open directories.
177 /** The head of the LRU list. */
178 struct bftw_file *head;
179 /** The tail of the LRU list. */
180 struct bftw_file *tail;
181 /** The insertion target for the LRU list. */
182 struct bftw_file *target;
183 /** The remaining capacity of the LRU list. */
186 /** bftw_file arena. */
188 /** bfs_dir arena. */
190 /** Remaining bfs_dir capacity. */
194 /** Initialize a cache. */
195 static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) {
197 cache->target = NULL;
198 cache->capacity = capacity;
200 VARENA_INIT(&cache->files, struct bftw_file, name);
201 bfs_dir_arena(&cache->dirs);
203 cache->dirlimit = capacity - 1;
204 if (cache->dirlimit > 1024) {
205 cache->dirlimit = 1024;
209 /** Allocate a directory. */
210 static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) {
211 if (cache->dirlimit == 0) {
217 return arena_alloc(&cache->dirs);
220 /** Free a directory. */
221 static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) {
223 arena_free(&cache->dirs, dir);
226 /** Remove a bftw_file from the LRU list. */
227 static void bftw_lru_remove(struct bftw_cache *cache, struct bftw_file *file) {
228 if (cache->target == file) {
229 cache->target = file->lru.prev;
232 LIST_REMOVE(cache, file, lru);
235 /** Remove a bftw_file from the cache. */
236 static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) {
237 bftw_lru_remove(cache, file);
241 /** Close a bftw_file. */
242 static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) {
243 bfs_assert(file->fd >= 0);
244 bfs_assert(file->pincount == 0);
247 bfs_closedir(file->dir);
248 bftw_freedir(cache, file->dir);
255 bftw_cache_remove(cache, file);
258 /** Pop the least recently used directory from the cache. */
259 static int bftw_cache_pop(struct bftw_cache *cache) {
260 struct bftw_file *file = cache->tail;
265 bftw_file_close(cache, file);
269 /** Add a bftw_file to the LRU list. */
270 static void bftw_lru_add(struct bftw_cache *cache, struct bftw_file *file) {
271 bfs_assert(file->fd >= 0);
273 LIST_INSERT(cache, cache->target, file, lru);
275 // Prefer to keep the root paths open by keeping them at the head of the list
276 if (file->depth == 0) {
277 cache->target = file;
281 /** Add a bftw_file to the cache. */
282 static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) {
283 bfs_assert(file->fd >= 0);
285 if (cache->capacity == 0 && bftw_cache_pop(cache) != 0) {
286 bftw_file_close(cache, file);
291 bfs_assert(cache->capacity > 0);
294 bftw_lru_add(cache, file);
298 /** Pin a cache entry so it won't be closed. */
299 static void bftw_cache_pin(struct bftw_cache *cache, struct bftw_file *file) {
300 bfs_assert(file->fd >= 0);
302 if (file->pincount++ == 0) {
303 bftw_lru_remove(cache, file);
307 /** Unpin a cache entry. */
308 static void bftw_cache_unpin(struct bftw_cache *cache, struct bftw_file *file) {
309 bfs_assert(file->fd >= 0);
310 bfs_assert(file->pincount > 0);
312 if (--file->pincount == 0) {
313 bftw_lru_add(cache, file);
317 /** Compute the name offset of a child path. */
318 static size_t bftw_child_nameoff(const struct bftw_file *parent) {
319 size_t ret = parent->nameoff + parent->namelen;
320 if (parent->name[parent->namelen - 1] != '/') {
326 /** Destroy a cache. */
327 static void bftw_cache_destroy(struct bftw_cache *cache) {
328 bfs_assert(LIST_EMPTY(cache));
329 bfs_assert(!cache->target);
331 varena_destroy(&cache->files);
332 arena_destroy(&cache->dirs);
335 /** Create a new bftw_file. */
336 static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_file *parent, const char *name) {
337 size_t namelen = strlen(name);
338 struct bftw_file *file = varena_alloc(&cache->files, namelen + 1);
343 file->parent = parent;
346 file->root = parent->root;
347 file->depth = parent->depth + 1;
348 file->nameoff = bftw_child_nameoff(parent);
356 SLIST_ITEM_INIT(file);
357 SLIST_ITEM_INIT(file, to_read);
358 LIST_ITEM_INIT(file, lru);
363 file->ioqueued = false;
366 file->type = BFS_UNKNOWN;
370 file->namelen = namelen;
371 memcpy(file->name, name, namelen + 1);
376 /** Associate an open directory with a bftw_file. */
377 static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, struct bfs_dir *dir) {
378 bfs_assert(!file->dir);
382 bfs_assert(file->fd == bfs_dirfd(dir));
384 file->fd = bfs_dirfd(dir);
385 bftw_cache_add(cache, file);
388 bftw_cache_pin(cache, file);
391 /** Free a bftw_file. */
392 static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) {
393 bfs_assert(file->refcount == 0);
396 bftw_file_close(cache, file);
399 varena_free(&cache->files, file, file->namelen + 1);
403 * Holds the current state of the bftw() traversal.
406 /** The path(s) to start from. */
408 /** The number of starting paths. */
410 /** bftw() callback. */
411 bftw_callback *callback;
412 /** bftw() callback data. */
415 enum bftw_flags flags;
416 /** Search strategy. */
417 enum bftw_strategy strategy;
418 /** The mount table. */
419 const struct bfs_mtab *mtab;
420 /** bfs_opendir() flags. */
421 enum bfs_dir_flags dir_flags;
423 /** The appropriate errno value, if any. */
426 /** The cache of open directories. */
427 struct bftw_cache cache;
429 /** The async I/O queue. */
431 /** The number of I/O threads. */
433 /** Tracks the imbalance between main thread and background I/O. */
436 /** A batch of directories to open. */
437 struct bftw_list dir_batch;
438 /** The queue of directories to open. */
439 struct bftw_list to_open;
440 /** The queue of directories to read. */
441 struct bftw_list to_read;
442 /** The queue of unpinned directories to unwrap. */
443 struct bftw_list to_close;
445 /** A batch of files to enqueue. */
446 struct bftw_list file_batch;
447 /** The queue of files to visit. */
448 struct bftw_list to_visit;
450 /** The current path. */
452 /** The current file. */
453 struct bftw_file *file;
454 /** The previous file. */
455 struct bftw_file *previous;
457 /** The currently open directory. */
459 /** The current directory entry. */
460 struct bfs_dirent *de;
461 /** Storage for the directory entry. */
462 struct bfs_dirent de_storage;
463 /** Any error encountered while reading the directory. */
466 /** Extra data about the current file. */
470 /** Check if we have to buffer files before visiting them. */
471 static bool bftw_must_buffer(const struct bftw_state *state) {
472 if (state->flags & BFTW_SORT) {
473 // Have to buffer the files to sort them
477 if (state->strategy == BFTW_DFS && state->nthreads == 0) {
478 // Without buffering, we would get a not-quite-depth-first
488 // This is okay for iterative deepening, since the caller only
489 // sees files at the target depth. We also deem it okay for
490 // parallel searches, since the order is unpredictable anyway.
497 /** Initialize the bftw() state. */
498 static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) {
499 state->paths = args->paths;
500 state->npaths = args->npaths;
501 state->callback = args->callback;
502 state->ptr = args->ptr;
503 state->flags = args->flags;
504 state->strategy = args->strategy;
505 state->mtab = args->mtab;
506 state->dir_flags = 0;
509 if (args->nopenfd < 2) {
514 size_t nopenfd = args->nopenfd;
515 size_t qdepth = 4096;
516 size_t nthreads = args->nthreads;
519 // io_uring uses one fd per ring, ioq uses one ring per thread
520 if (nthreads >= nopenfd - 1) {
521 nthreads = nopenfd - 2;
526 bftw_cache_init(&state->cache, nopenfd);
529 state->ioq = ioq_create(qdepth, nthreads);
536 state->nthreads = nthreads;
538 if (bftw_must_buffer(state)) {
539 state->flags |= BFTW_BUFFER;
542 if (state->flags & BFTW_WHITEOUTS) {
543 state->dir_flags |= BFS_DIR_WHITEOUTS;
546 state->imbalance = 0;
548 SLIST_INIT(&state->dir_batch);
549 SLIST_INIT(&state->to_open);
550 SLIST_INIT(&state->to_read);
551 SLIST_INIT(&state->to_close);
553 SLIST_INIT(&state->file_batch);
554 SLIST_INIT(&state->to_visit);
558 state->previous = NULL;
567 /** Unpin a directory, and possibly queue it for unwrapping. */
568 static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, bool force) {
569 bftw_cache_unpin(&state->cache, file);
571 if (file->dir && (force || file->pincount == 0)) {
572 if (!SLIST_ATTACHED(&state->to_close, file)) {
573 SLIST_APPEND(&state->to_close, file);
578 /** Adjust the I/O queue balance. */
579 static void bftw_ioq_balance(struct bftw_state *state, long delta) {
580 // Avoid signed overflow
581 state->imbalance = (unsigned long)state->imbalance + (unsigned long)delta;
584 /** Pop a response from the I/O queue. */
585 static int bftw_ioq_pop(struct bftw_state *state, bool block) {
586 struct ioq *ioq = state->ioq;
591 struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq);
596 struct bftw_cache *cache = &state->cache;
597 struct bftw_file *file;
598 struct bftw_file *parent;
601 enum ioq_op op = ent->op;
609 dir = ent->closedir.dir;
610 bftw_freedir(cache, dir);
615 file->ioqueued = false;
618 parent = file->parent;
620 bftw_unpin_dir(state, parent, false);
623 dir = ent->opendir.dir;
625 bftw_file_set_dir(cache, file, dir);
627 bftw_freedir(cache, dir);
630 if (!(state->flags & BFTW_SORT)) {
631 SLIST_APPEND(&state->to_read, file, to_read);
640 /** Try to reserve space in the I/O queue. */
641 static int bftw_ioq_reserve(struct bftw_state *state) {
642 struct ioq *ioq = state->ioq;
647 // With only one background thread, we should balance I/O between it and
648 // the main thread. With more than one background thread, it's faster
649 // to wait on background I/O than it is to do it on the main thread.
650 bool balance = state->nthreads <= 1;
651 if (balance && state->imbalance < 0) {
655 if (ioq_capacity(ioq) > 0) {
659 if (bftw_ioq_pop(state, !balance) < 0) {
666 /** Try to reserve space in the cache. */
667 static int bftw_cache_reserve(struct bftw_state *state) {
668 struct bftw_cache *cache = &state->cache;
669 if (cache->capacity > 0) {
673 while (bftw_ioq_pop(state, true) >= 0) {
674 if (cache->capacity > 0) {
679 if (bftw_cache_pop(cache) != 0) {
684 bfs_assert(cache->capacity > 0);
688 /** Open a bftw_file relative to another one. */
689 static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, struct bftw_file *base, const char *at_path) {
690 bfs_assert(file->fd < 0);
692 struct bftw_cache *cache = &state->cache;
694 int at_fd = AT_FDCWD;
696 bftw_cache_pin(cache, base);
701 if (bftw_cache_reserve(state) != 0) {
705 int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY;
706 fd = openat(at_fd, at_path, flags);
708 if (fd < 0 && errno == EMFILE) {
709 if (bftw_cache_pop(cache) == 0) {
710 fd = openat(at_fd, at_path, flags);
717 bftw_cache_unpin(cache, base);
722 bftw_cache_add(cache, file);
728 /** Open a bftw_file. */
729 static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, const char *path) {
730 // Find the nearest open ancestor
731 struct bftw_file *base = file;
734 } while (base && base->fd < 0);
736 const char *at_path = path;
738 at_path += bftw_child_nameoff(base);
741 int fd = bftw_file_openat(state, file, base, at_path);
742 if (fd >= 0 || errno != ENAMETOOLONG) {
746 // Handle ENAMETOOLONG by manually traversing the path component-by-component
747 struct bftw_list parents;
748 SLIST_INIT(&parents);
750 struct bftw_file *cur;
751 for (cur = file; cur != base; cur = cur->parent) {
752 SLIST_PREPEND(&parents, cur);
755 while ((cur = SLIST_POP(&parents))) {
756 if (!cur->parent || cur->parent->fd >= 0) {
757 bftw_file_openat(state, cur, cur->parent, cur->name);
764 /** Close a directory, asynchronously if possible. */
765 static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) {
766 if (bftw_ioq_reserve(state) == 0) {
767 if (ioq_closedir(state->ioq, dir, NULL) == 0) {
772 struct bftw_cache *cache = &state->cache;
773 int ret = bfs_closedir(dir);
774 bftw_freedir(cache, dir);
779 /** Close a file descriptor, asynchronously if possible. */
780 static int bftw_ioq_close(struct bftw_state *state, int fd) {
781 if (bftw_ioq_reserve(state) == 0) {
782 if (ioq_close(state->ioq, fd, NULL) == 0) {
787 struct bftw_cache *cache = &state->cache;
788 int ret = xclose(fd);
793 /** Close a file, asynchronously if possible. */
794 static int bftw_close(struct bftw_state *state, struct bftw_file *file) {
795 bfs_assert(file->fd >= 0);
796 bfs_assert(file->pincount == 0);
798 struct bfs_dir *dir = file->dir;
801 bftw_lru_remove(&state->cache, file);
806 return bftw_ioq_closedir(state, dir);
808 return bftw_ioq_close(state, fd);
812 /** Free an open directory. */
813 static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) {
814 struct bfs_dir *dir = file->dir;
819 struct bftw_cache *cache = &state->cache;
821 // Try to keep an open fd if any children exist
822 bool reffed = file->refcount > 1;
823 // Keep the fd the same if it's pinned
824 bool pinned = file->pincount > 0;
826 #if BFS_USE_UNWRAPDIR
827 if (reffed || pinned) {
829 bftw_freedir(cache, dir);
840 return bftw_close(state, file);
843 // Make room for dup()
844 bftw_cache_pin(cache, file);
845 int ret = bftw_cache_reserve(state);
846 bftw_cache_unpin(cache, file);
851 int fd = dup_cloexec(file->fd);
859 return bftw_ioq_closedir(state, dir);
862 /** Open a directory asynchronously. */
863 static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) {
864 if (bftw_ioq_reserve(state) != 0) {
869 struct bftw_cache *cache = &state->cache;
870 struct bftw_file *parent = file->parent;
876 bftw_cache_pin(cache, parent);
879 if (bftw_cache_reserve(state) != 0) {
883 struct bfs_dir *dir = bftw_allocdir(cache);
888 if (ioq_opendir(state->ioq, dir, dfd, file->name, state->dir_flags, file) != 0) {
892 file->ioqueued = true;
894 bftw_ioq_balance(state, -1);
898 bftw_freedir(cache, dir);
901 bftw_cache_unpin(cache, parent);
907 /** Open a batch of directories asynchronously. */
908 static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) {
909 for_slist (struct bftw_file, dir, queue) {
910 if (bftw_ioq_opendir(state, dir) != 0) {
917 /** Push a directory onto the queue. */
918 static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) {
919 bfs_assert(file->type == BFS_DIR);
921 struct bftw_list *queue;
922 if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) {
923 // In breadth-first mode, or if we're already buffering files,
924 // we can push directly to the to_open queue
925 queue = &state->to_open;
927 // For a depth-first, unbuffered search, add directories to a
928 // batch, then push the patch to the front of the queue
929 queue = &state->dir_batch;
932 SLIST_APPEND(queue, file);
934 if (state->flags & BFTW_SORT) {
935 // When sorting, directories are kept in order on the to_read
936 // list; otherwise, they are only added once they are open
937 SLIST_APPEND(&state->to_read, file, to_read);
940 bftw_ioq_opendirs(state, queue);
943 /** Pop a directory to read from the queue. */
944 static bool bftw_pop_dir(struct bftw_state *state) {
945 bfs_assert(!state->file);
947 struct bftw_cache *cache = &state->cache;
948 bool have_files = !SLIST_EMPTY(&state->to_visit);
950 if (state->flags & BFTW_SORT) {
951 // Keep strict breadth-first order when sorting
952 if (state->strategy == BFTW_BFS && have_files) {
956 while (SLIST_EMPTY(&state->to_read)) {
957 // Block if we have no other files/dirs to visit, or no room in the cache
958 bool have_dirs = !SLIST_EMPTY(&state->to_open);
959 bool have_room = cache->capacity > 0 && cache->dirlimit > 0;
960 bool block = !(have_dirs || have_files) || !have_room;
962 if (bftw_ioq_pop(state, block) < 0) {
968 struct bftw_file *file = SLIST_POP(&state->to_read, to_read);
969 if (!file || file == state->to_open.head) {
970 file = SLIST_POP(&state->to_open);
976 while (file->ioqueued) {
977 bftw_ioq_pop(state, true);
984 /** Pop a file to visit from the queue. */
985 static bool bftw_pop_file(struct bftw_state *state) {
986 bfs_assert(!state->file);
987 state->file = SLIST_POP(&state->to_visit);
991 /** Build the path to the current file. */
992 static int bftw_build_path(struct bftw_state *state, const char *name) {
993 const struct bftw_file *file = state->file;
995 size_t pathlen = file ? file->nameoff + file->namelen : 0;
996 if (dstresize(&state->path, pathlen) != 0) {
997 state->error = errno;
1001 // Try to find a common ancestor with the existing path
1002 const struct bftw_file *ancestor = state->previous;
1003 while (ancestor && ancestor->depth > file->depth) {
1004 ancestor = ancestor->parent;
1007 // Build the path backwards
1008 while (file && file != ancestor) {
1009 if (file->nameoff > 0) {
1010 state->path[file->nameoff - 1] = '/';
1012 memcpy(state->path + file->nameoff, file->name, file->namelen);
1014 if (ancestor && ancestor->depth == file->depth) {
1015 ancestor = ancestor->parent;
1017 file = file->parent;
1020 state->previous = state->file;
1023 if (pathlen > 0 && state->path[pathlen - 1] != '/') {
1024 if (dstrapp(&state->path, '/') != 0) {
1025 state->error = errno;
1029 if (dstrcat(&state->path, name) != 0) {
1030 state->error = errno;
1038 /** Open a bftw_file as a directory. */
1039 static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_file *file, const char *path) {
1040 int fd = bftw_file_open(state, file, path);
1045 struct bftw_cache *cache = &state->cache;
1046 struct bfs_dir *dir = bftw_allocdir(cache);
1051 bftw_ioq_balance(state, +1);
1053 if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) {
1054 bftw_freedir(cache, dir);
1058 bftw_file_set_dir(cache, file, dir);
1062 /** Open the current directory. */
1063 static int bftw_opendir(struct bftw_state *state) {
1064 bfs_assert(!state->dir);
1065 bfs_assert(!state->de);
1067 state->direrror = 0;
1069 struct bftw_file *file = state->file;
1070 state->dir = file->dir;
1075 if (bftw_build_path(state, NULL) != 0) {
1079 state->dir = bftw_file_opendir(state, file, state->path);
1081 state->direrror = errno;
1087 /** Read an entry from the current directory. */
1088 static int bftw_readdir(struct bftw_state *state) {
1093 int ret = bfs_readdir(state->dir, &state->de_storage);
1095 state->de = &state->de_storage;
1096 } else if (ret == 0) {
1100 state->direrror = errno;
1106 /** Check if a stat() call is needed for this visit. */
1107 static bool bftw_need_stat(const struct bftw_state *state) {
1108 if (state->flags & BFTW_STAT) {
1112 const struct BFTW *ftwbuf = &state->ftwbuf;
1113 if (ftwbuf->type == BFS_UNKNOWN) {
1117 if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) {
1121 if (ftwbuf->type == BFS_DIR) {
1122 if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) {
1126 } else if (state->mtab) {
1127 // Linux fills in d_type from the underlying inode, even when
1128 // the directory entry is a bind mount point. In that case, we
1129 // need to stat() to get the correct type. We don't need to
1130 // check for directories because they can only be mounted over
1131 // by other directories.
1132 if (bfs_might_be_mount(state->mtab, ftwbuf->path)) {
1141 /** Initialize bftw_stat cache. */
1142 static void bftw_stat_init(struct bftw_stat *cache) {
1147 /** Open a file if necessary. */
1148 static int bftw_ensure_open(struct bftw_state *state, struct bftw_file *file, const char *path) {
1152 char *copy = strndup(path, file->nameoff + file->namelen);
1157 ret = bftw_file_open(state, file, copy);
1164 /** Initialize the buffers with data about the current path. */
1165 static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
1166 struct bftw_file *file = state->file;
1167 const struct bfs_dirent *de = state->de;
1169 struct BFTW *ftwbuf = &state->ftwbuf;
1170 ftwbuf->path = state->path;
1171 ftwbuf->root = file ? file->root->name : ftwbuf->path;
1173 ftwbuf->visit = visit;
1174 ftwbuf->type = BFS_UNKNOWN;
1175 ftwbuf->error = state->direrror;
1176 ftwbuf->at_fd = AT_FDCWD;
1177 ftwbuf->at_path = ftwbuf->path;
1178 ftwbuf->stat_flags = BFS_STAT_NOFOLLOW;
1179 bftw_stat_init(&ftwbuf->lstat_cache);
1180 bftw_stat_init(&ftwbuf->stat_cache);
1182 struct bftw_file *parent = NULL;
1185 ftwbuf->depth = file->depth + 1;
1186 ftwbuf->type = de->type;
1187 ftwbuf->nameoff = bftw_child_nameoff(file);
1189 parent = file->parent;
1190 ftwbuf->depth = file->depth;
1191 ftwbuf->type = file->type;
1192 ftwbuf->nameoff = file->nameoff;
1196 // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG
1197 if (bftw_ensure_open(state, parent, state->path) >= 0) {
1198 ftwbuf->at_fd = parent->fd;
1199 ftwbuf->at_path += ftwbuf->nameoff;
1201 ftwbuf->error = errno;
1205 if (ftwbuf->depth == 0) {
1206 // Compute the name offset for root paths like "foo/bar"
1207 ftwbuf->nameoff = xbaseoff(ftwbuf->path);
1210 if (ftwbuf->error != 0) {
1211 ftwbuf->type = BFS_ERROR;
1215 int follow_flags = BFTW_FOLLOW_ALL;
1216 if (ftwbuf->depth == 0) {
1217 follow_flags |= BFTW_FOLLOW_ROOTS;
1219 bool follow = state->flags & follow_flags;
1221 ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW;
1224 const struct bfs_stat *statbuf = NULL;
1225 if (bftw_need_stat(state)) {
1226 statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
1228 ftwbuf->type = bfs_mode_to_type(statbuf->mode);
1230 ftwbuf->type = BFS_ERROR;
1231 ftwbuf->error = errno;
1236 if (ftwbuf->type == BFS_DIR && (state->flags & BFTW_DETECT_CYCLES)) {
1237 for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) {
1238 if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) {
1239 ftwbuf->type = BFS_ERROR;
1240 ftwbuf->error = ELOOP;
1247 /** Check if the current file is a mount point. */
1248 static bool bftw_is_mount(struct bftw_state *state, const char *name) {
1249 const struct bftw_file *file = state->file;
1254 const struct bftw_file *parent = name ? file : file->parent;
1259 const struct BFTW *ftwbuf = &state->ftwbuf;
1260 const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
1261 return statbuf && statbuf->dev != parent->dev;
1264 /** Invoke the callback. */
1265 static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) {
1266 if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) {
1270 if (bftw_build_path(state, name) != 0) {
1274 const struct BFTW *ftwbuf = &state->ftwbuf;
1275 bftw_init_ftwbuf(state, visit);
1277 // Never give the callback BFS_ERROR unless BFTW_RECOVER is specified
1278 if (ftwbuf->type == BFS_ERROR && !(state->flags & BFTW_RECOVER)) {
1279 state->error = ftwbuf->error;
1283 if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) {
1287 enum bftw_action ret = state->callback(ftwbuf, state->ptr);
1290 if (visit != BFTW_PRE) {
1293 if (ftwbuf->type != BFS_DIR) {
1296 if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) {
1305 state->error = EINVAL;
1311 * Flags controlling which files get visited when done with a directory.
1313 enum bftw_gc_flags {
1314 /** Don't visit anything. */
1315 BFTW_VISIT_NONE = 0,
1316 /** Report directory errors. */
1317 BFTW_VISIT_ERROR = 1 << 0,
1318 /** Visit the file itself. */
1319 BFTW_VISIT_FILE = 1 << 1,
1320 /** Visit the file's ancestors. */
1321 BFTW_VISIT_PARENTS = 1 << 2,
1322 /** Visit both the file and its ancestors. */
1323 BFTW_VISIT_ALL = BFTW_VISIT_ERROR | BFTW_VISIT_FILE | BFTW_VISIT_PARENTS,
1326 /** Garbage collect the current file and its parents. */
1327 static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) {
1330 struct bftw_file *file = state->file;
1331 if (file && file->dir) {
1332 bftw_unpin_dir(state, file, true);
1337 if (state->direrror != 0) {
1338 if (flags & BFTW_VISIT_ERROR) {
1339 if (bftw_call_back(state, NULL, BFTW_PRE) == BFTW_STOP) {
1344 state->error = state->direrror;
1347 state->direrror = 0;
1349 while ((file = SLIST_POP(&state->to_close))) {
1350 bftw_unwrapdir(state, file);
1353 enum bftw_gc_flags visit = BFTW_VISIT_FILE;
1354 while ((file = state->file)) {
1355 if (--file->refcount > 0) {
1360 if (flags & visit) {
1361 if (bftw_call_back(state, NULL, BFTW_POST) == BFTW_STOP) {
1366 visit = BFTW_VISIT_PARENTS;
1368 struct bftw_file *parent = file->parent;
1369 if (state->previous == file) {
1370 state->previous = parent;
1372 state->file = parent;
1374 if (file->fd >= 0) {
1375 bftw_close(state, file);
1377 bftw_file_free(&state->cache, file);
1383 /** Sort a bftw_list by filename. */
1384 static void bftw_list_sort(struct bftw_list *list) {
1385 if (!list->head || !list->head->next) {
1389 struct bftw_list left, right;
1394 for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) {
1395 struct bftw_file *tortoise = SLIST_POP(list);
1396 SLIST_APPEND(&left, tortoise);
1398 SLIST_EXTEND(&right, list);
1401 bftw_list_sort(&left);
1402 bftw_list_sort(&right);
1405 while (!SLIST_EMPTY(&left) && !SLIST_EMPTY(&right)) {
1406 struct bftw_file *lf = left.head;
1407 struct bftw_file *rf = right.head;
1409 if (strcoll(lf->name, rf->name) <= 0) {
1411 SLIST_APPEND(list, lf);
1414 SLIST_APPEND(list, rf);
1417 SLIST_EXTEND(list, &left);
1418 SLIST_EXTEND(list, &right);
1421 /** Finish adding a batch of files. */
1422 static void bftw_batch_finish(struct bftw_state *state) {
1423 if (state->flags & BFTW_SORT) {
1424 bftw_list_sort(&state->file_batch);
1427 if (state->strategy != BFTW_BFS) {
1428 SLIST_EXTEND(&state->dir_batch, &state->to_open);
1429 SLIST_EXTEND(&state->file_batch, &state->to_visit);
1432 SLIST_EXTEND(&state->to_open, &state->dir_batch);
1433 SLIST_EXTEND(&state->to_visit, &state->file_batch);
1435 bftw_ioq_opendirs(state, &state->to_open);
1438 /** Close the current directory. */
1439 static int bftw_closedir(struct bftw_state *state) {
1440 if (bftw_gc(state, BFTW_VISIT_ALL) != 0) {
1444 bftw_batch_finish(state);
1448 /** Fill file identity information from an ftwbuf. */
1449 static void bftw_save_ftwbuf(struct bftw_file *file, const struct BFTW *ftwbuf) {
1450 file->type = ftwbuf->type;
1452 const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf;
1453 if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) {
1454 statbuf = ftwbuf->lstat_cache.buf;
1457 file->dev = statbuf->dev;
1458 file->ino = statbuf->ino;
1462 /** Visit and/or enqueue the current file. */
1463 static int bftw_visit(struct bftw_state *state, const char *name) {
1464 struct bftw_file *file = state->file;
1466 if (name && (state->flags & BFTW_BUFFER)) {
1467 file = bftw_file_new(&state->cache, file, name);
1469 state->error = errno;
1474 file->type = state->de->type;
1477 SLIST_APPEND(&state->file_batch, file);
1481 switch (bftw_call_back(state, name, BFTW_PRE)) {
1484 file = bftw_file_new(&state->cache, state->file, name);
1489 state->error = errno;
1493 bftw_save_ftwbuf(file, &state->ftwbuf);
1494 bftw_push_dir(state, file);
1498 if (file && !name) {
1499 return bftw_gc(state, BFTW_VISIT_PARENTS);
1510 * Dispose of the bftw() state.
1513 * The bftw() return value.
1515 static int bftw_state_destroy(struct bftw_state *state) {
1516 dstrfree(state->path);
1518 struct ioq *ioq = state->ioq;
1521 while (bftw_ioq_pop(state, true) >= 0);
1525 SLIST_EXTEND(&state->to_open, &state->dir_batch);
1526 SLIST_EXTEND(&state->to_visit, &state->file_batch);
1528 bftw_gc(state, BFTW_VISIT_NONE);
1529 } while (bftw_pop_dir(state) || bftw_pop_file(state));
1533 bftw_cache_destroy(&state->cache);
1535 errno = state->error;
1536 return state->error ? -1 : 0;
1540 * Shared implementation for all search strategies.
1542 static int bftw_impl(struct bftw_state *state) {
1543 for (size_t i = 0; i < state->npaths; ++i) {
1544 if (bftw_visit(state, state->paths[i]) != 0) {
1548 bftw_batch_finish(state);
1551 while (bftw_pop_dir(state)) {
1552 if (bftw_opendir(state) != 0) {
1555 while (bftw_readdir(state) > 0) {
1556 if (bftw_visit(state, state->de->name) != 0) {
1560 if (bftw_closedir(state) != 0) {
1565 if (!bftw_pop_file(state)) {
1568 if (bftw_visit(state, NULL) != 0) {
1577 * bftw() implementation for simple breadth-/depth-first search.
1579 static int bftw_walk(const struct bftw_args *args) {
1580 struct bftw_state state;
1581 if (bftw_state_init(&state, args) != 0) {
1586 return bftw_state_destroy(&state);
1590 * Iterative deepening search state.
1592 struct bftw_ids_state {
1593 /** Nested walk state. */
1594 struct bftw_state nested;
1595 /** The wrapped callback. */
1596 bftw_callback *delegate;
1597 /** The wrapped callback arguments. */
1599 /** Which visit this search corresponds to. */
1600 enum bftw_visit visit;
1601 /** Whether to override the bftw_visit. */
1603 /** The current minimum depth (inclusive). */
1605 /** The current maximum depth (exclusive). */
1607 /** The set of pruned paths. */
1609 /** Whether the bottom has been found. */
1613 /** Iterative deepening callback function. */
1614 static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) {
1615 struct bftw_ids_state *state = ptr;
1617 if (state->force_visit) {
1618 struct BFTW *mutbuf = (struct BFTW *)ftwbuf;
1619 mutbuf->visit = state->visit;
1622 if (ftwbuf->type == BFS_ERROR) {
1623 if (ftwbuf->depth + 1 >= state->min_depth) {
1624 return state->delegate(ftwbuf, state->ptr);
1630 if (ftwbuf->depth < state->min_depth) {
1631 if (trie_find_str(&state->pruned, ftwbuf->path)) {
1634 return BFTW_CONTINUE;
1636 } else if (state->visit == BFTW_POST) {
1637 if (trie_find_str(&state->pruned, ftwbuf->path)) {
1642 enum bftw_action ret = BFTW_CONTINUE;
1643 if (ftwbuf->visit == state->visit) {
1644 ret = state->delegate(ftwbuf, state->ptr);
1649 if (ftwbuf->type == BFS_DIR && ftwbuf->depth + 1 >= state->max_depth) {
1650 state->bottom = false;
1656 if (ftwbuf->type == BFS_DIR) {
1657 if (!trie_insert_str(&state->pruned, ftwbuf->path)) {
1658 state->nested.error = errno;
1671 /** Initialize iterative deepening state. */
1672 static int bftw_ids_init(struct bftw_ids_state *state, const struct bftw_args *args) {
1673 state->delegate = args->callback;
1674 state->ptr = args->ptr;
1675 state->visit = BFTW_PRE;
1676 state->force_visit = false;
1677 state->min_depth = 0;
1678 state->max_depth = 1;
1679 trie_init(&state->pruned);
1680 state->bottom = false;
1682 struct bftw_args ids_args = *args;
1683 ids_args.callback = bftw_ids_callback;
1684 ids_args.ptr = state;
1685 ids_args.flags &= ~BFTW_POST_ORDER;
1686 return bftw_state_init(&state->nested, &ids_args);
1689 /** Finish an iterative deepening search. */
1690 static int bftw_ids_destroy(struct bftw_ids_state *state) {
1691 trie_destroy(&state->pruned);
1692 return bftw_state_destroy(&state->nested);
1696 * Iterative deepening bftw() wrapper.
1698 static int bftw_ids(const struct bftw_args *args) {
1699 struct bftw_ids_state state;
1700 if (bftw_ids_init(&state, args) != 0) {
1704 while (!state.bottom) {
1705 state.bottom = true;
1707 if (bftw_impl(&state.nested) != 0) {
1715 if (args->flags & BFTW_POST_ORDER) {
1716 state.visit = BFTW_POST;
1717 state.force_visit = true;
1719 while (state.min_depth > 0) {
1723 if (bftw_impl(&state.nested) != 0) {
1730 return bftw_ids_destroy(&state);
1734 * Exponential deepening bftw() wrapper.
1736 static int bftw_eds(const struct bftw_args *args) {
1737 struct bftw_ids_state state;
1738 if (bftw_ids_init(&state, args) != 0) {
1742 while (!state.bottom) {
1743 state.bottom = true;
1745 if (bftw_impl(&state.nested) != 0) {
1749 state.min_depth = state.max_depth;
1750 state.max_depth *= 2;
1753 if (args->flags & BFTW_POST_ORDER) {
1754 state.visit = BFTW_POST;
1755 state.min_depth = 0;
1756 state.nested.flags |= BFTW_POST_ORDER;
1758 bftw_impl(&state.nested);
1762 return bftw_ids_destroy(&state);
1765 int bftw(const struct bftw_args *args) {
1766 switch (args->strategy) {
1769 return bftw_walk(args);
1771 return bftw_ids(args);
1773 return bftw_eds(args);