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:	10037d0978 "fusefs: Implement support for the auto_unmount"
This commit is contained in:
Alan Somers
2026-01-19 12:11:46 -07:00
parent c2b513335f
commit ffb747d587
5 changed files with 108 additions and 3 deletions
+81
View File
@@ -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:
+11 -1
View File
@@ -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)
+4 -1
View File
@@ -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();
+7 -1
View File
@@ -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,
+5
View File
@@ -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.
*