summaryrefslogtreecommitdiffstats
path: root/bftw.c
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2019-02-13 11:12:30 -0500
committerTavian Barnes <tavianator@tavianator.com>2019-03-06 23:01:19 -0800
commit0689a4a16f5e80e0c2368f4a68c69ce5f2fdc038 (patch)
tree60301a8961c72698b44291ecb0473e24c61f923a /bftw.c
parent7fc960a23eab7fce9f5e0666b1a9b3f5eae832af (diff)
downloadbfs-0689a4a16f5e80e0c2368f4a68c69ce5f2fdc038.tar.xz
bftw: Work around d_type being wrong for bind mounts on Linux
C.f. https://savannah.gnu.org/bugs/?54913 C.f. https://lkml.org/lkml/2019/2/11/2027 Fixes https://github.com/tavianator/bfs/issues/37
Diffstat (limited to 'bftw.c')
-rw-r--r--bftw.c71
1 files changed, 52 insertions, 19 deletions
diff --git a/bftw.c b/bftw.c
index 6795bd0..4f94a55 100644
--- a/bftw.c
+++ b/bftw.c
@@ -769,6 +769,8 @@ struct bftw_state {
void *ptr;
/** bftw() flags. */
enum bftw_flags flags;
+ /** The mount table. */
+ const struct bfs_mtab *mtab;
/** The appropriate errno value, if any. */
int error;
@@ -800,6 +802,7 @@ static int bftw_state_init(struct bftw_state *state, const char *root, const str
state->callback = args->callback;
state->ptr = args->ptr;
state->flags = args->flags;
+ state->mtab = args->mtab;
state->error = 0;
@@ -863,6 +866,41 @@ static int bftw_update_path(struct bftw_state *state) {
return 0;
}
+/** Check if a stat() call is needed for this visit. */
+static bool bftw_need_stat(struct bftw_state *state) {
+ if (state->flags & BFTW_STAT) {
+ return true;
+ }
+
+ struct BFTW *ftwbuf = &state->ftwbuf;
+ if (ftwbuf->typeflag == BFTW_UNKNOWN) {
+ return true;
+ }
+
+ if (ftwbuf->typeflag == BFTW_LNK && !(ftwbuf->at_flags & AT_SYMLINK_NOFOLLOW)) {
+ return true;
+ }
+
+ if (ftwbuf->typeflag == BFTW_DIR) {
+ if (state->flags & (BFTW_DETECT_CYCLES | BFTW_XDEV)) {
+ return true;
+ }
+#if __linux__
+ } else if (state->mtab) {
+ // Linux fills in d_type from the underlying inode, even when
+ // the directory entry is a bind mount point. In that case, we
+ // need to stat() to get the correct type. We don't need to
+ // check for directories because they can only be mounted over
+ // by other directories.
+ if (bfs_maybe_mount(state->mtab, ftwbuf->path)) {
+ return true;
+ }
+#endif
+ }
+
+ return false;
+}
+
/**
* Initialize the buffers with data about the current path.
*/
@@ -875,8 +913,8 @@ static void bftw_prepare_visit(struct bftw_state *state) {
ftwbuf->path = dir ? reader->path : state->root;
ftwbuf->root = state->root;
ftwbuf->depth = 0;
- ftwbuf->error = reader->error;
ftwbuf->visit = state->visit;
+ ftwbuf->error = reader->error;
ftwbuf->statbuf = NULL;
ftwbuf->at_fd = AT_FDCWD;
ftwbuf->at_path = ftwbuf->path;
@@ -922,29 +960,24 @@ static void bftw_prepare_visit(struct bftw_state *state) {
ftwbuf->at_flags = 0;
}
- bool detect_cycles = (state->flags & BFTW_DETECT_CYCLES) && de;
-
- bool xdev = state->flags & BFTW_XDEV;
-
- if ((state->flags & BFTW_STAT)
- || ftwbuf->typeflag == BFTW_UNKNOWN
- || (ftwbuf->typeflag == BFTW_LNK && follow)
- || (ftwbuf->typeflag == BFTW_DIR && (detect_cycles || xdev))) {
+ if (bftw_need_stat(state)) {
if (bftw_stat(ftwbuf, &state->statbuf) != 0) {
- ftwbuf->error = errno;
ftwbuf->typeflag = BFTW_ERROR;
+ ftwbuf->error = errno;
return;
}
+ }
- if (ftwbuf->typeflag == BFTW_DIR && detect_cycles) {
- dev_t dev = ftwbuf->statbuf->dev;
- ino_t ino = ftwbuf->statbuf->ino;
- for (const struct bftw_dir *parent = dir; parent; parent = parent->parent) {
- if (dev == parent->dev && ino == parent->ino) {
- ftwbuf->error = ELOOP;
- ftwbuf->typeflag = BFTW_ERROR;
- return;
- }
+ if (ftwbuf->typeflag == BFTW_DIR && (state->flags & BFTW_DETECT_CYCLES)) {
+ const struct bfs_stat *statbuf = ftwbuf->statbuf;
+ for (const struct bftw_dir *parent = dir; parent; parent = parent->parent) {
+ if (parent->depth == ftwbuf->depth) {
+ continue;
+ }
+ if (parent->dev == statbuf->dev && parent->ino == statbuf->ino) {
+ ftwbuf->typeflag = BFTW_ERROR;
+ ftwbuf->error = ELOOP;
+ return;
}
}
}