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. *
