diff options
author | Alan Somers <asomers@gmail.com> | 2020-12-29 01:25:21 +0000 |
---|---|---|
committer | Alan Somers <asomers@FreeBSD.org> | 2021-01-01 17:18:23 +0000 |
commit | 92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba (patch) | |
tree | 4a5fc1b96281a02f3e9b9c0dc6c1de90fcf06d41 /tests | |
parent | ae39db74066a0ff1682c1c841be030099d9d4557 (diff) | |
download | src-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/Makefile | 1 | ||||
-rw-r--r-- | tests/sys/fs/fusefs/copy_file_range.cc | 401 | ||||
-rw-r--r-- | tests/sys/fs/fusefs/default_permissions.cc | 110 | ||||
-rw-r--r-- | tests/sys/fs/fusefs/mockfs.cc | 20 | ||||
-rw-r--r-- | tests/sys/fs/fusefs/mockfs.hh | 1 | ||||
-rw-r--r-- | tests/sys/fs/fusefs/write.cc | 1 |
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); |