]> Sergey Matveev's repositories - bfs.git/commitdiff
dir: Fix FreeBSD union mounts
authorTavian Barnes <tavianator@tavianator.com>
Tue, 17 Oct 2023 15:35:41 +0000 (11:35 -0400)
committerTavian Barnes <tavianator@tavianator.com>
Tue, 17 Oct 2023 16:33:39 +0000 (12:33 -0400)
src/dir.c
tests/posix/overlayfs.out [new file with mode: 0644]
tests/posix/overlayfs.sh [new file with mode: 0644]
tests/posix/unionfs.out [new file with mode: 0644]
tests/posix/unionfs.sh [new file with mode: 0644]

index dee02e506fc1c0264b10863d9c849f8a8c268422..371696f34bb2fa0acf74c15e2809fb362a07e7a2 100644 (file)
--- a/src/dir.c
+++ b/src/dir.c
@@ -7,6 +7,7 @@
 #include "config.h"
 #include "diag.h"
 #include "sanity.h"
+#include "trie.h"
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -101,7 +102,9 @@ enum bfs_type bfs_mode_to_type(mode_t mode) {
  */
 enum {
        /** We've reached the end of the directory. */
-       BFS_DIR_EOF = BFS_DIR_PRIVATE << 0,
+       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 {
@@ -111,6 +114,9 @@ struct bfs_dir {
        int fd;
        unsigned short pos;
        unsigned short size;
+#  if __FreeBSD__
+       struct trie trie;
+#  endif
        alignas(sys_dirent) char buf[];
 #else
        DIR *dir;
@@ -153,7 +159,14 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_di
        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) {
@@ -245,11 +258,23 @@ 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 && __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
 
@@ -274,7 +299,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;
                }
 
@@ -287,6 +315,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);
@@ -297,7 +335,7 @@ int bfs_closedir(struct bfs_dir *dir) {
        }
 #endif
 
-       sanitize_uninit(dir, DIR_SIZE);
+       bfs_destroydir(dir);
        return ret;
 }
 
@@ -309,7 +347,7 @@ int bfs_unwrapdir(struct bfs_dir *dir) {
        int ret = fdclosedir(dir->dir);
 #endif
 
-       sanitize_uninit(dir, DIR_SIZE);
+       bfs_destroydir(dir);
        return ret;
 }
 #endif
diff --git a/tests/posix/overlayfs.out b/tests/posix/overlayfs.out
new file mode 100644 (file)
index 0000000..754d01d
--- /dev/null
@@ -0,0 +1,5 @@
+scratch/merged
+scratch/merged/bar
+scratch/merged/baz
+scratch/merged/baz/qux
+scratch/merged/foo
diff --git a/tests/posix/overlayfs.sh b/tests/posix/overlayfs.sh
new file mode 100644 (file)
index 0000000..a56b5b3
--- /dev/null
@@ -0,0 +1,7 @@
+test "$UNAME" = "Linux" || skip
+clean_scratch
+"$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}}
+mkdir -p scratch/{work,merged}
+bfs_sudo mount -t overlay overlay -olowerdir=scratch/lower,upperdir=scratch/upper,workdir=scratch/work scratch/merged || skip
+trap "bfs_sudo umount scratch/merged; bfs_sudo rm -rf scratch/work" EXIT
+bfs_diff scratch/merged
diff --git a/tests/posix/unionfs.out b/tests/posix/unionfs.out
new file mode 100644 (file)
index 0000000..6d0fa3c
--- /dev/null
@@ -0,0 +1,10 @@
+scratch
+scratch/lower
+scratch/lower/bar
+scratch/lower/baz
+scratch/lower/foo
+scratch/upper
+scratch/upper/bar
+scratch/upper/baz
+scratch/upper/baz/qux
+scratch/upper/foo
diff --git a/tests/posix/unionfs.sh b/tests/posix/unionfs.sh
new file mode 100644 (file)
index 0000000..88a549f
--- /dev/null
@@ -0,0 +1,6 @@
+[[ "$UNAME" == *BSD* ]] || skip
+clean_scratch
+"$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}}
+bfs_sudo mount -t unionfs -o below scratch/{lower,upper} || skip
+trap "bfs_sudo umount scratch/upper" EXIT
+bfs_diff scratch