Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107654937
D48473.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
22 KB
Referenced Files
None
Subscribers
None
D48473.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D48473: fusefs: add some tests that invoke nfsd
Attached
Detach File
Event Timeline
Log In to Comment