Page MenuHomeFreeBSD

D48473.id.diff
No OneTemporary

D48473.id.diff

diff --git a/sys/fs/fuse/fuse_file.c b/sys/fs/fuse/fuse_file.c
--- a/sys/fs/fuse/fuse_file.c
+++ b/sys/fs/fuse/fuse_file.c
@@ -221,6 +221,14 @@
out:
counter_u64_add(fuse_fh_count, -1);
LIST_REMOVE(fufh, next);
+#ifdef INVARIANTS
+ fufh->fufh_type = FUFH_INVALID;
+ fufh->fh_id = 0xdeadc0de;
+ fufh->fuse_open_flags = -1;
+ fufh->gid = -1;
+ fufh->pid = -1;
+ fufh->uid = -1;
+#endif
free(fufh, M_FUSE_FILEHANDLE);
return err;
diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c
--- a/sys/fs/fuse/fuse_vfsops.c
+++ b/sys/fs/fuse/fuse_vfsops.c
@@ -274,7 +274,7 @@
if (!(fuse_get_mpdata(mp)->dataflags & FSESS_EXPORT_SUPPORT))
return EOPNOTSUPP;
- error = VFS_VGET(mp, ffhp->nid, LK_EXCLUSIVE, &nvp);
+ error = VFS_VGET(mp, ffhp->nid, flags, &nvp);
if (error) {
*vpp = NULLVP;
return (error);
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
@@ -4,7 +4,7 @@
TESTSDIR= ${TESTSBASE}/sys/fs/fusefs
-# We could simply link all of these files into a single executable. But since
+# We could simply link most of these files into a single executable. But since
# Kyua treats googletest programs as plain tests, it's better to separate them
# out, so we get more granular reporting.
GTESTS+= access
@@ -36,6 +36,7 @@
GTESTS+= mknod
GTESTS+= mount
GTESTS+= nfs
+GTESTS+= nfsd
GTESTS+= notify
GTESTS+= open
GTESTS+= opendir
@@ -64,6 +65,7 @@
TEST_METADATA.default_permissions_privileged+= required_user="root"
TEST_METADATA.mknod+= required_user="root"
TEST_METADATA.nfs+= required_user="root"
+TEST_METADATA.nfsd+= required_user="root"
TEST_METADATA+= timeout=10
@@ -94,5 +96,6 @@
LIBADD+= pthread
LIBADD+= gmock gtest
LIBADD+= util
+LIBADD.nfsd+= jail
.include <bsd.test.mk>
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
@@ -295,7 +295,8 @@
in.body.read.offset,
in.body.read.size);
if (verbosity > 1)
- printf(" flags=%#x", in.body.read.flags);
+ printf(" flags=%#x fh=%#" PRIx64,
+ in.body.read.flags, in.body.read.fh);
break;
case FUSE_READDIR:
printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
@@ -888,6 +889,14 @@
*/
if (0 == strncmp("aiod", ki->ki_comm, 4))
ok = true;
+
+ /*
+ * Allow access by mountd and nfsd, needed by the nfsd tests
+ */
+ if (0 == strncmp("mountd", ki->ki_comm, 6) ||
+ 0 == strncmp("nfsd", ki->ki_comm, 4))
+ ok = true;
+
free(ki);
return (ok);
}
diff --git a/tests/sys/fs/fusefs/nfsd.cc b/tests/sys/fs/fusefs/nfsd.cc
new file mode 100644
--- /dev/null
+++ b/tests/sys/fs/fusefs/nfsd.cc
@@ -0,0 +1,602 @@
+/*-
+ * 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.
+ */
+
+/* This file tests access via the in-kernel nfsd process */
+
+/* TODO:
+ * * read a file twice
+ * * readdir with seekdir/telldir
+ * * file system implements FUSE_OPEN
+ * * file system implements FUSE_OPENDIR
+ * * write a file twice
+ * * race when fuse_vnop_write opens the file handle, if we ever set
+ * MNTK_SHARED_WRITES in fusefs.
+ * * deallocate twice
+ * * race when fuse_vnop_deallocate opens the file handle
+ * * getfhat
+ * * stat a file in a subdirectory. Does this call VP_VPTOFH?
+ */
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/jail.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+
+#include <aio.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <jail.h>
+#include <semaphore.h>
+#include <unistd.h>
+
+#include "mntopts.h" // for build_iovec
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace std;
+using namespace testing;
+
+/* Like /usr/sbin/jexec */
+template<typename... Args>
+static int
+jexec(int jid, const char *cmd, Args... args)
+{
+ int pid, r, status;
+
+ errno = 0;
+ switch (pid = fork()) {
+ case -1:
+ /* fork failed */
+ return -1;
+ case 0:
+ /* In child */
+ if ((r = jail_attach(jid)) < 0) {
+ perror("jail_attach");
+ _exit(r);
+ }
+ if ((r = execlp(cmd, cmd, args..., nullptr)) < 0) {
+ perror("execlp");
+ _exit(r);
+ }
+ break;
+ default:
+ /* In parent */
+ r = waitpid(pid, &status, WEXITED);
+ return (r < 0 || WEXITSTATUS(status) != 0);
+ }
+ return 0;
+}
+
+class Nfsd: public FuseTest {
+int m_jid;
+int m_s;
+struct ifreq m_ifr;
+
+public:
+char *m_mntpnt;
+
+Nfsd(): m_jid(-1), m_s(-1), m_mntpnt(NULL)
+{
+ bzero(&m_ifr, sizeof(m_ifr));
+ m_ifr.ifr_data = (caddr_t)-1;
+ m_init_flags |= FUSE_ASYNC_READ | FUSE_EXPORT_SUPPORT;
+ m_default_permissions = true;
+ m_allow_other = true;
+ m_maxread = 32768;
+ strlcpy(m_ifr.ifr_name, "epair", sizeof(m_ifr.ifr_name));
+};
+
+virtual void SetUp() {
+ const char *nodename = "kern.features.vimage";
+ const char *jailname = "fusefs_nfsd";
+ char mountd_args[MAXPATHLEN];
+ char ifconfig_args[MAXPATHLEN];
+ char pwd[MAXPATHLEN];
+ char epairb[16];
+ char *alpha_idx, *cmd, *exports;
+ int val = 0;
+ size_t size = sizeof(val);
+ int modid;
+ int exportsfd, r;
+
+ // Verify prerequistes
+ if (geteuid() != 0)
+ GTEST_SKIP() << "This test requires a privileged user";
+ if ((modid = modfind("nfsd")) < 0)
+ GTEST_SKIP() <<
+ "This test requires the nfsd module to be available";
+ if ((modid = modfind("if_epair")) < 0)
+ GTEST_SKIP() <<
+ "This test requires the if_epair module to be available";
+ if (0 != sysctlbyname(nodename, &val, &size, NULL, 0))
+ GTEST_SKIP() << "This test requires VIMAGE";
+
+ // Setup general expectations
+ FuseTest::SetUp();
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN);
+ }, Eq(true)),
+ _)
+ ).Times(AtMost(1))
+ .WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPENDIR);
+ }, Eq(true)),
+ _)
+ ).Times(AtMost(1))
+ .WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_GETATTR &&
+ in.header.nodeid == FUSE_ROOT_ID);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
+ {
+ SET_OUT_HEADER_LEN(out, attr);
+ out.body.attr.attr.ino = FUSE_ROOT_ID;
+ out.body.attr.attr.mode = S_IFDIR | 0755;
+ out.body.attr.attr_valid = UINT64_MAX;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([](auto in) {
+ return (in.header.opcode == FUSE_STATFS);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
+ {
+ SET_OUT_HEADER_LEN(out, statfs);
+ })));
+ EXPECT_LOOKUP(FUSE_ROOT_ID, ".")
+ .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = FUSE_ROOT_ID;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ out.body.entry.attr.ino = FUSE_ROOT_ID;
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ })));
+
+
+ // Write the exports file
+ getcwd(pwd, sizeof(pwd));
+ asprintf(&exports,
+ "V4: %s/mountpoint -sec=sys\n"
+ "%s/mountpoint -maproot=root -network 192.0.2.1/24\n", pwd, pwd);
+ exportsfd = open("exports", O_RDWR | O_CREAT | O_TRUNC, 0644);
+ ASSERT_LE(0, exportsfd) << strerror(errno);
+ r = write(exportsfd, exports, strlen(exports));
+ ASSERT_LT(0, r);
+ close(exportsfd);
+ free(exports);
+
+ // create epair interfaces
+ ASSERT_LE(0, m_s = socket(AF_INET, SOCK_DGRAM, 0));
+ ASSERT_LE(0, ioctl(m_s, SIOCIFCREATE2, &m_ifr));
+ strlcpy(epairb, m_ifr.ifr_name, sizeof(epairb));
+ alpha_idx = strrchr(epairb, 'a');
+ *alpha_idx++ = 'b';
+ *alpha_idx = '\0';
+
+ // Create the jail
+ m_jid = jail_setv(JAIL_CREATE,
+ "name", jailname,
+ "vnet", "new", //epairb,
+ "host", NULL,
+ "persist", NULL,
+ "allow.mount", "true",
+ "allow.mount.fusefs", "true",
+ "allow.nfsd", "true",
+ "enforce_statfs", "1",
+ NULL);
+ ASSERT_LE(0, m_jid) << jail_errmsg;
+ sprintf(ifconfig_args, "/sbin/ifconfig %s vnet %s", epairb, jailname);
+ ASSERT_EQ(0, system(ifconfig_args));
+ ASSERT_EQ(0, jexec(m_jid, "/sbin/sysctl", "-q",
+ "vfs.nfsd.testing_disable_grace=1"));
+
+ // Assign IP addresses
+ sprintf(ifconfig_args, "/sbin/ifconfig %s 192.0.2.1/24 up",
+ m_ifr.ifr_name);
+ ASSERT_EQ(0, system(ifconfig_args));
+ ASSERT_EQ(0, jexec(m_jid, "/sbin/ifconfig", epairb, "192.0.2.2/24", "up"));
+
+ // Start rpcbind, mountd, and nfsd
+ // XXX starting rpcbind within the jail fails if it's already running
+ // outside of the jail. How to fix?
+ ASSERT_EQ(0, jexec(m_jid, "/usr/sbin/rpcbind")) << strerror(errno);
+ sprintf(mountd_args, "%s/exports", getcwd(pwd, sizeof(pwd)));
+ ASSERT_EQ(0, jexec(m_jid, "/usr/sbin/mountd", mountd_args));
+ ASSERT_EQ(0, jexec(m_jid, "/usr/sbin/nfsd", "-t"));
+
+ // Mount the NFS client
+ ASSERT_EQ(0, mkdir("client", 0755));
+ asprintf(&m_mntpnt, "%s/client", pwd);
+ /*
+ * Mounting NFS directly with nmount(2) requires sending RPCs to
+ * rpcbind. It's much easier to to use /sbin/mount.
+ */
+ asprintf(&cmd,
+ "/sbin/mount -t nfs -o nfsv4,minorversion=2 192.0.2.2:/ %s",
+ m_mntpnt);
+ system(cmd);
+ free(cmd);
+}
+
+void TearDown() {
+ if (m_mntpnt) {
+ unmount(m_mntpnt, MNT_FORCE);
+ free(m_mntpnt);
+ }
+ rmdir("client");
+ if (m_jid >= 0)
+ jail_remove(m_jid);
+ if (m_s >= 0 && strlen(m_ifr.ifr_name) > 0)
+ ioctl(m_s, SIOCIFDESTROY, &m_ifr);
+ FuseTest::TearDown();
+}
+
+void expect_fsync(uint64_t ino)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_FSYNC &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnErrno(0)));
+}
+
+void expect_lookup(uint64_t parent, const char *path, uint64_t ino,
+ mode_t mode, uint64_t size)
+{
+ EXPECT_LOOKUP(parent, path)
+ .WillRepeatedly(Invoke(
+ ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.ino = ino;
+ out.body.entry.attr.mode = mode;
+ out.body.entry.attr.nlink = 1;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.attr.size = size;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ // nfsd often does lookups for "." when all it knows is the inode. So
+ // set an expectation for that, too.
+ EXPECT_LOOKUP(ino, ".")
+ .WillRepeatedly(Invoke(
+ ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = mode;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.ino = ino;
+ out.body.entry.attr.nlink = 1;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.attr.size = size;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+}
+
+};
+
+/* punch a hole in a file via nfsd, without calling VOP_OPEN */
+TEST_F(Nfsd, fspacectl)
+{
+ const char FULLPATH[] = "client/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ struct spacectl_range rqsr, rmsr;
+ uint64_t ino = 42;
+ uint64_t fsize = 2000;
+ uint64_t offset = 500;
+ uint64_t length = 1000;
+ int fd;
+
+ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, S_IFREG | 0644, fsize);
+ expect_fallocate(ino, offset, length,
+ FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
+ // nfsd sets IO_SYNC during deallocate, for some reason
+ expect_fsync(ino);
+
+ fd = open(FULLPATH, O_RDWR);
+ ASSERT_LE(0, fd) << strerror(errno);
+ rqsr.r_offset = offset;
+ rqsr.r_len = length;
+ EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
+ EXPECT_EQ(0, rmsr.r_len);
+ EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
+
+ leak(fd);
+}
+
+/* Read a file via nfsd, without calling VOP_OPEN */
+TEST_F(Nfsd, read)
+{
+ const char FULLPATH[] = "client/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const char *CONTENTS = "abcdefgh";
+ uint64_t ino = 42;
+ int fd;
+ ssize_t bufsize = strlen(CONTENTS);
+ uint8_t buf[bufsize];
+
+ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, S_IFREG | 0644, bufsize);
+ expect_read(ino, 0, bufsize, bufsize, CONTENTS, 0, 0);
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+
+ ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
+ ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
+
+ leak(fd);
+}
+
+/*
+ * Read a file via nfsd from two threads in a way that can trigger a
+ * use-after-free of a fuse file handle.
+ *
+ * Outline:
+ * - Thread 1 enters fuse_vnop_read, opens a fuse file file handle, and sets
+ * closefufh = true.
+ * - Thread 1 sends FUSE_READ and blocks waiting on the server.
+ * - Thread 2 enters fuse_vnop_read and gets a reference to the fufh created by
+ * thread 1.
+ * - Thread 2 blocks waiting on FUSE_READ from the server
+ * - Thread 1 resumes, and closes the fufh
+ * - Thread 2 resumes. But since it is trying to read a large amount of data,
+ * it must send a second FUSE_READ. It dereferences the now-closed fuse file
+ * handle.
+ *
+ * The NFS client itself will split read operations up into m_maxbcachebuf
+ * sized chunks. So we have to set m_maxread to something less, so that Thread
+ * 2's read gets split up too. That's necessary to trigger the bug
+ * deterministically, but it could happen by chance even without setting
+ * m_maxread.
+ *
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=283448
+ */
+TEST_F(Nfsd, read_race)
+{
+ const char FULLPATH[] = "client/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ uint64_t ino = 42;
+ uint64_t read_unique1 = 9999, read_unique2 = 9998;
+ int fd;
+ ssize_t buf1size = 40;
+ ssize_t buf2size = 65536;
+ ssize_t buf3size = m_maxread;
+ char *buf1, *buf2, *buf3;
+ off_t off1 = 0;
+ off_t off2 = 65536;
+ off_t fsize = 4 * m_maxread;
+ struct aiocb iocb1, iocb2, *paiocb;
+ uint32_t read_size1 = 0, read_size2 = 0;
+ mockfs_buf_out out2;
+ sem_t sem;
+
+ buf1 = new char[buf1size]();
+ buf2 = new char[buf2size]();
+ buf3 = new char[buf3size]();
+ bzero(buf3, buf3size);
+ ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
+
+ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, S_IFREG | 0644, fsize);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READ &&
+ in.header.nodeid == ino &&
+ in.body.read.offset == (uint64_t)off1);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
+ uint32_t size = in.body.read.size;
+
+ out.header.len = sizeof(struct fuse_out_header) + size;
+ bzero(out.body.bytes, size);
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READ &&
+ in.header.nodeid == ino &&
+ in.body.read.offset == (uint64_t)off1 + m_maxread);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in, auto &out __unused) {
+ read_unique1 = in.header.unique;
+ read_size1 = in.body.read.size;
+ sem_post(&sem);
+ }));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READ &&
+ in.header.nodeid == ino &&
+ in.body.read.offset == (uint64_t)off2);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in, auto &out) {
+ read_unique2 = in.header.unique;
+ read_size2 = in.body.read.size;
+
+ // Complete the first read request
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.unique = read_unique1;
+ out0->header.len = sizeof(struct fuse_out_header) + read_size1;
+ out0->header.error = 0;
+ bzero(out0->body.bytes, in.body.read.size);
+ out.push_back(std::move(out0));
+ }));
+ expect_read(ino, off2 + m_maxread, m_maxread, m_maxread, buf3, 0, 0);
+ /* This is the clearest symptom of bug 283448: invalid FH */
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READ &&
+ in.body.read.fh != 0);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ // Disable readahead, which makes the test less deterministic.
+ ASSERT_EQ(0, posix_fadvise(fd, 0, fsize, POSIX_FADV_RANDOM));
+
+ iocb1.aio_nbytes = buf1size;
+ iocb1.aio_fildes = fd;
+ iocb1.aio_buf = buf1;
+ iocb1.aio_offset = off1;
+ iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
+ ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
+
+ sem_wait(&sem);
+
+ iocb2.aio_nbytes = buf2size;
+ iocb2.aio_fildes = fd;
+ iocb2.aio_buf = buf2;
+ iocb2.aio_offset = off2;
+ iocb2.aio_sigevent.sigev_notify = SIGEV_NONE;
+ ASSERT_EQ(0, aio_read(&iocb2)) << strerror(errno);
+
+ /* Wait for the first operation to complete */
+ (void) aio_waitcomplete(&paiocb, NULL);
+
+ /* Now finish the second operation */
+ out2.header.len = sizeof(out2.header) + read_size2;
+ out2.header.error = 0;
+ out2.header.unique = read_unique2;
+ out2.expected_errno = 0;
+ bzero(out2.body.bytes, read_size2);
+ m_mock->write_response(out2);
+
+ (void) aio_waitcomplete(&paiocb, NULL);
+
+ sem_destroy(&sem);
+ delete[] buf3;
+ delete[] buf2;
+ delete[] buf1;
+}
+
+/* FUSE_READDIR returns nothing but "." and ".." */
+TEST_F(Nfsd, readdir)
+{
+ const char FULLPATH[] = "client/some_dir";
+ const char RELPATH[] = "some_dir";
+ const char dot[] = ".";
+ const char dotdot[] = "..";
+ uint64_t ino = 42;
+ DIR *dir;
+ struct dirent *de;
+ vector<struct dirent> ents(2);
+ vector<struct dirent> empty_ents(0);
+
+ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, S_IFDIR | 0755, 0);
+ ents[0].d_fileno = FUSE_ROOT_ID;
+ ents[0].d_off = 2000;
+ ents[0].d_namlen = 2;
+ ents[0].d_type = DT_DIR;
+ strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
+ ents[1].d_fileno = ino;
+ ents[1].d_off = 3000;
+ ents[1].d_namlen = 1;
+ ents[1].d_type = DT_DIR;
+ strncpy(ents[1].d_name, dot, 1);
+ expect_readdir(ino, 0, ents, 0);
+ expect_readdir(ino, 3000, empty_ents, 0);
+ // nfsd does a lookup on every dirent, for some reason
+ expect_lookup(ino, dotdot, FUSE_ROOT_ID, S_IFDIR | 0755, 0);
+ expect_lookup(ino, dot, ino, S_IFDIR | 0755, 0);
+
+ errno = 0;
+ dir = opendir(FULLPATH);
+ ASSERT_NE(nullptr, dir) << strerror(errno);
+
+ errno = 0;
+ de = readdir(dir);
+ ASSERT_NE(nullptr, de) << strerror(errno);
+ EXPECT_EQ(ino, de->d_fileno);
+ EXPECT_EQ(DT_DIR, de->d_type);
+ EXPECT_EQ(1, de->d_namlen);
+ EXPECT_EQ(0, strcmp(dot, de->d_name));
+
+ errno = 0;
+ de = readdir(dir);
+ ASSERT_NE(nullptr, de) << strerror(errno);
+ EXPECT_EQ(DT_DIR, de->d_type);
+ EXPECT_EQ(2, de->d_namlen);
+ EXPECT_EQ(0, strcmp(dotdot, de->d_name));
+
+ ASSERT_EQ(nullptr, readdir(dir));
+ ASSERT_EQ(0, errno);
+
+ leakdir(dir);
+}
+
+/* Write a file via nfsd, without calling VOP_OPEN */
+TEST_F(Nfsd, write)
+{
+ const char FULLPATH[] = "client/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const char *CONTENTS = "abcdefgh";
+ uint64_t ino = 42;
+ int fd;
+ ssize_t bufsize = strlen(CONTENTS);
+
+ expect_lookup(FUSE_ROOT_ID, RELPATH, ino, S_IFREG | 0644, 0);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ const char *buf = (const char*)in.body.bytes +
+ sizeof(struct fuse_write_in);
+
+ return (in.header.opcode == FUSE_WRITE &&
+ in.header.nodeid == ino &&
+ in.body.write.fh == 0 &&
+ in.body.write.offset == 0 &&
+ in.body.write.size == bufsize &&
+ 0 == bcmp(buf, CONTENTS, bufsize));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = bufsize;
+ })));
+
+ fd = open(FULLPATH, O_WRONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+
+ ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
+ close(fd); /* Close file to flush writes to fuse server */
+}
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
@@ -197,7 +197,7 @@
* the provided entries
*/
void expect_readdir(uint64_t ino, uint64_t off,
- std::vector<struct dirent> &ents);
+ std::vector<struct dirent> &ents, uint64_t fh=FH);
/*
* Create an expectation that FUSE_RELEASE will be called exactly once
@@ -226,7 +226,7 @@
*/
void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, uint32_t flags_set, uint32_t flags_unset,
- const void *contents);
+ const void *contents, uint64_t fh = FH);
/* Protocol 7.8 version of expect_write */
void expect_write_7_8(uint64_t ino, uint64_t offset, uint64_t isize,
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
@@ -404,13 +404,13 @@
}
void FuseTest::expect_readdir(uint64_t ino, uint64_t off,
- std::vector<struct dirent> &ents)
+ std::vector<struct dirent> &ents, uint64_t fh)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READDIR &&
in.header.nodeid == ino &&
- in.body.readdir.fh == FH &&
+ in.body.readdir.fh == fh &&
in.body.readdir.offset == off);
}, Eq(true)),
_)
@@ -491,7 +491,7 @@
void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, uint32_t flags_set, uint32_t flags_unset,
- const void *contents)
+ const void *contents, uint64_t fh)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
@@ -509,7 +509,7 @@
return (in.header.opcode == FUSE_WRITE &&
in.header.nodeid == ino &&
- in.body.write.fh == FH &&
+ in.body.write.fh == fh &&
in.body.write.offset == offset &&
in.body.write.size == isize &&
pid_ok &&

File Metadata

Mime Type
text/plain
Expires
Sat, Jan 18, 5:56 AM (4 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15833656
Default Alt Text
D48473.id.diff (22 KB)

Event Timeline