fusefs: don't fake the mountpoint's stat info before FUSE_INIT completes
Ever since the first GSoC contribution, fusefs has had a curious behavior. If the daemon hasn't finished responding to FUSE_INIT, fuse_vnop_getattr would reply to VOP_GETATTR requests for the mountpoint by returning all zeros. I don't know why. It isn't necessary for unmounting, even if the daemon is dead. Delete that behavior. Now VOP_GETATTR for the mountpoint will wait for the daemon to be ready, just like it will for any other vnode. Reported by: Vassili Tchersky Sponsored by: ConnectWise Differential Revision: https://reviews.freebsd.org/D50800
This commit is contained in:
@@ -1219,36 +1219,20 @@ fuse_vnop_getattr(struct vop_getattr_args *ap)
|
||||
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)));
|
||||
return (EXTERROR(ENOTCONN, "FUSE daemon is not "
|
||||
"initialized"));
|
||||
} 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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -44,12 +44,26 @@ using namespace testing;
|
||||
|
||||
/* Tests for behavior that happens before the server responds to FUSE_INIT */
|
||||
class PreInit: public FuseTest {
|
||||
public:
|
||||
void SetUp() {
|
||||
m_no_auto_init = true;
|
||||
FuseTest::SetUp();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Tests for behavior that happens before the server responds to FUSE_INIT,
|
||||
* parameterized on default_permissions
|
||||
*/
|
||||
class PreInitP: public PreInit,
|
||||
public WithParamInterface<bool>
|
||||
{
|
||||
void SetUp() {
|
||||
m_default_permissions = GetParam();
|
||||
PreInit::SetUp();
|
||||
}
|
||||
};
|
||||
|
||||
static void* unmount1(void* arg __unused) {
|
||||
ssize_t r;
|
||||
|
||||
@@ -152,3 +166,61 @@ TEST_F(PreInit, signal_during_unmount_before_init)
|
||||
sem_post(&sem0);
|
||||
m_mock->join_daemon();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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/PreInitP.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(PreInitP, 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, PreInitP, Bool());
|
||||
|
||||
Reference in New Issue
Block a user