diff options
Diffstat (limited to 'src/dir.c')
-rw-r--r-- | src/dir.c | 125 |
1 files changed, 96 insertions, 29 deletions
@@ -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 |