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 4892bbfe1fSAlan Somers void expect_maybe_lseek(uint64_t ino) 4992bbfe1fSAlan Somers { 5092bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 5192bbfe1fSAlan Somers ResultOf([=](auto in) { 5292bbfe1fSAlan Somers return (in.header.opcode == FUSE_LSEEK && 5392bbfe1fSAlan Somers in.header.nodeid == ino); 5492bbfe1fSAlan Somers }, Eq(true)), 5592bbfe1fSAlan Somers _) 5692bbfe1fSAlan Somers ).Times(AtMost(1)) 5792bbfe1fSAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); 5892bbfe1fSAlan Somers } 5992bbfe1fSAlan Somers 6092bbfe1fSAlan Somers void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh) 6192bbfe1fSAlan Somers { 6292bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 6392bbfe1fSAlan Somers ResultOf([=](auto in) { 6492bbfe1fSAlan Somers return (in.header.opcode == FUSE_OPEN && 6592bbfe1fSAlan Somers in.header.nodeid == ino); 6692bbfe1fSAlan Somers }, Eq(true)), 6792bbfe1fSAlan Somers _) 6892bbfe1fSAlan Somers ).Times(times) 6992bbfe1fSAlan Somers .WillRepeatedly(Invoke( 7092bbfe1fSAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 7192bbfe1fSAlan Somers out.header.len = sizeof(out.header); 7292bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, open); 7392bbfe1fSAlan Somers out.body.open.fh = fh; 7492bbfe1fSAlan Somers out.body.open.open_flags = flags; 7592bbfe1fSAlan Somers }))); 7692bbfe1fSAlan Somers } 7792bbfe1fSAlan Somers 7892bbfe1fSAlan Somers void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 7992bbfe1fSAlan Somers uint64_t osize, const void *contents) 8092bbfe1fSAlan Somers { 8192bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 8292bbfe1fSAlan Somers ResultOf([=](auto in) { 8392bbfe1fSAlan Somers const char *buf = (const char*)in.body.bytes + 8492bbfe1fSAlan Somers sizeof(struct fuse_write_in); 8592bbfe1fSAlan Somers 8692bbfe1fSAlan Somers return (in.header.opcode == FUSE_WRITE && 8792bbfe1fSAlan Somers in.header.nodeid == ino && 8892bbfe1fSAlan Somers in.body.write.offset == offset && 8992bbfe1fSAlan Somers in.body.write.size == isize && 9092bbfe1fSAlan Somers 0 == bcmp(buf, contents, isize)); 9192bbfe1fSAlan Somers }, Eq(true)), 9292bbfe1fSAlan Somers _) 9392bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 9492bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 9592bbfe1fSAlan Somers out.body.write.size = osize; 9692bbfe1fSAlan Somers }))); 9792bbfe1fSAlan Somers } 9892bbfe1fSAlan Somers 9992bbfe1fSAlan Somers }; 10092bbfe1fSAlan Somers 10192bbfe1fSAlan Somers 10292bbfe1fSAlan Somers class CopyFileRange_7_27: public CopyFileRange { 10392bbfe1fSAlan Somers public: 10492bbfe1fSAlan Somers virtual void SetUp() { 10592bbfe1fSAlan Somers m_kernel_minor_version = 27; 10692bbfe1fSAlan Somers CopyFileRange::SetUp(); 10792bbfe1fSAlan Somers } 10892bbfe1fSAlan Somers }; 10992bbfe1fSAlan Somers 1105169832cSAlan Somers class CopyFileRangeNoAtime: public CopyFileRange { 1115169832cSAlan Somers public: 1125169832cSAlan Somers virtual void SetUp() { 1135169832cSAlan Somers m_noatime = true; 1145169832cSAlan Somers CopyFileRange::SetUp(); 1155169832cSAlan Somers } 1165169832cSAlan Somers }; 1175169832cSAlan Somers 118*52360ca3SAlan Somers class CopyFileRangeRlimitFsize: public CopyFileRange { 119*52360ca3SAlan Somers public: 120*52360ca3SAlan Somers static sig_atomic_t s_sigxfsz; 121*52360ca3SAlan Somers struct rlimit m_initial_limit; 122*52360ca3SAlan Somers 123*52360ca3SAlan Somers virtual void SetUp() { 124*52360ca3SAlan Somers s_sigxfsz = 0; 125*52360ca3SAlan Somers getrlimit(RLIMIT_FSIZE, &m_initial_limit); 126*52360ca3SAlan Somers CopyFileRange::SetUp(); 127*52360ca3SAlan Somers } 128*52360ca3SAlan Somers 129*52360ca3SAlan Somers void TearDown() { 130*52360ca3SAlan Somers struct sigaction sa; 131*52360ca3SAlan Somers 132*52360ca3SAlan Somers setrlimit(RLIMIT_FSIZE, &m_initial_limit); 133*52360ca3SAlan Somers 134*52360ca3SAlan Somers bzero(&sa, sizeof(sa)); 135*52360ca3SAlan Somers sa.sa_handler = SIG_DFL; 136*52360ca3SAlan Somers sigaction(SIGXFSZ, &sa, NULL); 137*52360ca3SAlan Somers 138*52360ca3SAlan Somers FuseTest::TearDown(); 139*52360ca3SAlan Somers } 140*52360ca3SAlan Somers 141*52360ca3SAlan Somers }; 142*52360ca3SAlan Somers 143*52360ca3SAlan Somers sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0; 144*52360ca3SAlan Somers 145*52360ca3SAlan Somers void sigxfsz_handler(int __unused sig) { 146*52360ca3SAlan Somers CopyFileRangeRlimitFsize::s_sigxfsz = 1; 147*52360ca3SAlan Somers } 148*52360ca3SAlan Somers 14992bbfe1fSAlan Somers TEST_F(CopyFileRange, eio) 15092bbfe1fSAlan Somers { 15192bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 15292bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 15392bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 15492bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 15592bbfe1fSAlan Somers const uint64_t ino1 = 42; 15692bbfe1fSAlan Somers const uint64_t ino2 = 43; 15792bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 15892bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 15992bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 16092bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 16192bbfe1fSAlan Somers off_t start1 = 1 << 18; 16292bbfe1fSAlan Somers off_t start2 = 3 << 17; 16392bbfe1fSAlan Somers ssize_t len = 65536; 16492bbfe1fSAlan Somers int fd1, fd2; 16592bbfe1fSAlan Somers 16692bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 16792bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 16892bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 16992bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 17092bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 17192bbfe1fSAlan Somers ResultOf([=](auto in) { 17292bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 17392bbfe1fSAlan Somers in.header.nodeid == ino1 && 17492bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 17592bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 17692bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 17792bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 17892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 17992bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 18092bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 18192bbfe1fSAlan Somers }, Eq(true)), 18292bbfe1fSAlan Somers _) 18392bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(EIO))); 18492bbfe1fSAlan Somers 18592bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 18692bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 18792bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 18892bbfe1fSAlan Somers EXPECT_EQ(EIO, errno); 18992bbfe1fSAlan Somers } 19092bbfe1fSAlan Somers 19192bbfe1fSAlan Somers /* 19241ae9f9eSAlan Somers * copy_file_range should evict cached data for the modified region of the 19341ae9f9eSAlan Somers * destination file. 19441ae9f9eSAlan Somers */ 19541ae9f9eSAlan Somers TEST_F(CopyFileRange, evicts_cache) 19641ae9f9eSAlan Somers { 19741ae9f9eSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 19841ae9f9eSAlan Somers const char RELPATH1[] = "src.txt"; 19941ae9f9eSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 20041ae9f9eSAlan Somers const char RELPATH2[] = "dst.txt"; 20141ae9f9eSAlan Somers void *buf0, *buf1, *buf; 20241ae9f9eSAlan Somers const uint64_t ino1 = 42; 20341ae9f9eSAlan Somers const uint64_t ino2 = 43; 20441ae9f9eSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 20541ae9f9eSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 20641ae9f9eSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 20741ae9f9eSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 20841ae9f9eSAlan Somers off_t start1 = 1 << 18; 20941ae9f9eSAlan Somers off_t start2 = 3 << 17; 21041ae9f9eSAlan Somers ssize_t len = m_maxbcachebuf; 21141ae9f9eSAlan Somers int fd1, fd2; 21241ae9f9eSAlan Somers 21341ae9f9eSAlan Somers buf0 = malloc(m_maxbcachebuf); 21441ae9f9eSAlan Somers memset(buf0, 42, m_maxbcachebuf); 21541ae9f9eSAlan Somers 21641ae9f9eSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 21741ae9f9eSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 21841ae9f9eSAlan Somers expect_open(ino1, 0, 1, fh1); 21941ae9f9eSAlan Somers expect_open(ino2, 0, 1, fh2); 22041ae9f9eSAlan Somers expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1, 22141ae9f9eSAlan Somers fh2); 22241ae9f9eSAlan Somers EXPECT_CALL(*m_mock, process( 22341ae9f9eSAlan Somers ResultOf([=](auto in) { 22441ae9f9eSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 22541ae9f9eSAlan Somers in.header.nodeid == ino1 && 22641ae9f9eSAlan Somers in.body.copy_file_range.fh_in == fh1 && 22741ae9f9eSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 22841ae9f9eSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 22941ae9f9eSAlan Somers in.body.copy_file_range.fh_out == fh2 && 23041ae9f9eSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 23141ae9f9eSAlan Somers in.body.copy_file_range.len == (size_t)len && 23241ae9f9eSAlan Somers in.body.copy_file_range.flags == 0); 23341ae9f9eSAlan Somers }, Eq(true)), 23441ae9f9eSAlan Somers _) 23541ae9f9eSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 23641ae9f9eSAlan Somers SET_OUT_HEADER_LEN(out, write); 23741ae9f9eSAlan Somers out.body.write.size = len; 23841ae9f9eSAlan Somers }))); 23941ae9f9eSAlan Somers 24041ae9f9eSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 24141ae9f9eSAlan Somers fd2 = open(FULLPATH2, O_RDWR); 24241ae9f9eSAlan Somers 24341ae9f9eSAlan Somers // Prime cache 24441ae9f9eSAlan Somers buf = malloc(m_maxbcachebuf); 24541ae9f9eSAlan Somers ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 24641ae9f9eSAlan Somers << strerror(errno); 24741ae9f9eSAlan Somers EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf)); 24841ae9f9eSAlan Somers 24941ae9f9eSAlan Somers // Tell the FUSE server overwrite the region we just read 25041ae9f9eSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 25141ae9f9eSAlan Somers 25241ae9f9eSAlan Somers // Read again. This should bypass the cache and read direct from server 25341ae9f9eSAlan Somers buf1 = malloc(m_maxbcachebuf); 25441ae9f9eSAlan Somers memset(buf1, 69, m_maxbcachebuf); 25541ae9f9eSAlan Somers start2 -= len; 25641ae9f9eSAlan Somers expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1, 25741ae9f9eSAlan Somers fh2); 25841ae9f9eSAlan Somers ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 25941ae9f9eSAlan Somers << strerror(errno); 26041ae9f9eSAlan Somers EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf)); 26141ae9f9eSAlan Somers 26241ae9f9eSAlan Somers free(buf1); 26341ae9f9eSAlan Somers free(buf0); 26441ae9f9eSAlan Somers free(buf); 26541ae9f9eSAlan Somers leak(fd1); 26641ae9f9eSAlan Somers leak(fd2); 26741ae9f9eSAlan Somers } 26841ae9f9eSAlan Somers 26941ae9f9eSAlan Somers /* 27092bbfe1fSAlan Somers * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should 27192bbfe1fSAlan Somers * fallback to a read/write based implementation. 27292bbfe1fSAlan Somers */ 27392bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback) 27492bbfe1fSAlan Somers { 27592bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 27692bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 27792bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 27892bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 27992bbfe1fSAlan Somers const uint64_t ino1 = 42; 28092bbfe1fSAlan Somers const uint64_t ino2 = 43; 28192bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 28292bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 28392bbfe1fSAlan Somers off_t fsize2 = 0; 28492bbfe1fSAlan Somers off_t start1 = 0; 28592bbfe1fSAlan Somers off_t start2 = 0; 28692bbfe1fSAlan Somers const char *contents = "Hello, world!"; 28792bbfe1fSAlan Somers ssize_t len; 28892bbfe1fSAlan Somers int fd1, fd2; 28992bbfe1fSAlan Somers 29092bbfe1fSAlan Somers len = strlen(contents); 29192bbfe1fSAlan Somers 29292bbfe1fSAlan Somers /* 29392bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 29492bbfe1fSAlan Somers * predictable. 29592bbfe1fSAlan Somers */ 29692bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 29792bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 29892bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 29992bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 30092bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 30192bbfe1fSAlan Somers ResultOf([=](auto in) { 30292bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 30392bbfe1fSAlan Somers in.header.nodeid == ino1 && 30492bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 30592bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 30692bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 30792bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 30892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 30992bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 31092bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 31192bbfe1fSAlan Somers }, Eq(true)), 31292bbfe1fSAlan Somers _) 31392bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 31492bbfe1fSAlan Somers expect_maybe_lseek(ino1); 31592bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 31692bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 31792bbfe1fSAlan Somers 31892bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 31992bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 32092bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 32192bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 32292bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 32392bbfe1fSAlan Somers } 32492bbfe1fSAlan Somers 325*52360ca3SAlan Somers /* 326*52360ca3SAlan Somers * copy_file_range should send SIGXFSZ and return EFBIG when the operation 327*52360ca3SAlan Somers * would exceed the limit imposed by RLIMIT_FSIZE. 328*52360ca3SAlan Somers */ 329*52360ca3SAlan Somers TEST_F(CopyFileRangeRlimitFsize, signal) 33092bbfe1fSAlan Somers { 33192bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 33292bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 33392bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 33492bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 33592bbfe1fSAlan Somers struct rlimit rl; 33692bbfe1fSAlan Somers const uint64_t ino1 = 42; 33792bbfe1fSAlan Somers const uint64_t ino2 = 43; 33892bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 33992bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 34092bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 34192bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 34292bbfe1fSAlan Somers off_t start1 = 1 << 18; 34392bbfe1fSAlan Somers off_t start2 = fsize2; 34492bbfe1fSAlan Somers ssize_t len = 65536; 34592bbfe1fSAlan Somers int fd1, fd2; 34692bbfe1fSAlan Somers 34792bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 34892bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 34992bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 35092bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 35192bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 35292bbfe1fSAlan Somers ResultOf([=](auto in) { 35392bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 35492bbfe1fSAlan Somers }, Eq(true)), 35592bbfe1fSAlan Somers _) 35692bbfe1fSAlan Somers ).Times(0); 35792bbfe1fSAlan Somers 35892bbfe1fSAlan Somers rl.rlim_cur = fsize2; 359*52360ca3SAlan Somers rl.rlim_max = m_initial_limit.rlim_max; 36092bbfe1fSAlan Somers ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 36192bbfe1fSAlan Somers ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 36292bbfe1fSAlan Somers 36392bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 36492bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 36592bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 36692bbfe1fSAlan Somers EXPECT_EQ(EFBIG, errno); 36792bbfe1fSAlan Somers EXPECT_EQ(1, s_sigxfsz); 36892bbfe1fSAlan Somers } 36992bbfe1fSAlan Somers 370*52360ca3SAlan Somers /* 371*52360ca3SAlan Somers * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not 372*52360ca3SAlan Somers * aborted. 373*52360ca3SAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611 374*52360ca3SAlan Somers */ 375*52360ca3SAlan Somers TEST_F(CopyFileRangeRlimitFsize, truncate) 376*52360ca3SAlan Somers { 377*52360ca3SAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 378*52360ca3SAlan Somers const char RELPATH1[] = "src.txt"; 379*52360ca3SAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 380*52360ca3SAlan Somers const char RELPATH2[] = "dst.txt"; 381*52360ca3SAlan Somers struct rlimit rl; 382*52360ca3SAlan Somers const uint64_t ino1 = 42; 383*52360ca3SAlan Somers const uint64_t ino2 = 43; 384*52360ca3SAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 385*52360ca3SAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 386*52360ca3SAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 387*52360ca3SAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 388*52360ca3SAlan Somers off_t start1 = 1 << 18; 389*52360ca3SAlan Somers off_t start2 = fsize2; 390*52360ca3SAlan Somers ssize_t len = 65536; 391*52360ca3SAlan Somers off_t limit = start2 + len / 2; 392*52360ca3SAlan Somers int fd1, fd2; 393*52360ca3SAlan Somers 394*52360ca3SAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 395*52360ca3SAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 396*52360ca3SAlan Somers expect_open(ino1, 0, 1, fh1); 397*52360ca3SAlan Somers expect_open(ino2, 0, 1, fh2); 398*52360ca3SAlan Somers EXPECT_CALL(*m_mock, process( 399*52360ca3SAlan Somers ResultOf([=](auto in) { 400*52360ca3SAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 401*52360ca3SAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 402*52360ca3SAlan Somers in.body.copy_file_range.len == (size_t)len / 2 403*52360ca3SAlan Somers ); 404*52360ca3SAlan Somers }, Eq(true)), 405*52360ca3SAlan Somers _) 406*52360ca3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 407*52360ca3SAlan Somers SET_OUT_HEADER_LEN(out, write); 408*52360ca3SAlan Somers out.body.write.size = len / 2; 409*52360ca3SAlan Somers }))); 410*52360ca3SAlan Somers 411*52360ca3SAlan Somers rl.rlim_cur = limit; 412*52360ca3SAlan Somers rl.rlim_max = m_initial_limit.rlim_max; 413*52360ca3SAlan Somers ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 414*52360ca3SAlan Somers ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 415*52360ca3SAlan Somers 416*52360ca3SAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 417*52360ca3SAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 418*52360ca3SAlan Somers ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 419*52360ca3SAlan Somers } 420*52360ca3SAlan Somers 42192bbfe1fSAlan Somers TEST_F(CopyFileRange, ok) 42292bbfe1fSAlan Somers { 42392bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 42492bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 42592bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 42692bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 42792bbfe1fSAlan Somers const uint64_t ino1 = 42; 42892bbfe1fSAlan Somers const uint64_t ino2 = 43; 42992bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 43092bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 43192bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 43292bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 43392bbfe1fSAlan Somers off_t start1 = 1 << 18; 43492bbfe1fSAlan Somers off_t start2 = 3 << 17; 43592bbfe1fSAlan Somers ssize_t len = 65536; 43692bbfe1fSAlan Somers int fd1, fd2; 43792bbfe1fSAlan Somers 43892bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 43992bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 44092bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 44192bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 44292bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 44392bbfe1fSAlan Somers ResultOf([=](auto in) { 44492bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 44592bbfe1fSAlan Somers in.header.nodeid == ino1 && 44692bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 44792bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 44892bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 44992bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 45092bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 45192bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 45292bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 45392bbfe1fSAlan Somers }, Eq(true)), 45492bbfe1fSAlan Somers _) 45592bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 45692bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 45792bbfe1fSAlan Somers out.body.write.size = len; 45892bbfe1fSAlan Somers }))); 45992bbfe1fSAlan Somers 46092bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 46192bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 46292bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 46392bbfe1fSAlan Somers } 46492bbfe1fSAlan Somers 46592bbfe1fSAlan Somers /* 46692bbfe1fSAlan Somers * copy_file_range can make copies within a single file, as long as the ranges 46792bbfe1fSAlan Somers * don't overlap. 46892bbfe1fSAlan Somers * */ 46992bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file) 47092bbfe1fSAlan Somers { 47192bbfe1fSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 47292bbfe1fSAlan Somers const char RELPATH[] = "src.txt"; 47392bbfe1fSAlan Somers const uint64_t ino = 4; 47492bbfe1fSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 47592bbfe1fSAlan Somers off_t fsize = 1 << 20; /* 1 MiB */ 47692bbfe1fSAlan Somers off_t off_in = 1 << 18; 47792bbfe1fSAlan Somers off_t off_out = 3 << 17; 47892bbfe1fSAlan Somers ssize_t len = 65536; 47992bbfe1fSAlan Somers int fd; 48092bbfe1fSAlan Somers 48192bbfe1fSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 48292bbfe1fSAlan Somers expect_open(ino, 0, 1, fh); 48392bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 48492bbfe1fSAlan Somers ResultOf([=](auto in) { 48592bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 48692bbfe1fSAlan Somers in.header.nodeid == ino && 48792bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh && 48892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 48992bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino && 49092bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh && 49192bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 49292bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 49392bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 49492bbfe1fSAlan Somers }, Eq(true)), 49592bbfe1fSAlan Somers _) 49692bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 49792bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 49892bbfe1fSAlan Somers out.body.write.size = len; 49992bbfe1fSAlan Somers }))); 50092bbfe1fSAlan Somers 50192bbfe1fSAlan Somers fd = open(FULLPATH, O_RDWR); 50292bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 5034ac4b126SAlan Somers 5044ac4b126SAlan Somers leak(fd); 50592bbfe1fSAlan Somers } 50692bbfe1fSAlan Somers 50765d70b3bSAlan Somers /* 5085169832cSAlan Somers * copy_file_range should update the destination's mtime and ctime, and 5095169832cSAlan Somers * the source's atime. 5105169832cSAlan Somers */ 5115169832cSAlan Somers TEST_F(CopyFileRange, timestamps) 5125169832cSAlan Somers { 5135169832cSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 5145169832cSAlan Somers const char RELPATH1[] = "src.txt"; 5155169832cSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 5165169832cSAlan Somers const char RELPATH2[] = "dst.txt"; 5175169832cSAlan Somers struct stat sb1a, sb1b, sb2a, sb2b; 5185169832cSAlan Somers const uint64_t ino1 = 42; 5195169832cSAlan Somers const uint64_t ino2 = 43; 5205169832cSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 5215169832cSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 5225169832cSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 5235169832cSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 5245169832cSAlan Somers off_t start1 = 1 << 18; 5255169832cSAlan Somers off_t start2 = 3 << 17; 5265169832cSAlan Somers ssize_t len = 65536; 5275169832cSAlan Somers int fd1, fd2; 5285169832cSAlan Somers 5295169832cSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 5305169832cSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 5315169832cSAlan Somers expect_open(ino1, 0, 1, fh1); 5325169832cSAlan Somers expect_open(ino2, 0, 1, fh2); 5335169832cSAlan Somers EXPECT_CALL(*m_mock, process( 5345169832cSAlan Somers ResultOf([=](auto in) { 5355169832cSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 5365169832cSAlan Somers in.header.nodeid == ino1 && 5375169832cSAlan Somers in.body.copy_file_range.fh_in == fh1 && 5385169832cSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 5395169832cSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 5405169832cSAlan Somers in.body.copy_file_range.fh_out == fh2 && 5415169832cSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 5425169832cSAlan Somers in.body.copy_file_range.len == (size_t)len && 5435169832cSAlan Somers in.body.copy_file_range.flags == 0); 5445169832cSAlan Somers }, Eq(true)), 5455169832cSAlan Somers _) 5465169832cSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 5475169832cSAlan Somers SET_OUT_HEADER_LEN(out, write); 5485169832cSAlan Somers out.body.write.size = len; 5495169832cSAlan Somers }))); 5505169832cSAlan Somers 5515169832cSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 5525169832cSAlan Somers ASSERT_GE(fd1, 0); 5535169832cSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 5545169832cSAlan Somers ASSERT_GE(fd2, 0); 5555169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno); 5565169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno); 5575169832cSAlan Somers 5585169832cSAlan Somers nap(); 5595169832cSAlan Somers 5605169832cSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 5615169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno); 5625169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno); 5635169832cSAlan Somers 5645169832cSAlan Somers EXPECT_NE(sb1a.st_atime, sb1b.st_atime); 5655169832cSAlan Somers EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime); 5665169832cSAlan Somers EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime); 5675169832cSAlan Somers EXPECT_EQ(sb2a.st_atime, sb2b.st_atime); 5685169832cSAlan Somers EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime); 5695169832cSAlan Somers EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime); 5705169832cSAlan Somers 5715169832cSAlan Somers leak(fd1); 5725169832cSAlan Somers leak(fd2); 5735169832cSAlan Somers } 5745169832cSAlan Somers 5755169832cSAlan Somers /* 57665d70b3bSAlan Somers * copy_file_range can extend the size of a file 57765d70b3bSAlan Somers * */ 57865d70b3bSAlan Somers TEST_F(CopyFileRange, extend) 57965d70b3bSAlan Somers { 58065d70b3bSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 58165d70b3bSAlan Somers const char RELPATH[] = "src.txt"; 58265d70b3bSAlan Somers struct stat sb; 58365d70b3bSAlan Somers const uint64_t ino = 4; 58465d70b3bSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 58565d70b3bSAlan Somers off_t fsize = 65536; 58665d70b3bSAlan Somers off_t off_in = 0; 58765d70b3bSAlan Somers off_t off_out = 65536; 58865d70b3bSAlan Somers ssize_t len = 65536; 58965d70b3bSAlan Somers int fd; 59065d70b3bSAlan Somers 59165d70b3bSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 59265d70b3bSAlan Somers expect_open(ino, 0, 1, fh); 59365d70b3bSAlan Somers EXPECT_CALL(*m_mock, process( 59465d70b3bSAlan Somers ResultOf([=](auto in) { 59565d70b3bSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 59665d70b3bSAlan Somers in.header.nodeid == ino && 59765d70b3bSAlan Somers in.body.copy_file_range.fh_in == fh && 59865d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 59965d70b3bSAlan Somers in.body.copy_file_range.nodeid_out == ino && 60065d70b3bSAlan Somers in.body.copy_file_range.fh_out == fh && 60165d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 60265d70b3bSAlan Somers in.body.copy_file_range.len == (size_t)len && 60365d70b3bSAlan Somers in.body.copy_file_range.flags == 0); 60465d70b3bSAlan Somers }, Eq(true)), 60565d70b3bSAlan Somers _) 60665d70b3bSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 60765d70b3bSAlan Somers SET_OUT_HEADER_LEN(out, write); 60865d70b3bSAlan Somers out.body.write.size = len; 60965d70b3bSAlan Somers }))); 61065d70b3bSAlan Somers 61165d70b3bSAlan Somers fd = open(FULLPATH, O_RDWR); 61265d70b3bSAlan Somers ASSERT_GE(fd, 0); 61365d70b3bSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 61465d70b3bSAlan Somers 61565d70b3bSAlan Somers /* Check that cached attributes were updated appropriately */ 61665d70b3bSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 61765d70b3bSAlan Somers EXPECT_EQ(fsize + len, sb.st_size); 61865d70b3bSAlan Somers 61965d70b3bSAlan Somers leak(fd); 62065d70b3bSAlan Somers } 62165d70b3bSAlan Somers 62292bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */ 62392bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback) 62492bbfe1fSAlan Somers { 62592bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 62692bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 62792bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 62892bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 62992bbfe1fSAlan Somers const uint64_t ino1 = 42; 63092bbfe1fSAlan Somers const uint64_t ino2 = 43; 63192bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 63292bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 63392bbfe1fSAlan Somers off_t fsize2 = 0; 63492bbfe1fSAlan Somers off_t start1 = 0; 63592bbfe1fSAlan Somers off_t start2 = 0; 63692bbfe1fSAlan Somers const char *contents = "Hello, world!"; 63792bbfe1fSAlan Somers ssize_t len; 63892bbfe1fSAlan Somers int fd1, fd2; 63992bbfe1fSAlan Somers 64092bbfe1fSAlan Somers len = strlen(contents); 64192bbfe1fSAlan Somers 64292bbfe1fSAlan Somers /* 64392bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 64492bbfe1fSAlan Somers * predictable. 64592bbfe1fSAlan Somers */ 64692bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 64792bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 64892bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 64992bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 65092bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 65192bbfe1fSAlan Somers ResultOf([=](auto in) { 65292bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 65392bbfe1fSAlan Somers }, Eq(true)), 65492bbfe1fSAlan Somers _) 65592bbfe1fSAlan Somers ).Times(0); 65692bbfe1fSAlan Somers expect_maybe_lseek(ino1); 65792bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 65892bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 65992bbfe1fSAlan Somers 66092bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 66192bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 66292bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 66392bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 66492bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 6654ac4b126SAlan Somers 6664ac4b126SAlan Somers leak(fd1); 6674ac4b126SAlan Somers leak(fd2); 66892bbfe1fSAlan Somers } 66992bbfe1fSAlan Somers 6705169832cSAlan Somers /* 6715169832cSAlan Somers * With -o noatime, copy_file_range should update the destination's mtime and 6725169832cSAlan Somers * ctime, but not the source's atime. 6735169832cSAlan Somers */ 6745169832cSAlan Somers TEST_F(CopyFileRangeNoAtime, timestamps) 6755169832cSAlan Somers { 6765169832cSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 6775169832cSAlan Somers const char RELPATH1[] = "src.txt"; 6785169832cSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 6795169832cSAlan Somers const char RELPATH2[] = "dst.txt"; 6805169832cSAlan Somers struct stat sb1a, sb1b, sb2a, sb2b; 6815169832cSAlan Somers const uint64_t ino1 = 42; 6825169832cSAlan Somers const uint64_t ino2 = 43; 6835169832cSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 6845169832cSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 6855169832cSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 6865169832cSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 6875169832cSAlan Somers off_t start1 = 1 << 18; 6885169832cSAlan Somers off_t start2 = 3 << 17; 6895169832cSAlan Somers ssize_t len = 65536; 6905169832cSAlan Somers int fd1, fd2; 69192bbfe1fSAlan Somers 6925169832cSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 6935169832cSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 6945169832cSAlan Somers expect_open(ino1, 0, 1, fh1); 6955169832cSAlan Somers expect_open(ino2, 0, 1, fh2); 6965169832cSAlan Somers EXPECT_CALL(*m_mock, process( 6975169832cSAlan Somers ResultOf([=](auto in) { 6985169832cSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 6995169832cSAlan Somers in.header.nodeid == ino1 && 7005169832cSAlan Somers in.body.copy_file_range.fh_in == fh1 && 7015169832cSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 7025169832cSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 7035169832cSAlan Somers in.body.copy_file_range.fh_out == fh2 && 7045169832cSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 7055169832cSAlan Somers in.body.copy_file_range.len == (size_t)len && 7065169832cSAlan Somers in.body.copy_file_range.flags == 0); 7075169832cSAlan Somers }, Eq(true)), 7085169832cSAlan Somers _) 7095169832cSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 7105169832cSAlan Somers SET_OUT_HEADER_LEN(out, write); 7115169832cSAlan Somers out.body.write.size = len; 7125169832cSAlan Somers }))); 7135169832cSAlan Somers 7145169832cSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 7155169832cSAlan Somers ASSERT_GE(fd1, 0); 7165169832cSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 7175169832cSAlan Somers ASSERT_GE(fd2, 0); 7185169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno); 7195169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno); 7205169832cSAlan Somers 7215169832cSAlan Somers nap(); 7225169832cSAlan Somers 7235169832cSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 7245169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno); 7255169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno); 7265169832cSAlan Somers 7275169832cSAlan Somers EXPECT_EQ(sb1a.st_atime, sb1b.st_atime); 7285169832cSAlan Somers EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime); 7295169832cSAlan Somers EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime); 7305169832cSAlan Somers EXPECT_EQ(sb2a.st_atime, sb2b.st_atime); 7315169832cSAlan Somers EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime); 7325169832cSAlan Somers EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime); 7335169832cSAlan Somers 7345169832cSAlan Somers leak(fd1); 7355169832cSAlan Somers leak(fd2); 7365169832cSAlan Somers } 737