summaryrefslogtreecommitdiffstats
path: root/src/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dir.c')
-rw-r--r--src/dir.c125
1 files changed, 96 insertions, 29 deletions
diff --git a/src/dir.c b/src/dir.c
index 0304674..4bf72a1 100644
--- a/src/dir.c
+++ b/src/dir.c
@@ -2,11 +2,14 @@
// SPDX-License-Identifier: 0BSD
#include "dir.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
-#include "config.h"
#include "diag.h"
#include "sanity.h"
+#include "trie.h"
+
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -16,7 +19,7 @@
#include <unistd.h>
#if BFS_USE_GETDENTS
-# if __linux__
+# if BFS_HAS_GETDENTS64_SYSCALL
# include <sys/syscall.h>
# endif
@@ -24,12 +27,20 @@
static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
sanitize_uninit(buf, size);
-#if __linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)
- ssize_t ret = syscall(SYS_getdents64, fd, buf, size);
-#elif __linux__
+#if BFS_HAS_POSIX_GETDENTS
+ int flags = 0;
+# ifdef DT_FORCE_TYPE
+ flags |= DT_FORCE_TYPE;
+# endif
+ ssize_t ret = posix_getdents(fd, buf, size, flags);
+#elif BFS_HAS_GETDENTS
+ ssize_t ret = getdents(fd, buf, size);
+#elif BFS_HAS_GETDENTS64
ssize_t ret = getdents64(fd, buf, size);
+#elif BFS_HAS_GETDENTS64_SYSCALL
+ ssize_t ret = syscall(SYS_getdents64, fd, buf, size);
#else
- ssize_t ret = getdents(fd, buf, size);
+# error "No getdents() implementation"
#endif
if (ret > 0) {
@@ -41,11 +52,13 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
#endif // BFS_USE_GETDENTS
-#if BFS_USE_GETDENTS && __linux__
/** Directory entry type for bfs_getdents() */
-typedef struct dirent64 sys_dirent;
-#else
+#if !BFS_USE_GETDENTS || BFS_HAS_GETDENTS
typedef struct dirent sys_dirent;
+#elif BFS_HAS_POSIX_GETDENTS
+typedef struct posix_dent sys_dirent;
+#else
+typedef struct dirent64 sys_dirent;
#endif
enum bfs_type bfs_mode_to_type(mode_t mode) {
@@ -96,18 +109,31 @@ enum bfs_type bfs_mode_to_type(mode_t mode) {
}
}
+/**
+ * Private directory flags.
+ */
+enum {
+ /** We've reached the end of the directory. */
+ BFS_DIR_EOF = BFS_DIR_PRIVATE << 0,
+ /** This directory is a union mount we need to dedup manually. */
+ BFS_DIR_UNION = BFS_DIR_PRIVATE << 1,
+};
+
struct bfs_dir {
+ unsigned int flags;
+
#if BFS_USE_GETDENTS
- alignas(sys_dirent) int fd;
+ int fd;
unsigned short pos;
unsigned short size;
- // sys_dirent buf[];
+# if __FreeBSD__
+ struct trie trie;
+# endif
+ alignas(sys_dirent) char buf[];
#else
DIR *dir;
struct dirent *de;
#endif
-
- bool eof;
};
#if BFS_USE_GETDENTS
@@ -125,7 +151,7 @@ void bfs_dir_arena(struct arena *arena) {
arena_init(arena, alignof(struct bfs_dir), DIR_SIZE);
}
-int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) {
+int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags) {
int fd;
if (at_path) {
fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
@@ -139,11 +165,20 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) {
return -1;
}
+ dir->flags = flags;
+
#if BFS_USE_GETDENTS
dir->fd = fd;
dir->pos = 0;
dir->size = 0;
-#else
+
+# if __FreeBSD__ && defined(F_ISUNIONSTACK)
+ if (fcntl(fd, F_ISUNIONSTACK) > 0) {
+ dir->flags |= BFS_DIR_UNION;
+ trie_init(&dir->trie);
+ }
+# endif
+#else // !BFS_USE_GETDENTS
dir->dir = fdopendir(fd);
if (!dir->dir) {
if (at_path) {
@@ -154,7 +189,6 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) {
dir->de = NULL;
#endif
- dir->eof = false;
return 0;
}
@@ -170,14 +204,14 @@ int bfs_polldir(struct bfs_dir *dir) {
#if BFS_USE_GETDENTS
if (dir->pos < dir->size) {
return 1;
- } else if (dir->eof) {
+ } else if (dir->flags & BFS_DIR_EOF) {
return 0;
}
char *buf = (char *)(dir + 1);
ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE);
if (size == 0) {
- dir->eof = true;
+ dir->flags |= BFS_DIR_EOF;
return 0;
} else if (size < 0) {
return -1;
@@ -194,7 +228,7 @@ int bfs_polldir(struct bfs_dir *dir) {
if (size > 0) {
dir->size += size;
} else if (size == 0) {
- dir->eof = true;
+ dir->flags |= BFS_DIR_EOF;
}
}
@@ -202,7 +236,7 @@ int bfs_polldir(struct bfs_dir *dir) {
#else // !BFS_USE_GETDENTS
if (dir->de) {
return 1;
- } else if (dir->eof) {
+ } else if (dir->flags & BFS_DIR_EOF) {
return 0;
}
@@ -211,7 +245,7 @@ int bfs_polldir(struct bfs_dir *dir) {
if (dir->de) {
return 1;
} else if (errno == 0) {
- dir->eof = true;
+ dir->flags |= BFS_DIR_EOF;
return 0;
} else {
return -1;
@@ -236,13 +270,33 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) {
}
/** Skip ".", "..", and deleted/empty dirents. */
-static bool skip_dirent(const sys_dirent *de) {
-#if __FreeBSD__
+static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) {
+#if BFS_USE_GETDENTS
+# if __FreeBSD__
+ // Union mounts on FreeBSD have to be de-duplicated in userspace
+ if (dir->flags & BFS_DIR_UNION) {
+ struct trie_leaf *leaf = trie_insert_str(&dir->trie, de->d_name);
+ if (!leaf) {
+ return -1;
+ } else if (leaf->value) {
+ return 1;
+ } else {
+ leaf->value = leaf;
+ }
+ }
+
// NFS mounts on FreeBSD can return empty dirents with inode number 0
if (de->d_ino == 0) {
- return true;
+ return 1;
}
-#endif
+# endif
+
+# ifdef DT_WHT
+ if (de->d_type == DT_WHT && !(dir->flags & BFS_DIR_WHITEOUTS)) {
+ return 1;
+ }
+# endif
+#endif // BFS_USE_GETDENTS
const char *name = de->d_name;
return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
@@ -265,7 +319,10 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) {
return ret;
}
- if (skip_dirent(sysde)) {
+ int skip = bfs_skipdent(dir, sysde);
+ if (skip < 0) {
+ return skip;
+ } else if (skip) {
continue;
}
@@ -278,6 +335,16 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) {
}
}
+static void bfs_destroydir(struct bfs_dir *dir) {
+#if BFS_USE_GETDENTS && __FreeBSD__
+ if (dir->flags & BFS_DIR_UNION) {
+ trie_destroy(&dir->trie);
+ }
+#endif
+
+ sanitize_uninit(dir, DIR_SIZE);
+}
+
int bfs_closedir(struct bfs_dir *dir) {
#if BFS_USE_GETDENTS
int ret = xclose(dir->fd);
@@ -288,7 +355,7 @@ int bfs_closedir(struct bfs_dir *dir) {
}
#endif
- sanitize_uninit(dir, DIR_SIZE);
+ bfs_destroydir(dir);
return ret;
}
@@ -296,11 +363,11 @@ int bfs_closedir(struct bfs_dir *dir) {
int bfs_unwrapdir(struct bfs_dir *dir) {
#if BFS_USE_GETDENTS
int ret = dir->fd;
-#elif __FreeBSD__
+#elif BFS_HAS_FDCLOSEDIR
int ret = fdclosedir(dir->dir);
#endif
- sanitize_uninit(dir, DIR_SIZE);
+ bfs_destroydir(dir);
return ret;
}
#endif