Index: sys/fs/fuse/fuse_vfsops.c =================================================================== --- sys/fs/fuse/fuse_vfsops.c +++ sys/fs/fuse/fuse_vfsops.c @@ -433,7 +433,12 @@ */ mp->mnt_kern_flag |= MNTK_NULL_NOCACHE; MNT_IUNLOCK(mp); - /* We need this here as this slot is used by getnewvnode() */ + /* + * mnt_stat.f_iosize is used by the buffer cache. We choose a sensible + * value ourselves, rather than trusting what the FUSE server reports. + * But this is distinct from the f_iosize that we will report via + * statfs(2). + */ mp->mnt_stat.f_iosize = maxbcachebuf; if (subtype) { strlcat(mp->mnt_stat.f_fstypename, ".", MFSNAMELEN); @@ -682,6 +687,10 @@ sbp->f_ffree = fsfo->st.ffree; /* cast from uint64_t to int64_t */ sbp->f_namemax = fsfo->st.namelen; sbp->f_bsize = fsfo->st.frsize; /* cast from uint32_t to uint64_t */ + if (fsess_opt_datacache(mp)) + sbp->f_iosize = MAX(fsfo->st.bsize, mp->mnt_stat.f_iosize); + else + sbp->f_iosize = fsfo->st.bsize; fdisp_destroy(&fdi); return 0; Index: tests/sys/fs/fusefs/statfs.cc =================================================================== --- tests/sys/fs/fusefs/statfs.cc +++ tests/sys/fs/fusefs/statfs.cc @@ -33,6 +33,7 @@ extern "C" { #include #include +#include #include } @@ -43,6 +44,15 @@ class Statfs: public FuseTest {}; +class Statfs_cached: public Statfs { +public: +virtual void SetUp() { + m_init_flags |= FUSE_WRITEBACK_CACHE; + Statfs::SetUp(); +} +}; + + TEST_F(Statfs, eio) { struct statfs statbuf; @@ -150,24 +160,57 @@ out.body.statfs.st.ffree = 6; out.body.statfs.st.namelen = 128; out.body.statfs.st.frsize = 1024; + out.body.statfs.st.bsize = 131072; }))); ASSERT_NE(nullptr, getcwd(mp, PATH_MAX)) << strerror(errno); strlcat(mp, "/mountpoint", PATH_MAX); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); EXPECT_EQ(1024ul, statbuf.f_bsize); - /* - * fuse(4) ignores the filesystem's reported optimal transfer size, and - * chooses a size that works well with the rest of the system instead - */ EXPECT_EQ(1000ul, statbuf.f_blocks); EXPECT_EQ(100ul, statbuf.f_bfree); EXPECT_EQ(200l, statbuf.f_bavail); EXPECT_EQ(5ul, statbuf.f_files); EXPECT_EQ(6l, statbuf.f_ffree); + EXPECT_EQ(131072ul, statbuf.f_iosize); EXPECT_EQ(128u, statbuf.f_namemax); EXPECT_EQ(getuid(), statbuf.f_owner); EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename)); EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname)); EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname)); } + +/* + * When not using direct I/O fusefs will choose a minimum f_iosize suitable for + * the buffer cache. + */ +TEST_F(Statfs_cached, ok) +{ + struct statfs statbuf; + int maxbcachebuf; + const char *node = "vfs.maxbcachebuf"; + size_t size = sizeof(maxbcachebuf); + + ASSERT_EQ(0, sysctlbyname(node, &maxbcachebuf, &size, NULL, 0)) + << strerror(errno); + + 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); + out.body.statfs.st.blocks = 1000; + out.body.statfs.st.bfree = 100; + out.body.statfs.st.bavail = 200; + out.body.statfs.st.files = 5; + out.body.statfs.st.ffree = 6; + out.body.statfs.st.namelen = 128; + out.body.statfs.st.frsize = 1024; + out.body.statfs.st.bsize = 8192; + }))); + + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + EXPECT_EQ((uint64_t)maxbcachebuf, statbuf.f_iosize); +}