Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F147369363
D50799.id156949.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
D50799.id156949.diff
View Options
diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c
--- a/sys/fs/fuse/fuse_internal.c
+++ b/sys/fs/fuse/fuse_internal.c
@@ -979,6 +979,9 @@
struct fuse_data *data = tick->tk_data;
struct fuse_init_out *fiio = NULL;
+ if (fdata_get_dead(data))
+ goto out;
+
if ((err = tick->tk_aw_ohead.error)) {
goto out;
}
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -39,6 +39,7 @@
GTESTS+= notify
GTESTS+= open
GTESTS+= opendir
+GTESTS+= pre-init
GTESTS+= read
GTESTS+= readdir
GTESTS+= readlink
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -366,13 +366,17 @@
enum poll_method pm, uint32_t flags,
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 noatime, const char *fsname, const char *subtype,
+ bool no_auto_init);
virtual ~MockFS();
/* Kill the filesystem daemon without unmounting the filesystem */
void kill_daemon();
+ /* Wait until the daemon thread terminates */
+ void join_daemon();
+
/* Process FUSE requests endlessly */
void loop();
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -420,7 +420,7 @@
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)
+ const char *fsname, const char *subtype, bool no_auto_init)
: m_daemon_id(NULL),
m_kernel_minor_version(kernel_minor_version),
m_kq(pm == KQ ? kqueue() : -1),
@@ -529,7 +529,9 @@
ON_CALL(*this, process(_, _))
.WillByDefault(Invoke(this, &MockFS::process_default));
- init(flags);
+ if (!no_auto_init)
+ init(flags);
+
bzero(&sa, sizeof(sa));
sa.sa_handler = sigint_handler;
sa.sa_flags = 0; /* Don't set SA_RESTART! */
@@ -543,10 +545,7 @@
MockFS::~MockFS() {
kill_daemon();
- if (m_daemon_id != NULL) {
- pthread_join(m_daemon_id, NULL);
- m_daemon_id = NULL;
- }
+ join_daemon();
::unmount("mountpoint", MNT_FORCE);
rmdir("mountpoint");
if (m_kq >= 0)
@@ -787,6 +786,13 @@
m_fuse_fd = -1;
}
+void MockFS::join_daemon() {
+ if (m_daemon_id != NULL) {
+ pthread_join(m_daemon_id, NULL);
+ m_daemon_id = NULL;
+ }
+}
+
void MockFS::loop() {
std::vector<std::unique_ptr<mockfs_buf_out>> out;
diff --git a/tests/sys/fs/fusefs/pre-init.cc b/tests/sys/fs/fusefs/pre-init.cc
new file mode 100644
--- /dev/null
+++ b/tests/sys/fs/fusefs/pre-init.cc
@@ -0,0 +1,154 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 ConnectWise
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+/* Tests for behavior that happens before the server responds to FUSE_INIT */
+class PreInit: public FuseTest {
+void SetUp() {
+ m_no_auto_init = true;
+ FuseTest::SetUp();
+}
+};
+
+static void* unmount1(void* arg __unused) {
+ ssize_t r;
+
+ r = unmount("mountpoint", 0);
+ if (r >= 0)
+ return 0;
+ else
+ return (void*)(intptr_t)errno;
+}
+
+/*
+ * Attempting to unmount the file system before it fully initializes should
+ * work fine. The unmount will complete after initialization does.
+ */
+TEST_F(PreInit, unmount_before_init)
+{
+ sem_t sem0;
+ pthread_t th1;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([](auto in) {
+ return (in.header.opcode == FUSE_INIT);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
+ SET_OUT_HEADER_LEN(out, init);
+ out.body.init.major = FUSE_KERNEL_VERSION;
+ out.body.init.minor = FUSE_KERNEL_MINOR_VERSION;
+ out.body.init.flags = in.body.init.flags & m_init_flags;
+ out.body.init.max_write = m_maxwrite;
+ out.body.init.max_readahead = m_maxreadahead;
+ out.body.init.time_gran = m_time_gran;
+ sem_wait(&sem0);
+ })));
+ expect_destroy(0);
+
+ ASSERT_EQ(0, pthread_create(&th1, NULL, unmount1, NULL));
+ nap(); /* Wait for th1 to block in unmount() */
+ sem_post(&sem0);
+ /* The daemon will quit after receiving FUSE_DESTROY */
+ m_mock->join_daemon();
+}
+
+/*
+ * Don't panic in this very specific scenario:
+ *
+ * The server does not respond to FUSE_INIT in timely fashion.
+ * Some other process tries to do unmount.
+ * That other process gets killed by a signal.
+ * The server finally responds to FUSE_INIT.
+ *
+ * Regression test for bug 287438
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=287438
+ */
+TEST_F(PreInit, signal_during_unmount_before_init)
+{
+ sem_t sem0;
+ pid_t child;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([](auto in) {
+ return (in.header.opcode == FUSE_INIT);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
+ SET_OUT_HEADER_LEN(out, init);
+ out.body.init.major = FUSE_KERNEL_VERSION;
+ /*
+ * Use protocol 7.19, like libfuse2 does. The server must use
+ * protocol 7.27 or older to trigger the bug.
+ */
+ out.body.init.minor = 19;
+ out.body.init.flags = in.body.init.flags & m_init_flags;
+ out.body.init.max_write = m_maxwrite;
+ out.body.init.max_readahead = m_maxreadahead;
+ out.body.init.time_gran = m_time_gran;
+ sem_wait(&sem0);
+ })));
+
+ if ((child = ::fork()) == 0) {
+ /*
+ * In child. This will block waiting for FUSE_INIT to complete
+ * or the receipt of an asynchronous signal.
+ */
+ (void) unmount("mountpoint", 0);
+ _exit(0); /* Unreachable, unless parent dies after fork */
+ } else if (child > 0) {
+ /* In parent. Wait for child process to start, then kill it */
+ nap();
+ kill(child, SIGINT);
+ waitpid(child, NULL, WEXITED);
+ } else {
+ FAIL() << strerror(errno);
+ }
+ m_mock->m_quit = true; /* Since we are by now unmounted. */
+ sem_post(&sem0);
+ m_mock->join_daemon();
+}
diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh
--- a/tests/sys/fs/fusefs/utils.hh
+++ b/tests/sys/fs/fusefs/utils.hh
@@ -69,6 +69,7 @@
bool m_async;
bool m_noclusterr;
bool m_nointr;
+ bool m_no_auto_init;
unsigned m_time_gran;
MockFS *m_mock = NULL;
const static uint64_t FH = 0xdeadbeef1a7ebabe;
@@ -95,6 +96,7 @@
m_async(false),
m_noclusterr(false),
m_nointr(false),
+ m_no_auto_init(false),
m_time_gran(1),
m_fsname(""),
m_subtype(""),
diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc
--- a/tests/sys/fs/fusefs/utils.cc
+++ b/tests/sys/fs/fusefs/utils.cc
@@ -151,7 +151,8 @@
m_default_permissions, m_push_symlinks_in, m_ro,
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_nointr, m_noatime, m_fsname, m_subtype,
+ m_no_auto_init);
/*
* FUSE_ACCESS is called almost universally. Expecting it in
* each test case would be super-annoying. Instead, set a
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 11, 9:48 AM (5 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29535020
Default Alt Text
D50799.id156949.diff (8 KB)
Attached To
Mode
D50799: fusefs: Fix a panic when unmounting before init
Attached
Detach File
Event Timeline
Log In to Comment