aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAlan Somers <asomers@gmail.com>2020-12-29 01:25:21 +0000
committerAlan Somers <asomers@FreeBSD.org>2021-01-01 17:18:23 +0000
commit92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba (patch)
tree4a5fc1b96281a02f3e9b9c0dc6c1de90fcf06d41 /tests
parentae39db74066a0ff1682c1c841be030099d9d4557 (diff)
downloadsrc-92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba.tar.gz
src-92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba.zip
fusefs: implement FUSE_COPY_FILE_RANGE.
This updates the FUSE protocol to 7.28, though most of the new features are optional and are not yet implemented. MFC after: 2 weeks Relnotes: yes Reviewed by: cem Differential Revision: https://reviews.freebsd.org/D27818
Diffstat (limited to 'tests')
-rw-r--r--tests/sys/fs/fusefs/Makefile1
-rw-r--r--tests/sys/fs/fusefs/copy_file_range.cc401
-rw-r--r--tests/sys/fs/fusefs/default_permissions.cc110
-rw-r--r--tests/sys/fs/fusefs/mockfs.cc20
-rw-r--r--tests/sys/fs/fusefs/mockfs.hh1
-rw-r--r--tests/sys/fs/fusefs/write.cc1
6 files changed, 531 insertions, 3 deletions
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
index 8d199a53c074..2c858ff42dd1 100644
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -13,6 +13,7 @@ GTESTS+= access
GTESTS+= allow_other
GTESTS+= bmap
GTESTS+= cache
+GTESTS+= copy_file_range
GTESTS+= create
GTESTS+= default_permissions
GTESTS+= default_permissions_privileged
diff --git a/tests/sys/fs/fusefs/copy_file_range.cc b/tests/sys/fs/fusefs/copy_file_range.cc
new file mode 100644
index 000000000000..bb8eecf8b862
--- /dev/null
+++ b/tests/sys/fs/fusefs/copy_file_range.cc
@@ -0,0 +1,401 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Alan Somers
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class CopyFileRange: public FuseTest {
+public:
+static sig_atomic_t s_sigxfsz;
+
+void SetUp() {
+ s_sigxfsz = 0;
+ FuseTest::SetUp();
+}
+
+void TearDown() {
+ struct sigaction sa;
+
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGXFSZ, &sa, NULL);
+
+ FuseTest::TearDown();
+}
+
+void expect_maybe_lseek(uint64_t ino)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(AtMost(1))
+ .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
+}
+
+void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(times)
+ .WillRepeatedly(Invoke(
+ ReturnImmediate([=](auto in __unused, auto& out) {
+ out.header.len = sizeof(out.header);
+ SET_OUT_HEADER_LEN(out, open);
+ out.body.open.fh = fh;
+ out.body.open.open_flags = flags;
+ })));
+}
+
+void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
+ uint64_t osize, const void *contents)
+{
+ 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.offset == offset &&
+ in.body.write.size == isize &&
+ 0 == bcmp(buf, contents, isize));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = osize;
+ })));
+}
+
+};
+
+sig_atomic_t CopyFileRange::s_sigxfsz = 0;
+
+void sigxfsz_handler(int __unused sig) {
+ CopyFileRange::s_sigxfsz = 1;
+}
+
+
+class CopyFileRange_7_27: public CopyFileRange {
+public:
+virtual void SetUp() {
+ m_kernel_minor_version = 27;
+ CopyFileRange::SetUp();
+}
+};
+
+TEST_F(CopyFileRange, eio)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = 3 << 17;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(EIO)));
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+ EXPECT_EQ(EIO, errno);
+}
+
+/*
+ * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
+ * fallback to a read/write based implementation.
+ */
+TEST_F(CopyFileRange, fallback)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize2 = 0;
+ off_t start1 = 0;
+ off_t start2 = 0;
+ const char *contents = "Hello, world!";
+ ssize_t len;
+ int fd1, fd2;
+
+ len = strlen(contents);
+
+ /*
+ * Ensure that we read to EOF, just so the buffer cache's read size is
+ * predictable.
+ */
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ expect_maybe_lseek(ino1);
+ expect_read(ino1, start1, len, len, contents, 0);
+ expect_write(ino2, start2, len, len, contents);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ ASSERT_GE(fd1, 0);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_GE(fd2, 0);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+/* fusefs should respect RLIMIT_FSIZE */
+TEST_F(CopyFileRange, rlimit_fsize)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ struct rlimit rl;
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = fsize2;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+
+ rl.rlim_cur = fsize2;
+ rl.rlim_max = 10 * fsize2;
+ ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
+ ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+ EXPECT_EQ(EFBIG, errno);
+ EXPECT_EQ(1, s_sigxfsz);
+}
+
+TEST_F(CopyFileRange, ok)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize1 = 1 << 20; /* 1 MiB */
+ off_t fsize2 = 1 << 19; /* 512 KiB */
+ off_t start1 = 1 << 18;
+ off_t start2 = 3 << 17;
+ ssize_t len = 65536;
+ int fd1, fd2;
+
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino1 &&
+ in.body.copy_file_range.fh_in == fh1 &&
+ (off_t)in.body.copy_file_range.off_in == start1 &&
+ in.body.copy_file_range.nodeid_out == ino2 &&
+ in.body.copy_file_range.fh_out == fh2 &&
+ (off_t)in.body.copy_file_range.off_out == start2 &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+/*
+ * copy_file_range can make copies within a single file, as long as the ranges
+ * don't overlap.
+ * */
+TEST_F(CopyFileRange, same_file)
+{
+ const char FULLPATH[] = "mountpoint/src.txt";
+ const char RELPATH[] = "src.txt";
+ const uint64_t ino = 4;
+ const uint64_t fh = 0xdeadbeefa7ebabe;
+ off_t fsize = 1 << 20; /* 1 MiB */
+ off_t off_in = 1 << 18;
+ off_t off_out = 3 << 17;
+ ssize_t len = 65536;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1, fh);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino &&
+ in.body.copy_file_range.fh_in == fh &&
+ (off_t)in.body.copy_file_range.off_in == off_in &&
+ in.body.copy_file_range.nodeid_out == ino &&
+ in.body.copy_file_range.fh_out == fh &&
+ (off_t)in.body.copy_file_range.off_out == off_out &&
+ in.body.copy_file_range.len == (size_t)len &&
+ in.body.copy_file_range.flags == 0);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+
+ fd = open(FULLPATH, O_RDWR);
+ ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
+}
+
+/* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
+TEST_F(CopyFileRange_7_27, fallback)
+{
+ const char FULLPATH1[] = "mountpoint/src.txt";
+ const char RELPATH1[] = "src.txt";
+ const char FULLPATH2[] = "mountpoint/dst.txt";
+ const char RELPATH2[] = "dst.txt";
+ const uint64_t ino1 = 42;
+ const uint64_t ino2 = 43;
+ const uint64_t fh1 = 0xdeadbeef1a7ebabe;
+ const uint64_t fh2 = 0xdeadc0de88c0ffee;
+ off_t fsize2 = 0;
+ off_t start1 = 0;
+ off_t start2 = 0;
+ const char *contents = "Hello, world!";
+ ssize_t len;
+ int fd1, fd2;
+
+ len = strlen(contents);
+
+ /*
+ * Ensure that we read to EOF, just so the buffer cache's read size is
+ * predictable.
+ */
+ expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
+ expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
+ expect_open(ino1, 0, 1, fh1);
+ expect_open(ino2, 0, 1, fh2);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+ expect_maybe_lseek(ino1);
+ expect_read(ino1, start1, len, len, contents, 0);
+ expect_write(ino2, start2, len, len, contents);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ ASSERT_GE(fd1, 0);
+ fd2 = open(FULLPATH2, O_WRONLY);
+ ASSERT_GE(fd2, 0);
+ ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
+}
+
+
diff --git a/tests/sys/fs/fusefs/default_permissions.cc b/tests/sys/fs/fusefs/default_permissions.cc
index 368c28bbcb3f..6401f926bb49 100644
--- a/tests/sys/fs/fusefs/default_permissions.cc
+++ b/tests/sys/fs/fusefs/default_permissions.cc
@@ -109,6 +109,25 @@ void expect_create(const char *relpath, uint64_t ino)
})));
}
+void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
+ uint64_t off_out, uint64_t len)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
+ in.header.nodeid == ino_in &&
+ in.body.copy_file_range.off_in == off_in &&
+ in.body.copy_file_range.nodeid_out == ino_out &&
+ in.body.copy_file_range.off_out == off_out &&
+ in.body.copy_file_range.len == len);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, write);
+ out.body.write.size = len;
+ })));
+}
+
void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
uid_t uid = 0, gid_t gid = 0)
{
@@ -141,6 +160,7 @@ void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
class Access: public DefaultPermissions {};
class Chown: public DefaultPermissions {};
class Chgrp: public DefaultPermissions {};
+class CopyFileRange: public DefaultPermissions {};
class Lookup: public DefaultPermissions {};
class Open: public DefaultPermissions {};
class Setattr: public DefaultPermissions {};
@@ -477,6 +497,94 @@ TEST_F(Chgrp, ok)
EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
}
+/* A write by a non-owner should clear a file's SGID bit */
+TEST_F(CopyFileRange, clear_guid)
+{
+ const char FULLPATH_IN[] = "mountpoint/in.txt";
+ const char RELPATH_IN[] = "in.txt";
+ const char FULLPATH_OUT[] = "mountpoint/out.txt";
+ const char RELPATH_OUT[] = "out.txt";
+ struct stat sb;
+ uint64_t ino_in = 42;
+ uint64_t ino_out = 43;
+ mode_t oldmode = 02777;
+ mode_t newmode = 0777;
+ off_t fsize = 16;
+ off_t off_in = 0;
+ off_t off_out = 8;
+ off_t len = 8;
+ int fd_in, fd_out;
+
+ expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
+ FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
+ UINT64_MAX, 0, 0);
+ expect_open(ino_in, 0, 1);
+ FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
+ 1, UINT64_MAX, 0, 0);
+ expect_open(ino_out, 0, 1);
+ expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
+ expect_chmod(ino_out, newmode, fsize);
+
+ fd_in = open(FULLPATH_IN, O_RDONLY);
+ ASSERT_LE(0, fd_in) << strerror(errno);
+ fd_out = open(FULLPATH_OUT, O_WRONLY);
+ ASSERT_LE(0, fd_out) << strerror(errno);
+ ASSERT_EQ(len,
+ copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
+ << strerror(errno);
+ ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
+ ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
+
+ leak(fd_in);
+ leak(fd_out);
+}
+
+/* A write by a non-owner should clear a file's SUID bit */
+TEST_F(CopyFileRange, clear_suid)
+{
+ const char FULLPATH_IN[] = "mountpoint/in.txt";
+ const char RELPATH_IN[] = "in.txt";
+ const char FULLPATH_OUT[] = "mountpoint/out.txt";
+ const char RELPATH_OUT[] = "out.txt";
+ struct stat sb;
+ uint64_t ino_in = 42;
+ uint64_t ino_out = 43;
+ mode_t oldmode = 04777;
+ mode_t newmode = 0777;
+ off_t fsize = 16;
+ off_t off_in = 0;
+ off_t off_out = 8;
+ off_t len = 8;
+ int fd_in, fd_out;
+
+ expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
+ FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
+ UINT64_MAX, 0, 0);
+ expect_open(ino_in, 0, 1);
+ FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
+ 1, UINT64_MAX, 0, 0);
+ expect_open(ino_out, 0, 1);
+ expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
+ expect_chmod(ino_out, newmode, fsize);
+
+ fd_in = open(FULLPATH_IN, O_RDONLY);
+ ASSERT_LE(0, fd_in) << strerror(errno);
+ fd_out = open(FULLPATH_OUT, O_WRONLY);
+ ASSERT_LE(0, fd_out) << strerror(errno);
+ ASSERT_EQ(len,
+ copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
+ << strerror(errno);
+ ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
+ ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
+ EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
+
+ leak(fd_in);
+ leak(fd_out);
+}
+
TEST_F(Create, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
@@ -1311,5 +1419,3 @@ TEST_F(Write, recursion_panic_while_clearing_suid)
ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
leak(fd);
}
-
-
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index 0124d8765eb1..7e4991fb7bb9 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -110,6 +110,7 @@ const char* opcode2opname(uint32_t opcode)
"READDIRPLUS",
"RENAME2",
"LSEEK",
+ "COPY_FILE_RANGE",
};
if (opcode >= nitems(table))
return ("Unknown (opcode > max)");
@@ -182,6 +183,20 @@ void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
printf(" block=%" PRIx64 " blocksize=%#x",
in.body.bmap.block, in.body.bmap.blocksize);
break;
+ case FUSE_COPY_FILE_RANGE:
+ printf(" off_in=%" PRIu64 " ino_out=%" PRIu64
+ " off_out=%" PRIu64 " size=%" PRIu64,
+ in.body.copy_file_range.off_in,
+ in.body.copy_file_range.nodeid_out,
+ in.body.copy_file_range.off_out,
+ in.body.copy_file_range.len);
+ if (verbosity > 1)
+ printf(" fh_in=%" PRIu64 " fh_out=%" PRIu64
+ " flags=%" PRIx64,
+ in.body.copy_file_range.fh_in,
+ in.body.copy_file_range.fh_out,
+ in.body.copy_file_range.flags);
+ break;
case FUSE_CREATE:
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
@@ -663,6 +678,11 @@ void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
EXPECT_EQ(inlen, fih + sizeof(in.body.lseek));
EXPECT_EQ((size_t)buflen, inlen);
break;
+ case FUSE_COPY_FILE_RANGE:
+ EXPECT_EQ(inlen, fih + sizeof(in.body.copy_file_range));
+ EXPECT_EQ(0ul, in.body.copy_file_range.flags);
+ EXPECT_EQ((size_t)buflen, inlen);
+ break;
case FUSE_NOTIFY_REPLY:
case FUSE_BATCH_FORGET:
case FUSE_FALLOCATE:
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
index 6fb15089bc23..a7a6a55922c7 100644
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -157,6 +157,7 @@ union fuse_payloads_in {
uint8_t bytes[
max_max_write + 0x1000 - sizeof(struct fuse_in_header)
];
+ fuse_copy_file_range_in copy_file_range;
fuse_create_in create;
fuse_flush_in flush;
fuse_fsync_in fsync;
diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc
index 7529b76847d7..f0f9685000fb 100644
--- a/tests/sys/fs/fusefs/write.cc
+++ b/tests/sys/fs/fusefs/write.cc
@@ -511,7 +511,6 @@ TEST_F(Write, eof_during_rmw)
ssize_t bufsize = strlen(CONTENTS) + 1;
off_t orig_fsize = 10;
off_t truncated_fsize = 5;
- off_t final_fsize = bufsize;
int fd;
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, orig_fsize, 1);