aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/fs/fusefs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/fs/fusefs')
-rw-r--r--tests/sys/fs/fusefs/Makefile16
-rw-r--r--tests/sys/fs/fusefs/allow_other.cc3
-rw-r--r--tests/sys/fs/fusefs/bmap.cc25
-rw-r--r--tests/sys/fs/fusefs/ctl.sh69
-rw-r--r--tests/sys/fs/fusefs/destroy.cc2
-rw-r--r--tests/sys/fs/fusefs/fallocate.cc54
-rw-r--r--tests/sys/fs/fusefs/flush.cc30
-rw-r--r--tests/sys/fs/fusefs/forget.cc1
-rw-r--r--tests/sys/fs/fusefs/io.cc3
-rw-r--r--tests/sys/fs/fusefs/last_local_modify.cc5
-rw-r--r--tests/sys/fs/fusefs/lookup.cc7
-rw-r--r--tests/sys/fs/fusefs/lseek.cc140
-rw-r--r--tests/sys/fs/fusefs/mknod.cc2
-rw-r--r--tests/sys/fs/fusefs/mockfs.cc76
-rw-r--r--tests/sys/fs/fusefs/mockfs.hh20
-rw-r--r--tests/sys/fs/fusefs/mount.cc2
-rw-r--r--tests/sys/fs/fusefs/nfs.cc136
-rw-r--r--tests/sys/fs/fusefs/notify.cc1
-rw-r--r--tests/sys/fs/fusefs/open.cc40
-rw-r--r--tests/sys/fs/fusefs/opendir.cc27
-rw-r--r--tests/sys/fs/fusefs/pre-init.cc154
-rw-r--r--tests/sys/fs/fusefs/read.cc55
-rw-r--r--tests/sys/fs/fusefs/unlink.cc1
-rw-r--r--tests/sys/fs/fusefs/utils.cc27
-rw-r--r--tests/sys/fs/fusefs/utils.hh6
-rw-r--r--tests/sys/fs/fusefs/write.cc7
-rw-r--r--tests/sys/fs/fusefs/xattr.cc52
27 files changed, 806 insertions, 155 deletions
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
index f45f2f93e1c0..a21512798597 100644
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -1,10 +1,11 @@
-
.include <bsd.compiler.mk>
PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/fs/fusefs
+ATF_TESTS_SH+= ctl
+
# We could simply link all 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.
@@ -40,6 +41,7 @@ GTESTS+= nfs
GTESTS+= notify
GTESTS+= open
GTESTS+= opendir
+GTESTS+= pre-init
GTESTS+= read
GTESTS+= readdir
GTESTS+= readlink
@@ -56,7 +58,6 @@ GTESTS+= xattr
.for p in ${GTESTS}
SRCS.$p+= ${p}.cc
-SRCS.$p+= getmntopts.c
SRCS.$p+= mockfs.cc
SRCS.$p+= utils.cc
.endfor
@@ -65,12 +66,14 @@ TEST_METADATA.default_permissions+= required_user="unprivileged"
TEST_METADATA.default_permissions_privileged+= required_user="root"
TEST_METADATA.mknod+= required_user="root"
TEST_METADATA.nfs+= required_user="root"
+# ctl must be exclusive because it disables/enables camsim
+TEST_METADATA.ctl+= is_exclusive="true"
+TEST_METADATA.ctl+= required_user="root"
-# TODO: drastically increase timeout after test development is mostly complete
-TEST_METADATA+= timeout=10
+TEST_METADATA+= timeout=10
+TEST_METADATA+= required_kmods="fusefs"
FUSEFS= ${SRCTOP}/sys/fs/fuse
-MOUNT= ${SRCTOP}/sbin/mount
# Suppress warnings that GCC generates for the libc++ and gtest headers.
CXXWARNFLAGS.gcc+= -Wno-placement-new -Wno-attributes
# Suppress Wcast-align for readdir.cc, because it is unavoidable when using
@@ -89,9 +92,6 @@ CXXWARNFLAGS+= -Wno-vla-cxx-extension
.endif
CXXFLAGS+= -I${SRCTOP}/tests
CXXFLAGS+= -I${FUSEFS}
-CXXFLAGS+= -I${MOUNT}
-.PATH: ${MOUNT}
-CXXSTD= c++14
LIBADD+= pthread
LIBADD+= gmock gtest
diff --git a/tests/sys/fs/fusefs/allow_other.cc b/tests/sys/fs/fusefs/allow_other.cc
index dae6290ea8e5..24a161166a90 100644
--- a/tests/sys/fs/fusefs/allow_other.cc
+++ b/tests/sys/fs/fusefs/allow_other.cc
@@ -52,9 +52,6 @@ const static char RELPATH[] = "some_file.txt";
class NoAllowOther: public FuseTest {
public:
-/* Unprivileged user id */
-int m_uid;
-
virtual void SetUp() {
if (geteuid() != 0) {
GTEST_SKIP() << "This test must be run as root";
diff --git a/tests/sys/fs/fusefs/bmap.cc b/tests/sys/fs/fusefs/bmap.cc
index 4c9edac9360a..30612079657d 100644
--- a/tests/sys/fs/fusefs/bmap.cc
+++ b/tests/sys/fs/fusefs/bmap.cc
@@ -77,8 +77,6 @@ class BmapEof: public Bmap, public WithParamInterface<int> {};
/*
* Test FUSE_BMAP
- * XXX The FUSE protocol does not include the runp and runb variables, so those
- * must be guessed in-kernel.
*/
TEST_F(Bmap, bmap)
{
@@ -105,8 +103,19 @@ TEST_F(Bmap, bmap)
arg.runb = -1;
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
EXPECT_EQ(arg.bn, pbn);
- EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1);
- EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1);
+ /*
+ * XXX The FUSE protocol does not include the runp and runb variables,
+ * so those must be guessed in-kernel. There's no "right" answer, so
+ * just check that they're within reasonable limits.
+ */
+ EXPECT_LE(arg.runb, lbn);
+ EXPECT_LE((unsigned long)arg.runb, m_maxreadahead / m_maxbcachebuf);
+ EXPECT_LE((unsigned long)arg.runb, m_maxphys / m_maxbcachebuf);
+ EXPECT_GT(arg.runb, 0);
+ EXPECT_LE(arg.runp, filesize / m_maxbcachebuf - lbn);
+ EXPECT_LE((unsigned long)arg.runp, m_maxreadahead / m_maxbcachebuf);
+ EXPECT_LE((unsigned long)arg.runp, m_maxphys / m_maxbcachebuf);
+ EXPECT_GT(arg.runp, 0);
leak(fd);
}
@@ -142,7 +151,7 @@ TEST_F(Bmap, default_)
arg.runb = -1;
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
EXPECT_EQ(arg.bn, 0);
- EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1);
+ EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
EXPECT_EQ(arg.runb, 0);
/* In the middle */
@@ -152,8 +161,8 @@ TEST_F(Bmap, default_)
arg.runb = -1;
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
- EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1);
- EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1);
+ EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
+ EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
/* Last block */
lbn = filesize / m_maxbcachebuf - 1;
@@ -163,7 +172,7 @@ TEST_F(Bmap, default_)
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
EXPECT_EQ(arg.runp, 0);
- EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1);
+ EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
leak(fd);
}
diff --git a/tests/sys/fs/fusefs/ctl.sh b/tests/sys/fs/fusefs/ctl.sh
new file mode 100644
index 000000000000..7d2e7593cbdc
--- /dev/null
+++ b/tests/sys/fs/fusefs/ctl.sh
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 ConnectWise
+# All rights reserved.
+#
+# 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 DOCUMENTATION IS PROVIDED BY THE AUTHOR ``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 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.
+
+. $(atf_get_srcdir)/../../cam/ctl/ctl.subr
+
+# Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=283402
+#
+# Almost any fuse file system would work, but this tests uses fusefs-ext2
+# because it's simple and its download is very small.
+atf_test_case remove_lun_with_atime cleanup
+remove_lun_with_atime_head()
+{
+ atf_set "descr" "Remove a fuse-backed CTL LUN when atime is enabled"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "fuse-ext2 mkfs.ext2"
+}
+remove_lun_with_atime_body()
+{
+ MOUNTPOINT=$PWD/mnt
+ atf_check mkdir $MOUNTPOINT
+ atf_check truncate -s 1g ext2.img
+ atf_check mkfs.ext2 -q ext2.img
+ # Note: both default_permissions and atime must be enabled
+ atf_check fuse-ext2 -o default_permissions,allow_other,rw+ ext2.img \
+ $MOUNTPOINT
+
+ atf_check truncate -s 1m $MOUNTPOINT/file
+ create_block -o file=$MOUNTPOINT/file
+
+ # Force fusefs to open the file, and dirty its atime
+ atf_check dd if=/dev/$dev of=/dev/null count=1 status=none
+
+ # Finally, remove the LUN. Hopefully it won't panic.
+ atf_check -o ignore ctladm remove -b block -l $LUN
+
+ rm lun-create.txt # So we don't try to remove the LUN twice
+}
+remove_lun_with_atime_cleanup()
+{
+ cleanup
+ umount $PWD/mnt
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case remove_lun_with_atime
+}
diff --git a/tests/sys/fs/fusefs/destroy.cc b/tests/sys/fs/fusefs/destroy.cc
index 16d50da19b9b..45acb1f99724 100644
--- a/tests/sys/fs/fusefs/destroy.cc
+++ b/tests/sys/fs/fusefs/destroy.cc
@@ -60,7 +60,7 @@ static void* open_th(void* arg) {
* Check for any memory leaks like this:
* 1) kldunload fusefs, if necessary
* 2) kldload fusefs
- * 3) ./destroy --gtest_filter=Destroy.unsent_operations
+ * 3) ./destroy --gtest_filter=Death.unsent_operations
* 4) kldunload fusefs
* 5) check /var/log/messages for anything like this:
Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory.
diff --git a/tests/sys/fs/fusefs/fallocate.cc b/tests/sys/fs/fusefs/fallocate.cc
index ff5e3eb4f4bb..4e5b047b78b7 100644
--- a/tests/sys/fs/fusefs/fallocate.cc
+++ b/tests/sys/fs/fusefs/fallocate.cc
@@ -32,10 +32,9 @@ extern "C" {
#include <sys/time.h>
#include <fcntl.h>
+#include <mntopts.h> // for build_iovec
#include <signal.h>
#include <unistd.h>
-
-#include "mntopts.h" // for build_iovec
}
#include "mockfs.hh"
@@ -310,6 +309,57 @@ TEST_F(Fspacectl, erofs)
leak(fd);
}
+/*
+ * If FUSE_GETATTR fails when determining the size of the file, fspacectl
+ * should fail gracefully. This failure mode is easiest to trigger when
+ * attribute caching is disabled.
+ */
+TEST_F(Fspacectl, getattr_fails)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ Sequence seq;
+ struct spacectl_range rqsr;
+ const uint64_t ino = 42;
+ const uint64_t fsize = 2000;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([](auto in) {
+ return (in.header.opcode == FUSE_GETATTR &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(1)
+ .InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, attr);
+ out.body.attr.attr.ino = ino;
+ out.body.attr.attr.mode = S_IFREG | 0644;
+ out.body.attr.attr.size = fsize;
+ out.body.attr.attr_valid = 0;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([](auto in) {
+ return (in.header.opcode == FUSE_GETATTR &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(ReturnErrno(EIO));
+
+ fd = open(FULLPATH, O_RDWR);
+ ASSERT_LE(0, fd) << strerror(errno);
+ rqsr.r_offset = 500;
+ rqsr.r_len = 1000;
+ EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
+ EXPECT_EQ(EIO, errno);
+
+ leak(fd);
+}
+
TEST_F(Fspacectl, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
diff --git a/tests/sys/fs/fusefs/flush.cc b/tests/sys/fs/fusefs/flush.cc
index 474cdbdb2203..7ba1218b3287 100644
--- a/tests/sys/fs/fusefs/flush.cc
+++ b/tests/sys/fs/fusefs/flush.cc
@@ -109,6 +109,36 @@ TEST_F(Flush, open_twice)
EXPECT_EQ(0, close(fd)) << strerror(errno);
}
+/**
+ * Test for FOPEN_NOFLUSH: we expect that zero flush calls will be performed.
+ */
+TEST_F(Flush, open_noflush)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ uint64_t ino = 42;
+ uint64_t pid = (uint64_t)getpid();
+ int fd;
+
+ expect_lookup(RELPATH, ino, 1);
+ expect_open(ino, FOPEN_NOFLUSH, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_FLUSH &&
+ in.header.nodeid == ino &&
+ in.body.flush.lock_owner == pid &&
+ in.body.flush.fh == FH);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+ expect_release();
+
+ fd = open(FULLPATH, O_WRONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ // close MUST not flush
+ EXPECT_EQ(0, close(fd)) << strerror(errno);
+}
+
/*
* Some FUSE filesystem cache data internally and flush it on release. Such
* filesystems may generate errors during release. On Linux, these get
diff --git a/tests/sys/fs/fusefs/forget.cc b/tests/sys/fs/fusefs/forget.cc
index 846198e75925..1e7764ac4782 100644
--- a/tests/sys/fs/fusefs/forget.cc
+++ b/tests/sys/fs/fusefs/forget.cc
@@ -31,7 +31,6 @@
extern "C" {
#include <sys/types.h>
#include <sys/mount.h>
-#include <sys/sysctl.h>
#include <fcntl.h>
#include <semaphore.h>
diff --git a/tests/sys/fs/fusefs/io.cc b/tests/sys/fs/fusefs/io.cc
index 99b5eae34e09..ced291836da0 100644
--- a/tests/sys/fs/fusefs/io.cc
+++ b/tests/sys/fs/fusefs/io.cc
@@ -31,13 +31,14 @@
extern "C" {
#include <sys/types.h>
#include <sys/mman.h>
-#include <sys/sysctl.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
}
+#include <iomanip>
+
#include "mockfs.hh"
#include "utils.hh"
diff --git a/tests/sys/fs/fusefs/last_local_modify.cc b/tests/sys/fs/fusefs/last_local_modify.cc
index 495bfd8aa959..5fcd3c36c892 100644
--- a/tests/sys/fs/fusefs/last_local_modify.cc
+++ b/tests/sys/fs/fusefs/last_local_modify.cc
@@ -233,7 +233,6 @@ TEST_P(LastLocalModify, lookup)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
- out.body.entry.nodeid = ino;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
@@ -277,6 +276,7 @@ TEST_P(LastLocalModify, lookup)
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.entry.attr.mode = S_IFREG | 0644;
out0->body.entry.nodeid = ino;
+ out0->body.entry.attr.ino = ino;
out0->body.entry.entry_valid = UINT64_MAX;
out0->body.entry.attr_valid = UINT64_MAX;
out0->body.entry.attr.size = oldsize;
@@ -392,7 +392,6 @@ TEST_P(LastLocalModify, vfs_vget)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
- out.body.entry.nodeid = ino;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
@@ -414,7 +413,6 @@ TEST_P(LastLocalModify, vfs_vget)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.nodeid = ino;
out.body.entry.attr.size = oldsize;
- out.body.entry.nodeid = ino;
out.body.entry.attr_valid_nsec = NAP_NS / 2;
out.body.entry.attr.ino = ino;
out.body.entry.attr.mode = S_IFREG | 0644;
@@ -439,6 +437,7 @@ TEST_P(LastLocalModify, vfs_vget)
SET_OUT_HEADER_LEN(*out0, entry);
out0->body.entry.attr.mode = S_IFREG | 0644;
out0->body.entry.nodeid = ino;
+ out0->body.entry.attr.ino = ino;
out0->body.entry.entry_valid = UINT64_MAX;
out0->body.entry.attr_valid = UINT64_MAX;
out0->body.entry.attr.size = oldsize;
diff --git a/tests/sys/fs/fusefs/lookup.cc b/tests/sys/fs/fusefs/lookup.cc
index 6d506c1ab700..2cfe888b6b08 100644
--- a/tests/sys/fs/fusefs/lookup.cc
+++ b/tests/sys/fs/fusefs/lookup.cc
@@ -560,6 +560,7 @@ TEST_F(LookupExportable, dotdot_entry_cache_timeout)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr.ino = foo_ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0; // immediate timeout
})));
@@ -568,6 +569,7 @@ TEST_F(LookupExportable, dotdot_entry_cache_timeout)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr.ino = bar_ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -577,6 +579,7 @@ TEST_F(LookupExportable, dotdot_entry_cache_timeout)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = FUSE_ROOT_ID;
+ out.body.entry.attr.ino = FUSE_ROOT_ID;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -607,6 +610,7 @@ TEST_F(LookupExportable, dotdot_no_parent_nid)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr.ino = foo_ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -615,6 +619,7 @@ TEST_F(LookupExportable, dotdot_no_parent_nid)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr.ino = bar_ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -632,6 +637,7 @@ TEST_F(LookupExportable, dotdot_no_parent_nid)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr.ino = foo_ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -640,6 +646,7 @@ TEST_F(LookupExportable, dotdot_no_parent_nid)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = FUSE_ROOT_ID;
+ out.body.entry.attr.ino = FUSE_ROOT_ID;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
diff --git a/tests/sys/fs/fusefs/lseek.cc b/tests/sys/fs/fusefs/lseek.cc
index 5ffeb4b33cbd..12d41f7af1b2 100644
--- a/tests/sys/fs/fusefs/lseek.cc
+++ b/tests/sys/fs/fusefs/lseek.cc
@@ -71,6 +71,7 @@ TEST_F(LseekPathconf, already_enosys)
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
@@ -105,6 +106,7 @@ TEST_F(LseekPathconf, already_seeked)
out.body.lseek.offset = i.body.lseek.offset;
})));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(offset, lseek(fd, offset, SEEK_DATA));
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
@@ -113,6 +115,76 @@ TEST_F(LseekPathconf, already_seeked)
}
/*
+ * Use pathconf on a file not already opened. The server returns EACCES when
+ * the kernel tries to open it. The kernel should return EACCES, and make no
+ * judgement about whether the server does or does not support FUSE_LSEEK.
+ */
+TEST_F(LseekPathconf, eacces)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.entry_valid = UINT64_MAX;
+ out.body.entry.attr.mode = S_IFREG | 0644;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.size = fsize;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(2)
+ .WillRepeatedly(Invoke(ReturnErrno(EACCES)));
+
+ EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EACCES, errno);
+ /* Check again, to ensure that the kernel didn't record the response */
+ EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EACCES, errno);
+}
+
+/*
+ * If the server returns some weird error when we try FUSE_LSEEK, send that to
+ * the caller but don't record the answer.
+ */
+TEST_F(LseekPathconf, eio)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).Times(2)
+ .WillRepeatedly(Invoke(ReturnErrno(EIO)));
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
+
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EIO, errno);
+ /* Check again, to ensure that the kernel didn't record the response */
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EIO, errno);
+
+ leak(fd);
+}
+
+/*
* If no FUSE_LSEEK operation has been attempted since mount, try once as soon
* as a pathconf request comes in.
*/
@@ -134,6 +206,7 @@ TEST_F(LseekPathconf, enosys_now)
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
EXPECT_EQ(EINVAL, errno);
@@ -142,6 +215,34 @@ TEST_F(LseekPathconf, enosys_now)
}
/*
+ * Use pathconf, rather than fpathconf, on a file not already opened.
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278135
+ */
+TEST_F(LseekPathconf, pathconf)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_out = 1 << 29;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, lseek);
+ out.body.lseek.offset = offset_out;
+ })));
+ expect_release(ino, FuseTest::FH);
+
+ EXPECT_EQ(1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)) << strerror(errno);
+}
+
+/*
* If no FUSE_LSEEK operation has been attempted since mount, try one as soon
* as a pathconf request comes in. This is the typical pattern of bsdtar. It
* will only try SEEK_HOLE/SEEK_DATA if fpathconf says they're supported.
@@ -169,6 +270,7 @@ TEST_F(LseekPathconf, seek_now)
})));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(offset_initial, lseek(fd, offset_initial, SEEK_SET));
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
/* And check that the file pointer hasn't changed */
@@ -178,6 +280,39 @@ TEST_F(LseekPathconf, seek_now)
}
/*
+ * If the user calls pathconf(_, _PC_MIN_HOLE_SIZE) on a fully sparse or
+ * zero-length file, then SEEK_DATA will return ENXIO. That should be
+ * interpreted as success.
+ */
+TEST_F(LseekPathconf, zerolength)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 0;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.whence == SEEK_DATA);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENXIO)));
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ /* Check again, to ensure that the kernel recorded the response */
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+
+ leak(fd);
+}
+
+/*
* For servers using older protocol versions, no FUSE_LSEEK should be attempted
*/
TEST_F(LseekPathconf_7_23, already_enosys)
@@ -198,6 +333,7 @@ TEST_F(LseekPathconf_7_23, already_enosys)
).Times(0);
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
EXPECT_EQ(EINVAL, errno);
@@ -262,6 +398,7 @@ TEST_F(LseekSeekData, enosys)
_)
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
/*
* Default behavior: ENXIO if offset is < 0 or >= fsize, offset
@@ -302,6 +439,7 @@ TEST_F(LseekSeekHole, ok)
out.body.lseek.offset = offset_out;
})));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_HOLE));
EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
@@ -334,6 +472,7 @@ TEST_F(LseekSeekHole, enosys)
_)
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
/*
* Default behavior: ENXIO if offset is < 0 or >= fsize, fsize
@@ -371,6 +510,7 @@ TEST_F(LseekSeekHole, enxio)
_)
).WillOnce(Invoke(ReturnErrno(ENXIO)));
fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd);
EXPECT_EQ(-1, lseek(fd, offset_in, SEEK_HOLE));
EXPECT_EQ(ENXIO, errno);
diff --git a/tests/sys/fs/fusefs/mknod.cc b/tests/sys/fs/fusefs/mknod.cc
index 1fb855f44f29..eb745f19acd5 100644
--- a/tests/sys/fs/fusefs/mknod.cc
+++ b/tests/sys/fs/fusefs/mknod.cc
@@ -283,7 +283,7 @@ TEST_F(Mknod, parent_inode)
}
/*
- * fusefs(5) lacks VOP_WHITEOUT support. No bugzilla entry, because that's a
+ * fusefs(4) lacks VOP_WHITEOUT support. No bugzilla entry, because that's a
* feature, not a bug
*/
TEST_F(Mknod, DISABLED_whiteout)
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index bd7bd1b663f9..65cdc3919652 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -39,13 +39,12 @@ extern "C" {
#include <fcntl.h>
#include <libutil.h>
+#include <mntopts.h> // for build_iovec
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
-
-#include "mntopts.h" // for build_iovec
}
#include <cinttypes>
@@ -416,11 +415,25 @@ void MockFS::debug_response(const mockfs_buf_out &out) {
}
}
-MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
+MockFS::MockFS(int max_read, int max_readahead, bool allow_other,
+ bool default_permissions,
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),
+ m_maxread(max_read),
+ m_maxreadahead(max_readahead),
+ m_pid(getpid()),
+ m_uniques(new std::unordered_set<uint64_t>),
+ m_pm(pm),
+ m_time_gran(time_gran),
+ m_child_pid(-1),
+ m_maxwrite(MIN(max_write, max_max_write)),
+ m_nready(-1),
+ m_quit(false)
{
struct sigaction sa;
struct iovec *iov = NULL;
@@ -428,20 +441,6 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
char fdstr[15];
const bool trueval = true;
- m_daemon_id = NULL;
- m_kernel_minor_version = kernel_minor_version;
- m_maxreadahead = max_readahead;
- m_maxwrite = MIN(max_write, max_max_write);
- m_nready = -1;
- m_pm = pm;
- m_time_gran = time_gran;
- m_quit = false;
- m_last_unique = 0;
- if (m_pm == KQ)
- m_kq = kqueue();
- else
- m_kq = -1;
-
/*
* Kyua sets pwd to a testcase-unique tempdir; no need to use
* mkdtemp
@@ -466,15 +465,18 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
throw(std::system_error(errno, std::system_category(),
"Couldn't open /dev/fuse"));
- m_pid = getpid();
- m_child_pid = -1;
-
build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
build_iovec(&iov, &iovlen, "fspath",
__DECONST(void *, "mountpoint"), -1);
build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
sprintf(fdstr, "%d", m_fuse_fd);
build_iovec(&iov, &iovlen, "fd", fdstr, -1);
+ if (m_maxread > 0) {
+ char val[10];
+
+ snprintf(val, sizeof(val), "%d", m_maxread);
+ build_iovec(&iov, &iovlen, "max_read=", &val, -1);
+ }
if (allow_other) {
build_iovec(&iov, &iovlen, "allow_other",
__DECONST(void*, &trueval), sizeof(bool));
@@ -527,7 +529,9 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
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! */
@@ -541,10 +545,7 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
MockFS::~MockFS() {
kill_daemon();
- if (m_daemon_id != NULL) {
- pthread_join(m_daemon_id, NULL);
- m_daemon_id = NULL;
- }
+ join_daemon();
::unmount("mountpoint", MNT_FORCE);
rmdir("mountpoint");
if (m_kq >= 0)
@@ -738,14 +739,10 @@ void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
default:
FAIL() << "Unknown opcode " << in.header.opcode;
}
- /*
- * Check that the ticket's unique value is sequential. Technically it
- * doesn't need to be sequential, merely unique. But the current
- * fusefs driver _does_ make it sequential, and that's easy to check
- * for.
- */
- if (in.header.unique != ++m_last_unique)
- FAIL() << "Non-sequential unique value";
+ /* Verify that the ticket's unique value is actually unique. */
+ if (m_uniques->find(in.header.unique) != m_uniques->end())
+ FAIL() << "Non-unique \"unique\" value";
+ m_uniques->insert(in.header.unique);
}
void MockFS::init(uint32_t flags) {
@@ -789,6 +786,13 @@ void MockFS::kill_daemon() {
m_fuse_fd = -1;
}
+void MockFS::join_daemon() {
+ if (m_daemon_id != NULL) {
+ pthread_join(m_daemon_id, NULL);
+ m_daemon_id = NULL;
+ }
+}
+
void MockFS::loop() {
std::vector<std::unique_ptr<mockfs_buf_out>> out;
@@ -1035,6 +1039,10 @@ void MockFS::write_response(const mockfs_buf_out &out) {
ASSERT_EQ(-1, r);
ASSERT_EQ(out.expected_errno, errno) << strerror(errno);
} else {
+ if (r <= 0 && errno == EINVAL) {
+ printf("Failed to write response. unique=%" PRIu64
+ ":\n", out.header.unique);
+ }
ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
}
}
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
index 958964f769d4..ba6f7fded9d0 100644
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -36,6 +36,8 @@ extern "C" {
#include "fuse_kernel.h"
}
+#include <unordered_set>
+
#include <gmock/gmock.h>
#define TIME_T_MAX (std::numeric_limits<time_t>::max())
@@ -292,14 +294,20 @@ class MockFS {
int m_kq;
+ /*
+ * If nonzero, the maximum size in bytes of a read that the kernel will
+ * send to the server.
+ */
+ int m_maxread;
+
/* The max_readahead file system option */
uint32_t m_maxreadahead;
/* pid of the test process */
pid_t m_pid;
- /* The unique value of the header of the last received operation */
- uint64_t m_last_unique;
+ /* Every "unique" value of a fuse ticket seen so far */
+ std::unique_ptr<std::unordered_set<uint64_t>> m_uniques;
/* Method the daemon should use for I/O to and from /dev/fuse */
enum poll_method m_pm;
@@ -353,18 +361,22 @@ class MockFS {
bool m_quit;
/* Create a new mockfs and mount it to a tempdir */
- MockFS(int max_readahead, bool allow_other,
+ MockFS(int max_read, int max_readahead, bool allow_other,
bool default_permissions, 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 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();
/* Kill the filesystem daemon without unmounting the filesystem */
void kill_daemon();
+ /* Wait until the daemon thread terminates */
+ void join_daemon();
+
/* Process FUSE requests endlessly */
void loop();
diff --git a/tests/sys/fs/fusefs/mount.cc b/tests/sys/fs/fusefs/mount.cc
index 7a8d2c1396f0..ece518b09f66 100644
--- a/tests/sys/fs/fusefs/mount.cc
+++ b/tests/sys/fs/fusefs/mount.cc
@@ -33,7 +33,7 @@ extern "C" {
#include <sys/mount.h>
#include <sys/uio.h>
-#include "mntopts.h" // for build_iovec
+#include <mntopts.h> // for build_iovec
}
#include "mockfs.hh"
diff --git a/tests/sys/fs/fusefs/nfs.cc b/tests/sys/fs/fusefs/nfs.cc
index 79fead8e77cb..2fa2b290f383 100644
--- a/tests/sys/fs/fusefs/nfs.cc
+++ b/tests/sys/fs/fusefs/nfs.cc
@@ -84,6 +84,7 @@ TEST_F(Fhstat, estale)
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.generation = 1;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0;
@@ -95,6 +96,7 @@ TEST_F(Fhstat, estale)
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.generation = 2;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0;
@@ -121,6 +123,7 @@ TEST_F(Fhstat, lookup_dot)
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.generation = 1;
out.body.entry.attr.uid = uid;
out.body.entry.attr_valid = UINT64_MAX;
@@ -132,6 +135,7 @@ TEST_F(Fhstat, lookup_dot)
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.generation = 1;
out.body.entry.attr.uid = uid;
out.body.entry.attr_valid = UINT64_MAX;
@@ -144,6 +148,37 @@ TEST_F(Fhstat, lookup_dot)
EXPECT_EQ(mode, sb.st_mode);
}
+/* Gracefully handle failures to lookup ".". */
+TEST_F(Fhstat, lookup_dot_error)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t ino = 42;
+ const mode_t mode = S_IFDIR | 0755;
+ const uid_t uid = 12345;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
+ .WillOnce(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.generation = 1;
+ out.body.entry.attr.uid = uid;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(ino, ".")
+ .WillOnce(Invoke(ReturnErrno(EDOOFUS)));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ ASSERT_EQ(-1, fhstat(&fhp, &sb));
+ EXPECT_EQ(EDOOFUS, errno);
+}
+
/* Use a file handle whose entry is still cached */
TEST_F(Fhstat, cached)
{
@@ -159,6 +194,7 @@ TEST_F(Fhstat, cached)
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.generation = 1;
out.body.entry.attr.ino = ino;
out.body.entry.attr_valid = UINT64_MAX;
@@ -185,6 +221,7 @@ TEST_F(Fhstat, cache_expired)
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.generation = 1;
out.body.entry.attr.ino = ino;
out.body.entry.attr_valid = UINT64_MAX;
@@ -196,6 +233,7 @@ TEST_F(Fhstat, cache_expired)
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.generation = 1;
out.body.entry.attr.ino = ino;
out.body.entry.attr_valid = UINT64_MAX;
@@ -213,6 +251,99 @@ TEST_F(Fhstat, cache_expired)
EXPECT_EQ(ino, sb.st_ino);
}
+/*
+ * If the server returns a FUSE_LOOKUP response for a nodeid that we didn't
+ * lookup, it's a bug. But we should handle it gracefully.
+ */
+TEST_F(Fhstat, inconsistent_nodeid)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t ino_in = 42;
+ const uint64_t ino_out = 43;
+ const mode_t mode = S_IFDIR | 0755;
+ const uid_t uid = 12345;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = ino_in;
+ out.body.entry.attr.ino = ino_in;
+ out.body.entry.attr.mode = mode;
+ out.body.entry.generation = 1;
+ out.body.entry.attr.uid = uid;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(ino_in, ".")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = ino_out;
+ out.body.entry.attr.ino = ino_out;
+ out.body.entry.attr.mode = mode;
+ out.body.entry.generation = 1;
+ out.body.entry.attr.uid = uid;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ EXPECT_NE(0, fhstat(&fhp, &sb)) << strerror(errno);
+ EXPECT_EQ(EIO, errno);
+}
+
+/*
+ * If the server returns a FUSE_LOOKUP response where the nodeid doesn't match
+ * the inode number, and the file system is exported, it's a bug. But we
+ * should handle it gracefully.
+ */
+TEST_F(Fhstat, inconsistent_ino)
+{
+ const char FULLPATH[] = "mountpoint/some_dir/.";
+ const char RELDIRPATH[] = "some_dir";
+ fhandle_t fhp;
+ struct stat sb;
+ const uint64_t nodeid = 42;
+ const uint64_t ino = 711; // Could be anything that != nodeid
+ const mode_t mode = S_IFDIR | 0755;
+ const uid_t uid = 12345;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = nodeid;
+ out.body.entry.attr.ino = nodeid;
+ out.body.entry.attr.mode = mode;
+ out.body.entry.generation = 1;
+ out.body.entry.attr.uid = uid;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0;
+ })));
+
+ EXPECT_LOOKUP(nodeid, ".")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.nodeid = nodeid;
+ out.body.entry.attr.ino = ino;
+ out.body.entry.attr.mode = mode;
+ out.body.entry.generation = 1;
+ out.body.entry.attr.uid = uid;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0;
+ })));
+
+ ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
+ /*
+ * The fhstat operation will actually succeed. But future operations
+ * will likely fail.
+ */
+ ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
+ EXPECT_EQ(ino, sb.st_ino);
+}
+
/*
* If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
* lookups
@@ -230,6 +361,7 @@ TEST_F(FhstatNotExportable, lookup_dot)
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.generation = 1;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0;
@@ -252,6 +384,7 @@ TEST_F(Getfh, eoverflow)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = ino;
+ out.body.entry.attr.ino = ino;
out.body.entry.generation = (uint64_t)UINT32_MAX + 1;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
@@ -274,6 +407,7 @@ TEST_F(Getfh, ok)
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = S_IFDIR | 0755;
out.body.entry.nodeid = ino;
+ out.body.entry.attr.ino = ino;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = UINT64_MAX;
})));
@@ -305,6 +439,7 @@ TEST_F(Readdir, getdirentries)
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.generation = 1;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0;
@@ -315,6 +450,7 @@ TEST_F(Readdir, getdirentries)
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.generation = 1;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.entry_valid = 0;
diff --git a/tests/sys/fs/fusefs/notify.cc b/tests/sys/fs/fusefs/notify.cc
index e3f539f57599..1e22bde13db7 100644
--- a/tests/sys/fs/fusefs/notify.cc
+++ b/tests/sys/fs/fusefs/notify.cc
@@ -30,7 +30,6 @@
extern "C" {
#include <sys/types.h>
-#include <sys/sysctl.h>
#include <fcntl.h>
#include <pthread.h>
diff --git a/tests/sys/fs/fusefs/open.cc b/tests/sys/fs/fusefs/open.cc
index 7ab3aeb6ba2a..1212a7047f26 100644
--- a/tests/sys/fs/fusefs/open.cc
+++ b/tests/sys/fs/fusefs/open.cc
@@ -70,16 +70,8 @@ void test_ok(int os_flags, int fuse_flags) {
}
};
-
-class OpenNoOpenSupport: public FuseTest {
- virtual void SetUp() {
- m_init_flags = FUSE_NO_OPEN_SUPPORT;
- FuseTest::SetUp();
- }
-};
-
/*
- * fusefs(5) does not support I/O on device nodes (neither does UFS). But it
+ * fusefs(4) does not support I/O on device nodes (neither does UFS). But it
* shouldn't crash
*/
TEST_F(Open, chr)
@@ -281,37 +273,11 @@ TEST_F(Open, o_rdwr)
}
/*
- * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error
- */
-TEST_F(Open, enosys)
-{
- const char FULLPATH[] = "mountpoint/some_file.txt";
- const char RELPATH[] = "some_file.txt";
- uint64_t ino = 42;
- int fd;
-
- FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
- EXPECT_CALL(*m_mock, process(
- ResultOf([=](auto in) {
- return (in.header.opcode == FUSE_OPEN &&
- in.body.open.flags == (uint32_t)O_RDONLY &&
- in.header.nodeid == ino);
- }, Eq(true)),
- _)
- ).Times(1)
- .WillOnce(Invoke(ReturnErrno(ENOSYS)));
-
- fd = open(FULLPATH, O_RDONLY);
- ASSERT_EQ(-1, fd) << strerror(errno);
- EXPECT_EQ(ENOSYS, errno);
-}
-
-/*
- * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a
+ * If a fuse server returns ENOSYS to a
* FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will
* also succeed automatically without being sent to the server.
*/
-TEST_F(OpenNoOpenSupport, enosys)
+TEST_F(Open, enosys)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
diff --git a/tests/sys/fs/fusefs/opendir.cc b/tests/sys/fs/fusefs/opendir.cc
index dd837a8d43c1..e1fed59635fc 100644
--- a/tests/sys/fs/fusefs/opendir.cc
+++ b/tests/sys/fs/fusefs/opendir.cc
@@ -71,13 +71,6 @@ void expect_opendir(uint64_t ino, uint32_t flags, ProcessMockerT r)
};
-class OpendirNoOpendirSupport: public Opendir {
- virtual void SetUp() {
- m_init_flags = FUSE_NO_OPENDIR_SUPPORT;
- FuseTest::SetUp();
- }
-};
-
/*
* The fuse daemon fails the request with enoent. This usually indicates a
@@ -179,27 +172,11 @@ TEST_F(Opendir, opendir)
}
/*
- * Without FUSE_NO_OPENDIR_SUPPORT, returning ENOSYS is an error
- */
-TEST_F(Opendir, enosys)
-{
- const char FULLPATH[] = "mountpoint/some_file.txt";
- const char RELPATH[] = "some_file.txt";
- uint64_t ino = 42;
-
- expect_lookup(RELPATH, ino);
- expect_opendir(ino, O_RDONLY, ReturnErrno(ENOSYS));
-
- EXPECT_EQ(-1, open(FULLPATH, O_DIRECTORY));
- EXPECT_EQ(ENOSYS, errno);
-}
-
-/*
- * If a fuse server sets FUSE_NO_OPENDIR_SUPPORT and returns ENOSYS to a
+ * If a fuse server returns ENOSYS to a
* FUSE_OPENDIR, then it and subsequent FUSE_OPENDIR and FUSE_RELEASEDIR
* operations will also succeed automatically without being sent to the server.
*/
-TEST_F(OpendirNoOpendirSupport, enosys)
+TEST_F(Opendir, enosys)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
diff --git a/tests/sys/fs/fusefs/pre-init.cc b/tests/sys/fs/fusefs/pre-init.cc
new file mode 100644
index 000000000000..e990d3cafffa
--- /dev/null
+++ b/tests/sys/fs/fusefs/pre-init.cc
@@ -0,0 +1,154 @@
+/*-
+ * 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 <sys/param.h>
+#include <sys/mount.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+}
+
+#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 {
+void SetUp() {
+ m_no_auto_init = true;
+ FuseTest::SetUp();
+}
+};
+
+static void* unmount1(void* arg __unused) {
+ ssize_t r;
+
+ r = unmount("mountpoint", 0);
+ if (r >= 0)
+ return 0;
+ else
+ return (void*)(intptr_t)errno;
+}
+
+/*
+ * Attempting to unmount the file system before it fully initializes should
+ * work fine. The unmount will complete after initialization does.
+ */
+TEST_F(PreInit, unmount_before_init)
+{
+ sem_t sem0;
+ pthread_t th1;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+
+ 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;
+ sem_wait(&sem0);
+ })));
+ expect_destroy(0);
+
+ ASSERT_EQ(0, pthread_create(&th1, NULL, unmount1, NULL));
+ nap(); /* Wait for th1 to block in unmount() */
+ sem_post(&sem0);
+ /* The daemon will quit after receiving FUSE_DESTROY */
+ m_mock->join_daemon();
+}
+
+/*
+ * Don't panic in this very specific scenario:
+ *
+ * The server does not respond to FUSE_INIT in timely fashion.
+ * Some other process tries to do unmount.
+ * That other process gets killed by a signal.
+ * The server finally responds to FUSE_INIT.
+ *
+ * Regression test for bug 287438
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=287438
+ */
+TEST_F(PreInit, signal_during_unmount_before_init)
+{
+ sem_t sem0;
+ pid_t child;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+
+ 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;
+ /*
+ * Use protocol 7.19, like libfuse2 does. The server must use
+ * protocol 7.27 or older to trigger the bug.
+ */
+ out.body.init.minor = 19;
+ 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;
+ sem_wait(&sem0);
+ })));
+
+ if ((child = ::fork()) == 0) {
+ /*
+ * In child. This will block waiting for FUSE_INIT to complete
+ * or the receipt of an asynchronous signal.
+ */
+ (void) unmount("mountpoint", 0);
+ _exit(0); /* Unreachable, unless parent dies after fork */
+ } else if (child > 0) {
+ /* In parent. Wait for child process to start, then kill it */
+ nap();
+ kill(child, SIGINT);
+ waitpid(child, NULL, WEXITED);
+ } else {
+ FAIL() << strerror(errno);
+ }
+ m_mock->m_quit = true; /* Since we are by now unmounted. */
+ sem_post(&sem0);
+ m_mock->join_daemon();
+}
diff --git a/tests/sys/fs/fusefs/read.cc b/tests/sys/fs/fusefs/read.cc
index 373f742d4fd3..e9c79ba2ffda 100644
--- a/tests/sys/fs/fusefs/read.cc
+++ b/tests/sys/fs/fusefs/read.cc
@@ -111,6 +111,13 @@ class ReadAhead: public Read,
}
};
+class ReadMaxRead: public Read {
+ virtual void SetUp() {
+ m_maxread = 16384;
+ Read::SetUp();
+ }
+};
+
class ReadNoatime: public Read {
virtual void SetUp() {
m_noatime = true;
@@ -840,6 +847,52 @@ TEST_F(Read, mmap)
leak(fd);
}
+
+/* When max_read is set, large reads will be split up as necessary */
+TEST_F(ReadMaxRead, split)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ uint64_t ino = 42;
+ int fd;
+ ssize_t bufsize = 65536;
+ ssize_t fragsize = bufsize / 4;
+ char *rbuf, *frag0, *frag1, *frag2, *frag3;
+
+ rbuf = new char[bufsize]();
+ frag0 = new char[fragsize]();
+ frag1 = new char[fragsize]();
+ frag2 = new char[fragsize]();
+ frag3 = new char[fragsize]();
+ memset(frag0, '0', fragsize);
+ memset(frag1, '1', fragsize);
+ memset(frag2, '2', fragsize);
+ memset(frag3, '3', fragsize);
+
+ expect_lookup(RELPATH, ino, bufsize);
+ expect_open(ino, 0, 1);
+ expect_read(ino, 0, fragsize, fragsize, frag0);
+ expect_read(ino, fragsize, fragsize, fragsize, frag1);
+ expect_read(ino, 2 * fragsize, fragsize, fragsize, frag2);
+ expect_read(ino, 3 * fragsize, fragsize, fragsize, frag3);
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+
+ ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
+ ASSERT_EQ(0, memcmp(rbuf, frag0, fragsize));
+ ASSERT_EQ(0, memcmp(rbuf + fragsize, frag1, fragsize));
+ ASSERT_EQ(0, memcmp(rbuf + 2 * fragsize, frag2, fragsize));
+ ASSERT_EQ(0, memcmp(rbuf + 3 * fragsize, frag3, fragsize));
+
+ delete[] frag3;
+ delete[] frag2;
+ delete[] frag1;
+ delete[] frag0;
+ delete[] rbuf;
+ leak(fd);
+}
+
/*
* The kernel should not update the cached atime attribute during a read, if
* MNT_NOATIME is used.
@@ -1339,7 +1392,7 @@ TEST_P(ReadAhead, readahead) {
expect_open(ino, 0, 1);
maxcontig = m_noclusterr ? m_maxbcachebuf :
m_maxbcachebuf + m_maxreadahead;
- clustersize = MIN(maxcontig, m_maxphys);
+ clustersize = MIN((unsigned long )maxcontig, m_maxphys);
for (offs = 0; offs < bufsize; offs += clustersize) {
len = std::min((size_t)clustersize, (size_t)(filesize - offs));
expect_read(ino, offs, len, len, contents + offs);
diff --git a/tests/sys/fs/fusefs/unlink.cc b/tests/sys/fs/fusefs/unlink.cc
index db54e286d85d..1d8a371649ee 100644
--- a/tests/sys/fs/fusefs/unlink.cc
+++ b/tests/sys/fs/fusefs/unlink.cc
@@ -1,6 +1,5 @@
/*-
* Copyright (c) 2019 The FreeBSD Foundation
- * All rights reserved.
*
* This software was developed by BFF Storage Systems, LLC under sponsorship
* from the FreeBSD Foundation.
diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc
index 55a552e28eeb..125b7e2d6fc7 100644
--- a/tests/sys/fs/fusefs/utils.cc
+++ b/tests/sys/fs/fusefs/utils.cc
@@ -124,29 +124,21 @@ bool is_unsafe_aio_enabled(void) {
class FuseEnv: public Environment {
virtual void SetUp() {
+ check_environment();
}
};
void FuseTest::SetUp() {
const char *maxbcachebuf_node = "vfs.maxbcachebuf";
const char *maxphys_node = "kern.maxphys";
- int val = 0;
- size_t size = sizeof(val);
-
- /*
- * XXX check_environment should be called from FuseEnv::SetUp, but
- * can't due to https://github.com/google/googletest/issues/2189
- */
- check_environment();
- if (IsSkipped())
- return;
+ size_t size;
- ASSERT_EQ(0, sysctlbyname(maxbcachebuf_node, &val, &size, NULL, 0))
- << strerror(errno);
- m_maxbcachebuf = val;
- ASSERT_EQ(0, sysctlbyname(maxphys_node, &val, &size, NULL, 0))
+ size = sizeof(m_maxbcachebuf);
+ ASSERT_EQ(0, sysctlbyname(maxbcachebuf_node, &m_maxbcachebuf, &size,
+ NULL, 0)) << strerror(errno);
+ size = sizeof(m_maxphys);
+ ASSERT_EQ(0, sysctlbyname(maxphys_node, &m_maxphys, &size, NULL, 0))
<< strerror(errno);
- m_maxphys = val;
/*
* Set the default max_write to a distinct value from MAXPHYS to catch
* bugs that confuse the two.
@@ -155,11 +147,12 @@ void FuseTest::SetUp() {
m_maxwrite = MIN(libfuse_max_write, (uint32_t)m_maxphys / 2);
try {
- m_mock = new MockFS(m_maxreadahead, m_allow_other,
+ m_mock = new MockFS(m_maxread, m_maxreadahead, m_allow_other,
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
diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh
index 383f01ea4bfe..91bbba909672 100644
--- a/tests/sys/fs/fusefs/utils.hh
+++ b/tests/sys/fs/fusefs/utils.hh
@@ -55,6 +55,7 @@ bool is_unsafe_aio_enabled(void);
extern const uint32_t libfuse_max_write;
class FuseTest : public ::testing::Test {
protected:
+ uint32_t m_maxread;
uint32_t m_maxreadahead;
uint32_t m_maxwrite;
uint32_t m_init_flags;
@@ -68,6 +69,7 @@ class FuseTest : public ::testing::Test {
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;
@@ -77,9 +79,10 @@ class FuseTest : public ::testing::Test {
public:
int m_maxbcachebuf;
- int m_maxphys;
+ unsigned long m_maxphys;
FuseTest():
+ m_maxread(0),
m_maxreadahead(0),
m_maxwrite(0),
m_init_flags(0),
@@ -93,6 +96,7 @@ class FuseTest : public ::testing::Test {
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/write.cc b/tests/sys/fs/fusefs/write.cc
index f931f350a7c3..1fe2e3cc522d 100644
--- a/tests/sys/fs/fusefs/write.cc
+++ b/tests/sys/fs/fusefs/write.cc
@@ -179,12 +179,12 @@ class WriteCluster: public WriteBack {
public:
virtual void SetUp() {
m_async = true;
- m_maxwrite = 1 << 25; // Anything larger than MAXPHYS will suffice
+ m_maxwrite = UINT32_MAX; // Anything larger than MAXPHYS will suffice
WriteBack::SetUp();
if (m_maxphys < 2 * DFLTPHYS)
GTEST_SKIP() << "MAXPHYS must be at least twice DFLTPHYS"
<< " for this test";
- if (m_maxphys < 2 * m_maxbcachebuf)
+ if (m_maxphys < 2 * (unsigned long )m_maxbcachebuf)
GTEST_SKIP() << "MAXPHYS must be at least twice maxbcachebuf"
<< " for this test";
}
@@ -860,7 +860,8 @@ TEST_F(WriteMaxWrite, write)
ssize_t halfbufsize, bufsize;
halfbufsize = m_mock->m_maxwrite;
- if (halfbufsize >= m_maxbcachebuf || halfbufsize >= m_maxphys)
+ if (halfbufsize >= m_maxbcachebuf ||
+ (unsigned long )halfbufsize >= m_maxphys)
GTEST_SKIP() << "Must lower m_maxwrite for this test";
bufsize = halfbufsize * 2;
contents = new int[bufsize / sizeof(int)];
diff --git a/tests/sys/fs/fusefs/xattr.cc b/tests/sys/fs/fusefs/xattr.cc
index 7fa2a741e074..0ab203c96254 100644
--- a/tests/sys/fs/fusefs/xattr.cc
+++ b/tests/sys/fs/fusefs/xattr.cc
@@ -110,6 +110,8 @@ void expect_setxattr(uint64_t ino, const char *attr, const char *value,
const char *v = a + strlen(a) + 1;
return (in.header.opcode == FUSE_SETXATTR &&
in.header.nodeid == ino &&
+ in.body.setxattr.size == (strlen(value) + 1) &&
+ in.body.setxattr.setxattr_flags == 0 &&
0 == strcmp(attr, a) &&
0 == strcmp(value, v));
}, Eq(true)),
@@ -119,6 +121,33 @@ void expect_setxattr(uint64_t ino, const char *attr, const char *value,
};
+class Xattr_7_32:public FuseTest {
+public:
+virtual void SetUp()
+{
+ m_kernel_minor_version = 32;
+ FuseTest::SetUp();
+}
+
+void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value,
+ ProcessMockerT r)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ const char *a = (const char *)in.body.bytes +
+ FUSE_COMPAT_SETXATTR_IN_SIZE;
+ const char *v = a + strlen(a) + 1;
+ return (in.header.opcode == FUSE_SETXATTR &&
+ in.header.nodeid == ino &&
+ in.body.setxattr.size == (strlen(value) + 1) &&
+ 0 == strcmp(attr, a) &&
+ 0 == strcmp(value, v));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(r));
+}
+};
+
class Getxattr: public Xattr {};
class Listxattr: public Xattr {};
@@ -153,6 +182,7 @@ void TearDown() {
class Removexattr: public Xattr {};
class Setxattr: public Xattr {};
+class Setxattr_7_32:public Xattr_7_32 {};
class RofsXattr: public Xattr {
public:
virtual void SetUp() {
@@ -350,7 +380,7 @@ TEST_F(Listxattr, enotsup)
* On Linux, however, the file system is supposed to return ERANGE if an
* insufficiently large buffer is passed to listxattr(2).
*
- * fusefs(5) must guarantee the usual FreeBSD behavior.
+ * fusefs(4) must guarantee the usual FreeBSD behavior.
*/
TEST_F(Listxattr, erange)
{
@@ -569,7 +599,7 @@ TEST_F(Listxattr, size_only_race_smaller)
}));
expect_listxattr(ino, sizeof(attrs0),
ReturnImmediate([&](auto in __unused, auto& out) {
- strlcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
+ memcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
out.header.len = sizeof(fuse_out_header) +
sizeof(attrs1);
})
@@ -728,6 +758,7 @@ TEST_F(Removexattr, system)
<< strerror(errno);
}
+
/*
* If the filesystem returns ENOSYS, then it will be treated as a permanent
* failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
@@ -815,6 +846,23 @@ TEST_F(Setxattr, system)
ASSERT_EQ(value_len, r) << strerror(errno);
}
+
+TEST_F(Setxattr_7_32, ok)
+{
+ uint64_t ino = 42;
+ const char value[] = "whatever";
+ ssize_t value_len = strlen(value) + 1;
+ int ns = EXTATTR_NAMESPACE_USER;
+ ssize_t r;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
+ expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0));
+
+ r = extattr_set_file(FULLPATH, ns, "foo", (const void *)value,
+ value_len);
+ ASSERT_EQ(value_len, r) << strerror(errno);
+}
+
TEST_F(RofsXattr, deleteextattr_erofs)
{
uint64_t ino = 42;