diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -1203,36 +1203,20 @@ struct vattr *vap = ap->a_vap; struct ucred *cred = ap->a_cred; struct thread *td = curthread; - int err = 0; - int dataflags; - - dataflags = fuse_get_mpdata(vnode_mount(vp))->dataflags; - - /* Note that we are not bailing out on a dead file system just yet. */ - if (!(dataflags & FSESS_INITED)) { - if (!vnode_isvroot(vp)) { - fdata_set_dead(fuse_get_mpdata(vnode_mount(vp))); - err = ENOTCONN; - return err; - } else { - goto fake; - } - } err = fuse_internal_getattr(vp, vap, cred, td); if (err == ENOTCONN && vnode_isvroot(vp)) { - /* see comment in fuse_vfsop_statfs() */ - goto fake; - } else { - return err; + /* + * We want to seem a legitimate fs even if the daemon is dead, + * so that, eg., we can still do path based unmounting after + * the daemon dies. + */ + err = 0; + bzero(vap, sizeof(*vap)); + vap->va_type = vnode_vtype(vp); } - -fake: - bzero(vap, sizeof(*vap)); - vap->va_type = vnode_vtype(vp); - - return 0; + return err; } /* 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 @@ -37,6 +37,7 @@ GTESTS+= mount GTESTS+= nfs GTESTS+= notify +GTESTS+= pre-init GTESTS+= open GTESTS+= opendir GTESTS+= read 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,7 +366,8 @@ 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(); 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! */ 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,105 @@ +/*- + * 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 "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* Tests for behavior that happens before the server responds to FUSE_INIT */ +class PreInit: public FuseTest, + public WithParamInterface +{ +void SetUp() { + m_default_permissions = GetParam(); + m_no_auto_init = true; + FuseTest::SetUp(); +} +}; + + +/* + * If some process attempts VOP_GETATTR for the mountpoint before init is + * complete, fusefs should wait, just like it does for other VOPs. + * + * To verify that fuse_vnop_getattr does indeed wait for FUSE_INIT to complete, + * invoke the test like this: + * +> sudo cpuset -c -l 0 dtrace -i 'fbt:fusefs:fuse_internal_init_callback:' -i 'fbt:fusefs:fuse_vnop_getattr:' -c "./pre-init --gtest_filter=PI/PreInit.getattr_before_init/0" +... +dtrace: pid 4224 has exited +CPU ID FUNCTION:NAME + 0 68670 fuse_vnop_getattr:entry + 0 68893 fuse_internal_init_callback:entry + 0 68894 fuse_internal_init_callback:return + 0 68671 fuse_vnop_getattr:return + * + * Note that fuse_vnop_getattr was entered first, but exitted last. + */ +TEST_P(PreInit, getattr_before_init) +{ + struct stat sb; + nlink_t nlink = 12345; + + 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; + nap(); /* Allow stat() to run first */ + }))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto& in, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = in.header.nodeid; + out.body.attr.attr.mode = S_IFDIR | 0644; + out.body.attr.attr.nlink = nlink; + out.body.attr.attr_valid = UINT64_MAX; + }))); + + EXPECT_EQ(0, stat("mountpoint", &sb)); + EXPECT_EQ(nlink, sb.st_nlink); +} + +INSTANTIATE_TEST_SUITE_P(PI, PreInit, Bool()); 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