192bbfe1fSAlan Somers /*- 292bbfe1fSAlan Somers * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 392bbfe1fSAlan Somers * 492bbfe1fSAlan Somers * Copyright (c) 2020 Alan Somers 592bbfe1fSAlan Somers * 692bbfe1fSAlan Somers * Redistribution and use in source and binary forms, with or without 792bbfe1fSAlan Somers * modification, are permitted provided that the following conditions 892bbfe1fSAlan Somers * are met: 992bbfe1fSAlan Somers * 1. Redistributions of source code must retain the above copyright 1092bbfe1fSAlan Somers * notice, this list of conditions and the following disclaimer. 1192bbfe1fSAlan Somers * 2. Redistributions in binary form must reproduce the above copyright 1292bbfe1fSAlan Somers * notice, this list of conditions and the following disclaimer in the 1392bbfe1fSAlan Somers * documentation and/or other materials provided with the distribution. 1492bbfe1fSAlan Somers * 1592bbfe1fSAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1692bbfe1fSAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1792bbfe1fSAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1892bbfe1fSAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1992bbfe1fSAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2092bbfe1fSAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2192bbfe1fSAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2292bbfe1fSAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2392bbfe1fSAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2492bbfe1fSAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2592bbfe1fSAlan Somers * SUCH DAMAGE. 2692bbfe1fSAlan Somers * 2792bbfe1fSAlan Somers * $FreeBSD$ 2892bbfe1fSAlan Somers */ 2992bbfe1fSAlan Somers 3092bbfe1fSAlan Somers extern "C" { 3192bbfe1fSAlan Somers #include <sys/param.h> 3292bbfe1fSAlan Somers #include <sys/time.h> 3392bbfe1fSAlan Somers #include <sys/resource.h> 3492bbfe1fSAlan Somers 3592bbfe1fSAlan Somers #include <fcntl.h> 3692bbfe1fSAlan Somers #include <signal.h> 3792bbfe1fSAlan Somers #include <unistd.h> 3892bbfe1fSAlan Somers } 3992bbfe1fSAlan Somers 4092bbfe1fSAlan Somers #include "mockfs.hh" 4192bbfe1fSAlan Somers #include "utils.hh" 4292bbfe1fSAlan Somers 4392bbfe1fSAlan Somers using namespace testing; 4492bbfe1fSAlan Somers 4592bbfe1fSAlan Somers class CopyFileRange: public FuseTest { 4692bbfe1fSAlan Somers public: 4792bbfe1fSAlan Somers static sig_atomic_t s_sigxfsz; 4892bbfe1fSAlan Somers 4992bbfe1fSAlan Somers void SetUp() { 5092bbfe1fSAlan Somers s_sigxfsz = 0; 5192bbfe1fSAlan Somers FuseTest::SetUp(); 5292bbfe1fSAlan Somers } 5392bbfe1fSAlan Somers 5492bbfe1fSAlan Somers void TearDown() { 5592bbfe1fSAlan Somers struct sigaction sa; 5692bbfe1fSAlan Somers 5792bbfe1fSAlan Somers bzero(&sa, sizeof(sa)); 5892bbfe1fSAlan Somers sa.sa_handler = SIG_DFL; 5992bbfe1fSAlan Somers sigaction(SIGXFSZ, &sa, NULL); 6092bbfe1fSAlan Somers 6192bbfe1fSAlan Somers FuseTest::TearDown(); 6292bbfe1fSAlan Somers } 6392bbfe1fSAlan Somers 6492bbfe1fSAlan Somers void expect_maybe_lseek(uint64_t ino) 6592bbfe1fSAlan Somers { 6692bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 6792bbfe1fSAlan Somers ResultOf([=](auto in) { 6892bbfe1fSAlan Somers return (in.header.opcode == FUSE_LSEEK && 6992bbfe1fSAlan Somers in.header.nodeid == ino); 7092bbfe1fSAlan Somers }, Eq(true)), 7192bbfe1fSAlan Somers _) 7292bbfe1fSAlan Somers ).Times(AtMost(1)) 7392bbfe1fSAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); 7492bbfe1fSAlan Somers } 7592bbfe1fSAlan Somers 7692bbfe1fSAlan Somers void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh) 7792bbfe1fSAlan Somers { 7892bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 7992bbfe1fSAlan Somers ResultOf([=](auto in) { 8092bbfe1fSAlan Somers return (in.header.opcode == FUSE_OPEN && 8192bbfe1fSAlan Somers in.header.nodeid == ino); 8292bbfe1fSAlan Somers }, Eq(true)), 8392bbfe1fSAlan Somers _) 8492bbfe1fSAlan Somers ).Times(times) 8592bbfe1fSAlan Somers .WillRepeatedly(Invoke( 8692bbfe1fSAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 8792bbfe1fSAlan Somers out.header.len = sizeof(out.header); 8892bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, open); 8992bbfe1fSAlan Somers out.body.open.fh = fh; 9092bbfe1fSAlan Somers out.body.open.open_flags = flags; 9192bbfe1fSAlan Somers }))); 9292bbfe1fSAlan Somers } 9392bbfe1fSAlan Somers 9492bbfe1fSAlan Somers void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 9592bbfe1fSAlan Somers uint64_t osize, const void *contents) 9692bbfe1fSAlan Somers { 9792bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 9892bbfe1fSAlan Somers ResultOf([=](auto in) { 9992bbfe1fSAlan Somers const char *buf = (const char*)in.body.bytes + 10092bbfe1fSAlan Somers sizeof(struct fuse_write_in); 10192bbfe1fSAlan Somers 10292bbfe1fSAlan Somers return (in.header.opcode == FUSE_WRITE && 10392bbfe1fSAlan Somers in.header.nodeid == ino && 10492bbfe1fSAlan Somers in.body.write.offset == offset && 10592bbfe1fSAlan Somers in.body.write.size == isize && 10692bbfe1fSAlan Somers 0 == bcmp(buf, contents, isize)); 10792bbfe1fSAlan Somers }, Eq(true)), 10892bbfe1fSAlan Somers _) 10992bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 11092bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 11192bbfe1fSAlan Somers out.body.write.size = osize; 11292bbfe1fSAlan Somers }))); 11392bbfe1fSAlan Somers } 11492bbfe1fSAlan Somers 11592bbfe1fSAlan Somers }; 11692bbfe1fSAlan Somers 11792bbfe1fSAlan Somers sig_atomic_t CopyFileRange::s_sigxfsz = 0; 11892bbfe1fSAlan Somers 11992bbfe1fSAlan Somers void sigxfsz_handler(int __unused sig) { 12092bbfe1fSAlan Somers CopyFileRange::s_sigxfsz = 1; 12192bbfe1fSAlan Somers } 12292bbfe1fSAlan Somers 12392bbfe1fSAlan Somers 12492bbfe1fSAlan Somers class CopyFileRange_7_27: public CopyFileRange { 12592bbfe1fSAlan Somers public: 12692bbfe1fSAlan Somers virtual void SetUp() { 12792bbfe1fSAlan Somers m_kernel_minor_version = 27; 12892bbfe1fSAlan Somers CopyFileRange::SetUp(); 12992bbfe1fSAlan Somers } 13092bbfe1fSAlan Somers }; 13192bbfe1fSAlan Somers 13292bbfe1fSAlan Somers TEST_F(CopyFileRange, eio) 13392bbfe1fSAlan Somers { 13492bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 13592bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 13692bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 13792bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 13892bbfe1fSAlan Somers const uint64_t ino1 = 42; 13992bbfe1fSAlan Somers const uint64_t ino2 = 43; 14092bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 14192bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 14292bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 14392bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 14492bbfe1fSAlan Somers off_t start1 = 1 << 18; 14592bbfe1fSAlan Somers off_t start2 = 3 << 17; 14692bbfe1fSAlan Somers ssize_t len = 65536; 14792bbfe1fSAlan Somers int fd1, fd2; 14892bbfe1fSAlan Somers 14992bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 15092bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 15192bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 15292bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 15392bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 15492bbfe1fSAlan Somers ResultOf([=](auto in) { 15592bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 15692bbfe1fSAlan Somers in.header.nodeid == ino1 && 15792bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 15892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 15992bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 16092bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 16192bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 16292bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 16392bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 16492bbfe1fSAlan Somers }, Eq(true)), 16592bbfe1fSAlan Somers _) 16692bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(EIO))); 16792bbfe1fSAlan Somers 16892bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 16992bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 17092bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 17192bbfe1fSAlan Somers EXPECT_EQ(EIO, errno); 17292bbfe1fSAlan Somers } 17392bbfe1fSAlan Somers 17492bbfe1fSAlan Somers /* 17592bbfe1fSAlan Somers * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should 17692bbfe1fSAlan Somers * fallback to a read/write based implementation. 17792bbfe1fSAlan Somers */ 17892bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback) 17992bbfe1fSAlan Somers { 18092bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 18192bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 18292bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 18392bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 18492bbfe1fSAlan Somers const uint64_t ino1 = 42; 18592bbfe1fSAlan Somers const uint64_t ino2 = 43; 18692bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 18792bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 18892bbfe1fSAlan Somers off_t fsize2 = 0; 18992bbfe1fSAlan Somers off_t start1 = 0; 19092bbfe1fSAlan Somers off_t start2 = 0; 19192bbfe1fSAlan Somers const char *contents = "Hello, world!"; 19292bbfe1fSAlan Somers ssize_t len; 19392bbfe1fSAlan Somers int fd1, fd2; 19492bbfe1fSAlan Somers 19592bbfe1fSAlan Somers len = strlen(contents); 19692bbfe1fSAlan Somers 19792bbfe1fSAlan Somers /* 19892bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 19992bbfe1fSAlan Somers * predictable. 20092bbfe1fSAlan Somers */ 20192bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 20292bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 20392bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 20492bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 20592bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 20692bbfe1fSAlan Somers ResultOf([=](auto in) { 20792bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 20892bbfe1fSAlan Somers in.header.nodeid == ino1 && 20992bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 21092bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 21192bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 21292bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 21392bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 21492bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 21592bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 21692bbfe1fSAlan Somers }, Eq(true)), 21792bbfe1fSAlan Somers _) 21892bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 21992bbfe1fSAlan Somers expect_maybe_lseek(ino1); 22092bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 22192bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 22292bbfe1fSAlan Somers 22392bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 22492bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 22592bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 22692bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 22792bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 22892bbfe1fSAlan Somers } 22992bbfe1fSAlan Somers 23092bbfe1fSAlan Somers /* fusefs should respect RLIMIT_FSIZE */ 23192bbfe1fSAlan Somers TEST_F(CopyFileRange, rlimit_fsize) 23292bbfe1fSAlan Somers { 23392bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 23492bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 23592bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 23692bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 23792bbfe1fSAlan Somers struct rlimit rl; 23892bbfe1fSAlan Somers const uint64_t ino1 = 42; 23992bbfe1fSAlan Somers const uint64_t ino2 = 43; 24092bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 24192bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 24292bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 24392bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 24492bbfe1fSAlan Somers off_t start1 = 1 << 18; 24592bbfe1fSAlan Somers off_t start2 = fsize2; 24692bbfe1fSAlan Somers ssize_t len = 65536; 24792bbfe1fSAlan Somers int fd1, fd2; 24892bbfe1fSAlan Somers 24992bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 25092bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 25192bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 25292bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 25392bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 25492bbfe1fSAlan Somers ResultOf([=](auto in) { 25592bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 25692bbfe1fSAlan Somers }, Eq(true)), 25792bbfe1fSAlan Somers _) 25892bbfe1fSAlan Somers ).Times(0); 25992bbfe1fSAlan Somers 26092bbfe1fSAlan Somers rl.rlim_cur = fsize2; 26192bbfe1fSAlan Somers rl.rlim_max = 10 * fsize2; 26292bbfe1fSAlan Somers ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 26392bbfe1fSAlan Somers ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 26492bbfe1fSAlan Somers 26592bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 26692bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 26792bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 26892bbfe1fSAlan Somers EXPECT_EQ(EFBIG, errno); 26992bbfe1fSAlan Somers EXPECT_EQ(1, s_sigxfsz); 27092bbfe1fSAlan Somers } 27192bbfe1fSAlan Somers 27292bbfe1fSAlan Somers TEST_F(CopyFileRange, ok) 27392bbfe1fSAlan Somers { 27492bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 27592bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 27692bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 27792bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 27892bbfe1fSAlan Somers const uint64_t ino1 = 42; 27992bbfe1fSAlan Somers const uint64_t ino2 = 43; 28092bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 28192bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 28292bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 28392bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 28492bbfe1fSAlan Somers off_t start1 = 1 << 18; 28592bbfe1fSAlan Somers off_t start2 = 3 << 17; 28692bbfe1fSAlan Somers ssize_t len = 65536; 28792bbfe1fSAlan Somers int fd1, fd2; 28892bbfe1fSAlan Somers 28992bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 29092bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 29192bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 29292bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 29392bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 29492bbfe1fSAlan Somers ResultOf([=](auto in) { 29592bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 29692bbfe1fSAlan Somers in.header.nodeid == ino1 && 29792bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 29892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 29992bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 30092bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 30192bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 30292bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 30392bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 30492bbfe1fSAlan Somers }, Eq(true)), 30592bbfe1fSAlan Somers _) 30692bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 30792bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 30892bbfe1fSAlan Somers out.body.write.size = len; 30992bbfe1fSAlan Somers }))); 31092bbfe1fSAlan Somers 31192bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 31292bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 31392bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 31492bbfe1fSAlan Somers } 31592bbfe1fSAlan Somers 31692bbfe1fSAlan Somers /* 31792bbfe1fSAlan Somers * copy_file_range can make copies within a single file, as long as the ranges 31892bbfe1fSAlan Somers * don't overlap. 31992bbfe1fSAlan Somers * */ 32092bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file) 32192bbfe1fSAlan Somers { 32292bbfe1fSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 32392bbfe1fSAlan Somers const char RELPATH[] = "src.txt"; 32492bbfe1fSAlan Somers const uint64_t ino = 4; 32592bbfe1fSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 32692bbfe1fSAlan Somers off_t fsize = 1 << 20; /* 1 MiB */ 32792bbfe1fSAlan Somers off_t off_in = 1 << 18; 32892bbfe1fSAlan Somers off_t off_out = 3 << 17; 32992bbfe1fSAlan Somers ssize_t len = 65536; 33092bbfe1fSAlan Somers int fd; 33192bbfe1fSAlan Somers 33292bbfe1fSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 33392bbfe1fSAlan Somers expect_open(ino, 0, 1, fh); 33492bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 33592bbfe1fSAlan Somers ResultOf([=](auto in) { 33692bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 33792bbfe1fSAlan Somers in.header.nodeid == ino && 33892bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh && 33992bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 34092bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino && 34192bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh && 34292bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 34392bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 34492bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 34592bbfe1fSAlan Somers }, Eq(true)), 34692bbfe1fSAlan Somers _) 34792bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 34892bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 34992bbfe1fSAlan Somers out.body.write.size = len; 35092bbfe1fSAlan Somers }))); 35192bbfe1fSAlan Somers 35292bbfe1fSAlan Somers fd = open(FULLPATH, O_RDWR); 35392bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 35492bbfe1fSAlan Somers } 35592bbfe1fSAlan Somers 356*65d70b3bSAlan Somers /* 357*65d70b3bSAlan Somers * copy_file_range can extend the size of a file 358*65d70b3bSAlan Somers * */ 359*65d70b3bSAlan Somers TEST_F(CopyFileRange, extend) 360*65d70b3bSAlan Somers { 361*65d70b3bSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 362*65d70b3bSAlan Somers const char RELPATH[] = "src.txt"; 363*65d70b3bSAlan Somers struct stat sb; 364*65d70b3bSAlan Somers const uint64_t ino = 4; 365*65d70b3bSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 366*65d70b3bSAlan Somers off_t fsize = 65536; 367*65d70b3bSAlan Somers off_t off_in = 0; 368*65d70b3bSAlan Somers off_t off_out = 65536; 369*65d70b3bSAlan Somers ssize_t len = 65536; 370*65d70b3bSAlan Somers int fd; 371*65d70b3bSAlan Somers 372*65d70b3bSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 373*65d70b3bSAlan Somers expect_open(ino, 0, 1, fh); 374*65d70b3bSAlan Somers EXPECT_CALL(*m_mock, process( 375*65d70b3bSAlan Somers ResultOf([=](auto in) { 376*65d70b3bSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 377*65d70b3bSAlan Somers in.header.nodeid == ino && 378*65d70b3bSAlan Somers in.body.copy_file_range.fh_in == fh && 379*65d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 380*65d70b3bSAlan Somers in.body.copy_file_range.nodeid_out == ino && 381*65d70b3bSAlan Somers in.body.copy_file_range.fh_out == fh && 382*65d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 383*65d70b3bSAlan Somers in.body.copy_file_range.len == (size_t)len && 384*65d70b3bSAlan Somers in.body.copy_file_range.flags == 0); 385*65d70b3bSAlan Somers }, Eq(true)), 386*65d70b3bSAlan Somers _) 387*65d70b3bSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 388*65d70b3bSAlan Somers SET_OUT_HEADER_LEN(out, write); 389*65d70b3bSAlan Somers out.body.write.size = len; 390*65d70b3bSAlan Somers }))); 391*65d70b3bSAlan Somers 392*65d70b3bSAlan Somers fd = open(FULLPATH, O_RDWR); 393*65d70b3bSAlan Somers ASSERT_GE(fd, 0); 394*65d70b3bSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 395*65d70b3bSAlan Somers 396*65d70b3bSAlan Somers /* Check that cached attributes were updated appropriately */ 397*65d70b3bSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 398*65d70b3bSAlan Somers EXPECT_EQ(fsize + len, sb.st_size); 399*65d70b3bSAlan Somers 400*65d70b3bSAlan Somers leak(fd); 401*65d70b3bSAlan Somers } 402*65d70b3bSAlan Somers 40392bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */ 40492bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback) 40592bbfe1fSAlan Somers { 40692bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 40792bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 40892bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 40992bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 41092bbfe1fSAlan Somers const uint64_t ino1 = 42; 41192bbfe1fSAlan Somers const uint64_t ino2 = 43; 41292bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 41392bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 41492bbfe1fSAlan Somers off_t fsize2 = 0; 41592bbfe1fSAlan Somers off_t start1 = 0; 41692bbfe1fSAlan Somers off_t start2 = 0; 41792bbfe1fSAlan Somers const char *contents = "Hello, world!"; 41892bbfe1fSAlan Somers ssize_t len; 41992bbfe1fSAlan Somers int fd1, fd2; 42092bbfe1fSAlan Somers 42192bbfe1fSAlan Somers len = strlen(contents); 42292bbfe1fSAlan Somers 42392bbfe1fSAlan Somers /* 42492bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 42592bbfe1fSAlan Somers * predictable. 42692bbfe1fSAlan Somers */ 42792bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 42892bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 42992bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 43092bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 43192bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 43292bbfe1fSAlan Somers ResultOf([=](auto in) { 43392bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 43492bbfe1fSAlan Somers }, Eq(true)), 43592bbfe1fSAlan Somers _) 43692bbfe1fSAlan Somers ).Times(0); 43792bbfe1fSAlan Somers expect_maybe_lseek(ino1); 43892bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 43992bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 44092bbfe1fSAlan Somers 44192bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 44292bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 44392bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 44492bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 44592bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 44692bbfe1fSAlan Somers } 44792bbfe1fSAlan Somers 44892bbfe1fSAlan Somers 449