The branch main has been updated by asomers:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=ffb747d587bf09a982df67fba322b91d02f70be6

commit ffb747d587bf09a982df67fba322b91d02f70be6
Author:     Alan Somers <[email protected]>
AuthorDate: 2026-01-19 19:11:46 +0000
Commit:     Alan Somers <[email protected]>
CommitDate: 2026-01-24 16:02:33 +0000

    fusefs: Add tests for the new -o auto_unmount feature
    
    Add tests for mount_fusefs's new -o auto_unmount feature, recently added
    by arrowd.
    
    MFC with:       10037d0978f "fusefs: Implement support for the auto_unmount"
---
 tests/sys/fs/fusefs/destroy.cc | 81 ++++++++++++++++++++++++++++++++++++++++++
 tests/sys/fs/fusefs/mockfs.cc  | 12 ++++++-
 tests/sys/fs/fusefs/mockfs.hh  |  5 ++-
 tests/sys/fs/fusefs/utils.cc   |  8 ++++-
 tests/sys/fs/fusefs/utils.hh   |  5 +++
 5 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/tests/sys/fs/fusefs/destroy.cc b/tests/sys/fs/fusefs/destroy.cc
index 45acb1f99724..0c8e2fd22a50 100644
--- a/tests/sys/fs/fusefs/destroy.cc
+++ b/tests/sys/fs/fusefs/destroy.cc
@@ -29,6 +29,9 @@
  */
 
 extern "C" {
+#include <sys/param.h>
+#include <sys/mount.h>
+
 #include <fcntl.h>
 #include <pthread.h>
 #include <semaphore.h>
@@ -45,6 +48,30 @@ class Destroy: public FuseTest {};
 /* Tests for unexpected deaths of the server */
 class Death: public FuseTest{};
 
+/* Tests for the auto_unmount mount option*/
+class AutoUnmount: public FuseTest {
+virtual void SetUp() {
+       m_auto_unmount = true;
+       FuseTest::SetUp();
+}
+
+protected:
+/* Unmounting fusefs might be asynchronous with close, so use a retry loop */
+void assert_unmounted() {
+       struct statfs statbuf;
+
+       for (int retry = 100; retry > 0; retry--) {
+               ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
+               if (strcmp("fusefs", statbuf.f_fstypename) != 0 &&
+                   strcmp("/dev/fuse", statbuf.f_mntfromname) != 0)
+                       return;
+               nap();
+       }
+       FAIL() << "fusefs is still mounted";
+}
+
+};
+
 static void* open_th(void* arg) {
        int fd;
        const char *path = (const char*)arg;
@@ -55,6 +82,60 @@ static void* open_th(void* arg) {
        return 0;
 }
 
+/*
+ * With the auto_unmount mount option, the kernel will automatically unmount
+ * the file system when the server dies.
+ */
+TEST_F(AutoUnmount, auto_unmount)
+{
+       /* Kill the daemon */
+       m_mock->kill_daemon();
+
+       /* Use statfs to check that the file system is no longer mounted */
+       assert_unmounted();
+}
+
+/*
+ * When -o auto_unmount is used, the kernel should _not_ unmount the file
+ * system when any /dev/fuse file descriptor is closed, but only for the last
+ * one.
+ */
+TEST_F(AutoUnmount, dup)
+{
+       struct statfs statbuf;
+       int fuse2;
+
+       EXPECT_CALL(*m_mock, process(
+               ResultOf([](auto in) {
+                       return (in.header.opcode == FUSE_STATFS);
+               }, Eq(true)),
+               _)
+       ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+               SET_OUT_HEADER_LEN(out, statfs);
+       })));
+
+       fuse2 = dup_dev_fuse();
+
+       /*
+        * Close one of the /dev/fuse file descriptors.  Close the duplicate
+        * first so the daemon thread doesn't freak out when it gets a bunch of
+        * EBADF errors.
+        */
+       close(fuse2);
+
+       /* Use statfs to check that the file system is still mounted */
+       ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
+       EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename));
+       EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname));
+
+       /*
+        * Close the original file descriptor too. Now the file system should be
+        * unmounted at last.
+        */
+       m_mock->kill_daemon();
+       assert_unmounted();
+}
+
 /*
  * The server dies with unsent operations still on the message queue.
  * Check for any memory leaks like this:
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index ee47d9e0e01c..b9e4afbbea0b 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -426,7 +426,8 @@ MockFS::MockFS(int max_read, int max_readahead, bool 
allow_other,
        bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
        uint32_t kernel_minor_version, uint32_t max_write, bool async,
        bool noclusterr, unsigned time_gran, bool nointr, bool noatime,
-       const char *fsname, const char *subtype, bool no_auto_init)
+       const char *fsname, const char *subtype, bool no_auto_init,
+       bool auto_unmount)
        : m_daemon_id(NULL),
          m_kernel_minor_version(kernel_minor_version),
          m_kq(pm == KQ ? kqueue() : -1),
@@ -519,6 +520,10 @@ MockFS::MockFS(int max_read, int max_readahead, bool 
allow_other,
                build_iovec(&iov, &iovlen, "intr",
                        __DECONST(void*, &trueval), sizeof(bool));
        }
+       if (auto_unmount) {
+               build_iovec(&iov, &iovlen, "auto_unmount",
+                       __DECONST(void*, &trueval), sizeof(bool));
+       }
        if (*fsname) {
                build_iovec(&iov, &iovlen, "fsname=",
                        __DECONST(void*, fsname), -1);
@@ -787,6 +792,11 @@ void MockFS::init(uint32_t flags) {
        write(m_fuse_fd, out.get(), out->header.len);
 }
 
+int MockFS::dup_dev_fuse()
+{
+       return (dup(m_fuse_fd));
+}
+
 void MockFS::kill_daemon() {
        m_quit = true;
        if (m_daemon_id != NULL)
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
index 00503332f820..c8f90c2f5402 100644
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -372,10 +372,13 @@ class MockFS {
                uint32_t kernel_minor_version, uint32_t max_write, bool async,
                bool no_clusterr, unsigned time_gran, bool nointr,
                bool noatime, const char *fsname, const char *subtype,
-               bool no_auto_init);
+               bool no_auto_init, bool auto_unmount);
 
        virtual ~MockFS();
 
+       /* Duplicate the /dev/fuse file descriptor, and return the duplicate */
+       int dup_dev_fuse();
+
        /* Kill the filesystem daemon without unmounting the filesystem */
        void kill_daemon();
 
diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc
index 125b7e2d6fc7..93b850a7b7e3 100644
--- a/tests/sys/fs/fusefs/utils.cc
+++ b/tests/sys/fs/fusefs/utils.cc
@@ -152,7 +152,7 @@ void FuseTest::SetUp() {
                        m_pm, m_init_flags, m_kernel_minor_version,
                        m_maxwrite, m_async, m_noclusterr, m_time_gran,
                        m_nointr, m_noatime, m_fsname, m_subtype,
-                       m_no_auto_init);
+                       m_no_auto_init, m_auto_unmount);
                /* 
                 * FUSE_ACCESS is called almost universally.  Expecting it in
                 * each test case would be super-annoying.  Instead, set a
@@ -571,6 +571,12 @@ get_unprivileged_id(uid_t *uid, gid_t *gid)
        *gid = gr->gr_gid;
 }
 
+int
+FuseTest::dup_dev_fuse()
+{
+       return (m_mock->dup_dev_fuse());
+}
+
 void
 FuseTest::fork(bool drop_privs, int *child_status,
        std::function<void()> parent_func,
diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh
index 91bbba909672..ebd8d41d6961 100644
--- a/tests/sys/fs/fusefs/utils.hh
+++ b/tests/sys/fs/fusefs/utils.hh
@@ -70,6 +70,7 @@ class FuseTest : public ::testing::Test {
        bool m_noclusterr;
        bool m_nointr;
        bool m_no_auto_init;
+       bool m_auto_unmount;
        unsigned m_time_gran;
        MockFS *m_mock = NULL;
        const static uint64_t FH = 0xdeadbeef1a7ebabe;
@@ -97,6 +98,7 @@ class FuseTest : public ::testing::Test {
                m_noclusterr(false),
                m_nointr(false),
                m_no_auto_init(false),
+               m_auto_unmount(false),
                m_time_gran(1),
                m_fsname(""),
                m_subtype(""),
@@ -234,6 +236,9 @@ class FuseTest : public ::testing::Test {
        void expect_write_7_8(uint64_t ino, uint64_t offset, uint64_t isize,
                uint64_t osize, const void *contents);
 
+       /* Duplicate the /dev/fuse file descriptor, and return the duplicate */
+       int dup_dev_fuse();
+
        /*
         * Helper that runs code in a child process.
         *

Reply via email to