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 1325169832cSAlan Somers class CopyFileRangeNoAtime: public CopyFileRange { 1335169832cSAlan Somers public: 1345169832cSAlan Somers virtual void SetUp() { 1355169832cSAlan Somers m_noatime = true; 1365169832cSAlan Somers CopyFileRange::SetUp(); 1375169832cSAlan Somers } 1385169832cSAlan Somers }; 1395169832cSAlan Somers 14092bbfe1fSAlan Somers TEST_F(CopyFileRange, eio) 14192bbfe1fSAlan Somers { 14292bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 14392bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 14492bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 14592bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 14692bbfe1fSAlan Somers const uint64_t ino1 = 42; 14792bbfe1fSAlan Somers const uint64_t ino2 = 43; 14892bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 14992bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 15092bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 15192bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 15292bbfe1fSAlan Somers off_t start1 = 1 << 18; 15392bbfe1fSAlan Somers off_t start2 = 3 << 17; 15492bbfe1fSAlan Somers ssize_t len = 65536; 15592bbfe1fSAlan Somers int fd1, fd2; 15692bbfe1fSAlan Somers 15792bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 15892bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 15992bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 16092bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 16192bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 16292bbfe1fSAlan Somers ResultOf([=](auto in) { 16392bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 16492bbfe1fSAlan Somers in.header.nodeid == ino1 && 16592bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 16692bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 16792bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 16892bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 16992bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 17092bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 17192bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 17292bbfe1fSAlan Somers }, Eq(true)), 17392bbfe1fSAlan Somers _) 17492bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(EIO))); 17592bbfe1fSAlan Somers 17692bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 17792bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 17892bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 17992bbfe1fSAlan Somers EXPECT_EQ(EIO, errno); 18092bbfe1fSAlan Somers } 18192bbfe1fSAlan Somers 18292bbfe1fSAlan Somers /* 18341ae9f9eSAlan Somers * copy_file_range should evict cached data for the modified region of the 18441ae9f9eSAlan Somers * destination file. 18541ae9f9eSAlan Somers */ 18641ae9f9eSAlan Somers TEST_F(CopyFileRange, evicts_cache) 18741ae9f9eSAlan Somers { 18841ae9f9eSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 18941ae9f9eSAlan Somers const char RELPATH1[] = "src.txt"; 19041ae9f9eSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 19141ae9f9eSAlan Somers const char RELPATH2[] = "dst.txt"; 19241ae9f9eSAlan Somers void *buf0, *buf1, *buf; 19341ae9f9eSAlan Somers const uint64_t ino1 = 42; 19441ae9f9eSAlan Somers const uint64_t ino2 = 43; 19541ae9f9eSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 19641ae9f9eSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 19741ae9f9eSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 19841ae9f9eSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 19941ae9f9eSAlan Somers off_t start1 = 1 << 18; 20041ae9f9eSAlan Somers off_t start2 = 3 << 17; 20141ae9f9eSAlan Somers ssize_t len = m_maxbcachebuf; 20241ae9f9eSAlan Somers int fd1, fd2; 20341ae9f9eSAlan Somers 20441ae9f9eSAlan Somers buf0 = malloc(m_maxbcachebuf); 20541ae9f9eSAlan Somers memset(buf0, 42, m_maxbcachebuf); 20641ae9f9eSAlan Somers 20741ae9f9eSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 20841ae9f9eSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 20941ae9f9eSAlan Somers expect_open(ino1, 0, 1, fh1); 21041ae9f9eSAlan Somers expect_open(ino2, 0, 1, fh2); 21141ae9f9eSAlan Somers expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1, 21241ae9f9eSAlan Somers fh2); 21341ae9f9eSAlan Somers EXPECT_CALL(*m_mock, process( 21441ae9f9eSAlan Somers ResultOf([=](auto in) { 21541ae9f9eSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 21641ae9f9eSAlan Somers in.header.nodeid == ino1 && 21741ae9f9eSAlan Somers in.body.copy_file_range.fh_in == fh1 && 21841ae9f9eSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 21941ae9f9eSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 22041ae9f9eSAlan Somers in.body.copy_file_range.fh_out == fh2 && 22141ae9f9eSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 22241ae9f9eSAlan Somers in.body.copy_file_range.len == (size_t)len && 22341ae9f9eSAlan Somers in.body.copy_file_range.flags == 0); 22441ae9f9eSAlan Somers }, Eq(true)), 22541ae9f9eSAlan Somers _) 22641ae9f9eSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 22741ae9f9eSAlan Somers SET_OUT_HEADER_LEN(out, write); 22841ae9f9eSAlan Somers out.body.write.size = len; 22941ae9f9eSAlan Somers }))); 23041ae9f9eSAlan Somers 23141ae9f9eSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 23241ae9f9eSAlan Somers fd2 = open(FULLPATH2, O_RDWR); 23341ae9f9eSAlan Somers 23441ae9f9eSAlan Somers // Prime cache 23541ae9f9eSAlan Somers buf = malloc(m_maxbcachebuf); 23641ae9f9eSAlan Somers ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 23741ae9f9eSAlan Somers << strerror(errno); 23841ae9f9eSAlan Somers EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf)); 23941ae9f9eSAlan Somers 24041ae9f9eSAlan Somers // Tell the FUSE server overwrite the region we just read 24141ae9f9eSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 24241ae9f9eSAlan Somers 24341ae9f9eSAlan Somers // Read again. This should bypass the cache and read direct from server 24441ae9f9eSAlan Somers buf1 = malloc(m_maxbcachebuf); 24541ae9f9eSAlan Somers memset(buf1, 69, m_maxbcachebuf); 24641ae9f9eSAlan Somers start2 -= len; 24741ae9f9eSAlan Somers expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1, 24841ae9f9eSAlan Somers fh2); 24941ae9f9eSAlan Somers ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 25041ae9f9eSAlan Somers << strerror(errno); 25141ae9f9eSAlan Somers EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf)); 25241ae9f9eSAlan Somers 25341ae9f9eSAlan Somers free(buf1); 25441ae9f9eSAlan Somers free(buf0); 25541ae9f9eSAlan Somers free(buf); 25641ae9f9eSAlan Somers leak(fd1); 25741ae9f9eSAlan Somers leak(fd2); 25841ae9f9eSAlan Somers } 25941ae9f9eSAlan Somers 26041ae9f9eSAlan Somers /* 26192bbfe1fSAlan Somers * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should 26292bbfe1fSAlan Somers * fallback to a read/write based implementation. 26392bbfe1fSAlan Somers */ 26492bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback) 26592bbfe1fSAlan Somers { 26692bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 26792bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 26892bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 26992bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 27092bbfe1fSAlan Somers const uint64_t ino1 = 42; 27192bbfe1fSAlan Somers const uint64_t ino2 = 43; 27292bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 27392bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 27492bbfe1fSAlan Somers off_t fsize2 = 0; 27592bbfe1fSAlan Somers off_t start1 = 0; 27692bbfe1fSAlan Somers off_t start2 = 0; 27792bbfe1fSAlan Somers const char *contents = "Hello, world!"; 27892bbfe1fSAlan Somers ssize_t len; 27992bbfe1fSAlan Somers int fd1, fd2; 28092bbfe1fSAlan Somers 28192bbfe1fSAlan Somers len = strlen(contents); 28292bbfe1fSAlan Somers 28392bbfe1fSAlan Somers /* 28492bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 28592bbfe1fSAlan Somers * predictable. 28692bbfe1fSAlan Somers */ 28792bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 28892bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 28992bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 29092bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 29192bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 29292bbfe1fSAlan Somers ResultOf([=](auto in) { 29392bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 29492bbfe1fSAlan Somers in.header.nodeid == ino1 && 29592bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 29692bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 29792bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 29892bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 29992bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 30092bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 30192bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 30292bbfe1fSAlan Somers }, Eq(true)), 30392bbfe1fSAlan Somers _) 30492bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 30592bbfe1fSAlan Somers expect_maybe_lseek(ino1); 30692bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 30792bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 30892bbfe1fSAlan Somers 30992bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 31092bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 31192bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 31292bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 31392bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 31492bbfe1fSAlan Somers } 31592bbfe1fSAlan Somers 31692bbfe1fSAlan Somers /* fusefs should respect RLIMIT_FSIZE */ 31792bbfe1fSAlan Somers TEST_F(CopyFileRange, rlimit_fsize) 31892bbfe1fSAlan Somers { 31992bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 32092bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 32192bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 32292bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 32392bbfe1fSAlan Somers struct rlimit rl; 32492bbfe1fSAlan Somers const uint64_t ino1 = 42; 32592bbfe1fSAlan Somers const uint64_t ino2 = 43; 32692bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 32792bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 32892bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 32992bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 33092bbfe1fSAlan Somers off_t start1 = 1 << 18; 33192bbfe1fSAlan Somers off_t start2 = fsize2; 33292bbfe1fSAlan Somers ssize_t len = 65536; 33392bbfe1fSAlan Somers int fd1, fd2; 33492bbfe1fSAlan Somers 33592bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 33692bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 33792bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 33892bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 33992bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 34092bbfe1fSAlan Somers ResultOf([=](auto in) { 34192bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 34292bbfe1fSAlan Somers }, Eq(true)), 34392bbfe1fSAlan Somers _) 34492bbfe1fSAlan Somers ).Times(0); 34592bbfe1fSAlan Somers 34692bbfe1fSAlan Somers rl.rlim_cur = fsize2; 34792bbfe1fSAlan Somers rl.rlim_max = 10 * fsize2; 34892bbfe1fSAlan Somers ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 34992bbfe1fSAlan Somers ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 35092bbfe1fSAlan Somers 35192bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 35292bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 35392bbfe1fSAlan Somers ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 35492bbfe1fSAlan Somers EXPECT_EQ(EFBIG, errno); 35592bbfe1fSAlan Somers EXPECT_EQ(1, s_sigxfsz); 35692bbfe1fSAlan Somers } 35792bbfe1fSAlan Somers 35892bbfe1fSAlan Somers TEST_F(CopyFileRange, ok) 35992bbfe1fSAlan Somers { 36092bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 36192bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 36292bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 36392bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 36492bbfe1fSAlan Somers const uint64_t ino1 = 42; 36592bbfe1fSAlan Somers const uint64_t ino2 = 43; 36692bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 36792bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 36892bbfe1fSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 36992bbfe1fSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 37092bbfe1fSAlan Somers off_t start1 = 1 << 18; 37192bbfe1fSAlan Somers off_t start2 = 3 << 17; 37292bbfe1fSAlan Somers ssize_t len = 65536; 37392bbfe1fSAlan Somers int fd1, fd2; 37492bbfe1fSAlan Somers 37592bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 37692bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 37792bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 37892bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 37992bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 38092bbfe1fSAlan Somers ResultOf([=](auto in) { 38192bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 38292bbfe1fSAlan Somers in.header.nodeid == ino1 && 38392bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh1 && 38492bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 38592bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 38692bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh2 && 38792bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 38892bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 38992bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 39092bbfe1fSAlan Somers }, Eq(true)), 39192bbfe1fSAlan Somers _) 39292bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 39392bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 39492bbfe1fSAlan Somers out.body.write.size = len; 39592bbfe1fSAlan Somers }))); 39692bbfe1fSAlan Somers 39792bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 39892bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 39992bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 40092bbfe1fSAlan Somers } 40192bbfe1fSAlan Somers 40292bbfe1fSAlan Somers /* 40392bbfe1fSAlan Somers * copy_file_range can make copies within a single file, as long as the ranges 40492bbfe1fSAlan Somers * don't overlap. 40592bbfe1fSAlan Somers * */ 40692bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file) 40792bbfe1fSAlan Somers { 40892bbfe1fSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 40992bbfe1fSAlan Somers const char RELPATH[] = "src.txt"; 41092bbfe1fSAlan Somers const uint64_t ino = 4; 41192bbfe1fSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 41292bbfe1fSAlan Somers off_t fsize = 1 << 20; /* 1 MiB */ 41392bbfe1fSAlan Somers off_t off_in = 1 << 18; 41492bbfe1fSAlan Somers off_t off_out = 3 << 17; 41592bbfe1fSAlan Somers ssize_t len = 65536; 41692bbfe1fSAlan Somers int fd; 41792bbfe1fSAlan Somers 41892bbfe1fSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 41992bbfe1fSAlan Somers expect_open(ino, 0, 1, fh); 42092bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 42192bbfe1fSAlan Somers ResultOf([=](auto in) { 42292bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 42392bbfe1fSAlan Somers in.header.nodeid == ino && 42492bbfe1fSAlan Somers in.body.copy_file_range.fh_in == fh && 42592bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 42692bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino && 42792bbfe1fSAlan Somers in.body.copy_file_range.fh_out == fh && 42892bbfe1fSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 42992bbfe1fSAlan Somers in.body.copy_file_range.len == (size_t)len && 43092bbfe1fSAlan Somers in.body.copy_file_range.flags == 0); 43192bbfe1fSAlan Somers }, Eq(true)), 43292bbfe1fSAlan Somers _) 43392bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 43492bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 43592bbfe1fSAlan Somers out.body.write.size = len; 43692bbfe1fSAlan Somers }))); 43792bbfe1fSAlan Somers 43892bbfe1fSAlan Somers fd = open(FULLPATH, O_RDWR); 43992bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 440*4ac4b126SAlan Somers 441*4ac4b126SAlan Somers leak(fd); 44292bbfe1fSAlan Somers } 44392bbfe1fSAlan Somers 44465d70b3bSAlan Somers /* 4455169832cSAlan Somers * copy_file_range should update the destination's mtime and ctime, and 4465169832cSAlan Somers * the source's atime. 4475169832cSAlan Somers */ 4485169832cSAlan Somers TEST_F(CopyFileRange, timestamps) 4495169832cSAlan Somers { 4505169832cSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 4515169832cSAlan Somers const char RELPATH1[] = "src.txt"; 4525169832cSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 4535169832cSAlan Somers const char RELPATH2[] = "dst.txt"; 4545169832cSAlan Somers struct stat sb1a, sb1b, sb2a, sb2b; 4555169832cSAlan Somers const uint64_t ino1 = 42; 4565169832cSAlan Somers const uint64_t ino2 = 43; 4575169832cSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 4585169832cSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 4595169832cSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 4605169832cSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 4615169832cSAlan Somers off_t start1 = 1 << 18; 4625169832cSAlan Somers off_t start2 = 3 << 17; 4635169832cSAlan Somers ssize_t len = 65536; 4645169832cSAlan Somers int fd1, fd2; 4655169832cSAlan Somers 4665169832cSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 4675169832cSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 4685169832cSAlan Somers expect_open(ino1, 0, 1, fh1); 4695169832cSAlan Somers expect_open(ino2, 0, 1, fh2); 4705169832cSAlan Somers EXPECT_CALL(*m_mock, process( 4715169832cSAlan Somers ResultOf([=](auto in) { 4725169832cSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 4735169832cSAlan Somers in.header.nodeid == ino1 && 4745169832cSAlan Somers in.body.copy_file_range.fh_in == fh1 && 4755169832cSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 4765169832cSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 4775169832cSAlan Somers in.body.copy_file_range.fh_out == fh2 && 4785169832cSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 4795169832cSAlan Somers in.body.copy_file_range.len == (size_t)len && 4805169832cSAlan Somers in.body.copy_file_range.flags == 0); 4815169832cSAlan Somers }, Eq(true)), 4825169832cSAlan Somers _) 4835169832cSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 4845169832cSAlan Somers SET_OUT_HEADER_LEN(out, write); 4855169832cSAlan Somers out.body.write.size = len; 4865169832cSAlan Somers }))); 4875169832cSAlan Somers 4885169832cSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 4895169832cSAlan Somers ASSERT_GE(fd1, 0); 4905169832cSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 4915169832cSAlan Somers ASSERT_GE(fd2, 0); 4925169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno); 4935169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno); 4945169832cSAlan Somers 4955169832cSAlan Somers nap(); 4965169832cSAlan Somers 4975169832cSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 4985169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno); 4995169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno); 5005169832cSAlan Somers 5015169832cSAlan Somers EXPECT_NE(sb1a.st_atime, sb1b.st_atime); 5025169832cSAlan Somers EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime); 5035169832cSAlan Somers EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime); 5045169832cSAlan Somers EXPECT_EQ(sb2a.st_atime, sb2b.st_atime); 5055169832cSAlan Somers EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime); 5065169832cSAlan Somers EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime); 5075169832cSAlan Somers 5085169832cSAlan Somers leak(fd1); 5095169832cSAlan Somers leak(fd2); 5105169832cSAlan Somers } 5115169832cSAlan Somers 5125169832cSAlan Somers /* 51365d70b3bSAlan Somers * copy_file_range can extend the size of a file 51465d70b3bSAlan Somers * */ 51565d70b3bSAlan Somers TEST_F(CopyFileRange, extend) 51665d70b3bSAlan Somers { 51765d70b3bSAlan Somers const char FULLPATH[] = "mountpoint/src.txt"; 51865d70b3bSAlan Somers const char RELPATH[] = "src.txt"; 51965d70b3bSAlan Somers struct stat sb; 52065d70b3bSAlan Somers const uint64_t ino = 4; 52165d70b3bSAlan Somers const uint64_t fh = 0xdeadbeefa7ebabe; 52265d70b3bSAlan Somers off_t fsize = 65536; 52365d70b3bSAlan Somers off_t off_in = 0; 52465d70b3bSAlan Somers off_t off_out = 65536; 52565d70b3bSAlan Somers ssize_t len = 65536; 52665d70b3bSAlan Somers int fd; 52765d70b3bSAlan Somers 52865d70b3bSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 52965d70b3bSAlan Somers expect_open(ino, 0, 1, fh); 53065d70b3bSAlan Somers EXPECT_CALL(*m_mock, process( 53165d70b3bSAlan Somers ResultOf([=](auto in) { 53265d70b3bSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 53365d70b3bSAlan Somers in.header.nodeid == ino && 53465d70b3bSAlan Somers in.body.copy_file_range.fh_in == fh && 53565d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_in == off_in && 53665d70b3bSAlan Somers in.body.copy_file_range.nodeid_out == ino && 53765d70b3bSAlan Somers in.body.copy_file_range.fh_out == fh && 53865d70b3bSAlan Somers (off_t)in.body.copy_file_range.off_out == off_out && 53965d70b3bSAlan Somers in.body.copy_file_range.len == (size_t)len && 54065d70b3bSAlan Somers in.body.copy_file_range.flags == 0); 54165d70b3bSAlan Somers }, Eq(true)), 54265d70b3bSAlan Somers _) 54365d70b3bSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 54465d70b3bSAlan Somers SET_OUT_HEADER_LEN(out, write); 54565d70b3bSAlan Somers out.body.write.size = len; 54665d70b3bSAlan Somers }))); 54765d70b3bSAlan Somers 54865d70b3bSAlan Somers fd = open(FULLPATH, O_RDWR); 54965d70b3bSAlan Somers ASSERT_GE(fd, 0); 55065d70b3bSAlan Somers ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 55165d70b3bSAlan Somers 55265d70b3bSAlan Somers /* Check that cached attributes were updated appropriately */ 55365d70b3bSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 55465d70b3bSAlan Somers EXPECT_EQ(fsize + len, sb.st_size); 55565d70b3bSAlan Somers 55665d70b3bSAlan Somers leak(fd); 55765d70b3bSAlan Somers } 55865d70b3bSAlan Somers 55992bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */ 56092bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback) 56192bbfe1fSAlan Somers { 56292bbfe1fSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 56392bbfe1fSAlan Somers const char RELPATH1[] = "src.txt"; 56492bbfe1fSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 56592bbfe1fSAlan Somers const char RELPATH2[] = "dst.txt"; 56692bbfe1fSAlan Somers const uint64_t ino1 = 42; 56792bbfe1fSAlan Somers const uint64_t ino2 = 43; 56892bbfe1fSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 56992bbfe1fSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 57092bbfe1fSAlan Somers off_t fsize2 = 0; 57192bbfe1fSAlan Somers off_t start1 = 0; 57292bbfe1fSAlan Somers off_t start2 = 0; 57392bbfe1fSAlan Somers const char *contents = "Hello, world!"; 57492bbfe1fSAlan Somers ssize_t len; 57592bbfe1fSAlan Somers int fd1, fd2; 57692bbfe1fSAlan Somers 57792bbfe1fSAlan Somers len = strlen(contents); 57892bbfe1fSAlan Somers 57992bbfe1fSAlan Somers /* 58092bbfe1fSAlan Somers * Ensure that we read to EOF, just so the buffer cache's read size is 58192bbfe1fSAlan Somers * predictable. 58292bbfe1fSAlan Somers */ 58392bbfe1fSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 58492bbfe1fSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 58592bbfe1fSAlan Somers expect_open(ino1, 0, 1, fh1); 58692bbfe1fSAlan Somers expect_open(ino2, 0, 1, fh2); 58792bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 58892bbfe1fSAlan Somers ResultOf([=](auto in) { 58992bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE); 59092bbfe1fSAlan Somers }, Eq(true)), 59192bbfe1fSAlan Somers _) 59292bbfe1fSAlan Somers ).Times(0); 59392bbfe1fSAlan Somers expect_maybe_lseek(ino1); 59492bbfe1fSAlan Somers expect_read(ino1, start1, len, len, contents, 0); 59592bbfe1fSAlan Somers expect_write(ino2, start2, len, len, contents); 59692bbfe1fSAlan Somers 59792bbfe1fSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 59892bbfe1fSAlan Somers ASSERT_GE(fd1, 0); 59992bbfe1fSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 60092bbfe1fSAlan Somers ASSERT_GE(fd2, 0); 60192bbfe1fSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 602*4ac4b126SAlan Somers 603*4ac4b126SAlan Somers leak(fd1); 604*4ac4b126SAlan Somers leak(fd2); 60592bbfe1fSAlan Somers } 60692bbfe1fSAlan Somers 6075169832cSAlan Somers /* 6085169832cSAlan Somers * With -o noatime, copy_file_range should update the destination's mtime and 6095169832cSAlan Somers * ctime, but not the source's atime. 6105169832cSAlan Somers */ 6115169832cSAlan Somers TEST_F(CopyFileRangeNoAtime, timestamps) 6125169832cSAlan Somers { 6135169832cSAlan Somers const char FULLPATH1[] = "mountpoint/src.txt"; 6145169832cSAlan Somers const char RELPATH1[] = "src.txt"; 6155169832cSAlan Somers const char FULLPATH2[] = "mountpoint/dst.txt"; 6165169832cSAlan Somers const char RELPATH2[] = "dst.txt"; 6175169832cSAlan Somers struct stat sb1a, sb1b, sb2a, sb2b; 6185169832cSAlan Somers const uint64_t ino1 = 42; 6195169832cSAlan Somers const uint64_t ino2 = 43; 6205169832cSAlan Somers const uint64_t fh1 = 0xdeadbeef1a7ebabe; 6215169832cSAlan Somers const uint64_t fh2 = 0xdeadc0de88c0ffee; 6225169832cSAlan Somers off_t fsize1 = 1 << 20; /* 1 MiB */ 6235169832cSAlan Somers off_t fsize2 = 1 << 19; /* 512 KiB */ 6245169832cSAlan Somers off_t start1 = 1 << 18; 6255169832cSAlan Somers off_t start2 = 3 << 17; 6265169832cSAlan Somers ssize_t len = 65536; 6275169832cSAlan Somers int fd1, fd2; 62892bbfe1fSAlan Somers 6295169832cSAlan Somers expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 6305169832cSAlan Somers expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 6315169832cSAlan Somers expect_open(ino1, 0, 1, fh1); 6325169832cSAlan Somers expect_open(ino2, 0, 1, fh2); 6335169832cSAlan Somers EXPECT_CALL(*m_mock, process( 6345169832cSAlan Somers ResultOf([=](auto in) { 6355169832cSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 6365169832cSAlan Somers in.header.nodeid == ino1 && 6375169832cSAlan Somers in.body.copy_file_range.fh_in == fh1 && 6385169832cSAlan Somers (off_t)in.body.copy_file_range.off_in == start1 && 6395169832cSAlan Somers in.body.copy_file_range.nodeid_out == ino2 && 6405169832cSAlan Somers in.body.copy_file_range.fh_out == fh2 && 6415169832cSAlan Somers (off_t)in.body.copy_file_range.off_out == start2 && 6425169832cSAlan Somers in.body.copy_file_range.len == (size_t)len && 6435169832cSAlan Somers in.body.copy_file_range.flags == 0); 6445169832cSAlan Somers }, Eq(true)), 6455169832cSAlan Somers _) 6465169832cSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 6475169832cSAlan Somers SET_OUT_HEADER_LEN(out, write); 6485169832cSAlan Somers out.body.write.size = len; 6495169832cSAlan Somers }))); 6505169832cSAlan Somers 6515169832cSAlan Somers fd1 = open(FULLPATH1, O_RDONLY); 6525169832cSAlan Somers ASSERT_GE(fd1, 0); 6535169832cSAlan Somers fd2 = open(FULLPATH2, O_WRONLY); 6545169832cSAlan Somers ASSERT_GE(fd2, 0); 6555169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno); 6565169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno); 6575169832cSAlan Somers 6585169832cSAlan Somers nap(); 6595169832cSAlan Somers 6605169832cSAlan Somers ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 6615169832cSAlan Somers ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno); 6625169832cSAlan Somers ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno); 6635169832cSAlan Somers 6645169832cSAlan Somers EXPECT_EQ(sb1a.st_atime, sb1b.st_atime); 6655169832cSAlan Somers EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime); 6665169832cSAlan Somers EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime); 6675169832cSAlan Somers EXPECT_EQ(sb2a.st_atime, sb2b.st_atime); 6685169832cSAlan Somers EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime); 6695169832cSAlan Somers EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime); 6705169832cSAlan Somers 6715169832cSAlan Somers leak(fd1); 6725169832cSAlan Somers leak(fd2); 6735169832cSAlan Somers } 674