Path: blob/main/tests/sys/fs/fusefs/copy_file_range.cc
39537 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2020 Alan Somers4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627extern "C" {28#include <sys/param.h>29#include <sys/mman.h>30#include <sys/time.h>31#include <sys/resource.h>3233#include <fcntl.h>34#include <signal.h>35#include <unistd.h>36}3738#include "mockfs.hh"39#include "utils.hh"4041using namespace testing;4243class CopyFileRange: public FuseTest {44public:4546void expect_maybe_lseek(uint64_t ino)47{48EXPECT_CALL(*m_mock, process(49ResultOf([=](auto in) {50return (in.header.opcode == FUSE_LSEEK &&51in.header.nodeid == ino);52}, Eq(true)),53_)54).Times(AtMost(1))55.WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));56}5758void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)59{60EXPECT_CALL(*m_mock, process(61ResultOf([=](auto in) {62return (in.header.opcode == FUSE_OPEN &&63in.header.nodeid == ino);64}, Eq(true)),65_)66).Times(times)67.WillRepeatedly(Invoke(68ReturnImmediate([=](auto in __unused, auto& out) {69out.header.len = sizeof(out.header);70SET_OUT_HEADER_LEN(out, open);71out.body.open.fh = fh;72out.body.open.open_flags = flags;73})));74}7576void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,77uint64_t osize, const void *contents)78{79EXPECT_CALL(*m_mock, process(80ResultOf([=](auto in) {81const char *buf = (const char*)in.body.bytes +82sizeof(struct fuse_write_in);8384return (in.header.opcode == FUSE_WRITE &&85in.header.nodeid == ino &&86in.body.write.offset == offset &&87in.body.write.size == isize &&880 == bcmp(buf, contents, isize));89}, Eq(true)),90_)91).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {92SET_OUT_HEADER_LEN(out, write);93out.body.write.size = osize;94})));95}9697};9899100class CopyFileRange_7_27: public CopyFileRange {101public:102virtual void SetUp() {103m_kernel_minor_version = 27;104CopyFileRange::SetUp();105}106};107108class CopyFileRangeNoAtime: public CopyFileRange {109public:110virtual void SetUp() {111m_noatime = true;112CopyFileRange::SetUp();113}114};115116class CopyFileRangeRlimitFsize: public CopyFileRange {117public:118static sig_atomic_t s_sigxfsz;119struct rlimit m_initial_limit;120121virtual void SetUp() {122s_sigxfsz = 0;123getrlimit(RLIMIT_FSIZE, &m_initial_limit);124CopyFileRange::SetUp();125}126127void TearDown() {128struct sigaction sa;129130setrlimit(RLIMIT_FSIZE, &m_initial_limit);131132bzero(&sa, sizeof(sa));133sa.sa_handler = SIG_DFL;134sigaction(SIGXFSZ, &sa, NULL);135136FuseTest::TearDown();137}138139};140141sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;142143void sigxfsz_handler(int __unused sig) {144CopyFileRangeRlimitFsize::s_sigxfsz = 1;145}146147TEST_F(CopyFileRange, eio)148{149const char FULLPATH1[] = "mountpoint/src.txt";150const char RELPATH1[] = "src.txt";151const char FULLPATH2[] = "mountpoint/dst.txt";152const char RELPATH2[] = "dst.txt";153const uint64_t ino1 = 42;154const uint64_t ino2 = 43;155const uint64_t fh1 = 0xdeadbeef1a7ebabe;156const uint64_t fh2 = 0xdeadc0de88c0ffee;157off_t fsize1 = 1 << 20; /* 1 MiB */158off_t fsize2 = 1 << 19; /* 512 KiB */159off_t start1 = 1 << 18;160off_t start2 = 3 << 17;161ssize_t len = 65536;162int fd1, fd2;163164expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);165expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);166expect_open(ino1, 0, 1, fh1);167expect_open(ino2, 0, 1, fh2);168EXPECT_CALL(*m_mock, process(169ResultOf([=](auto in) {170return (in.header.opcode == FUSE_COPY_FILE_RANGE &&171in.header.nodeid == ino1 &&172in.body.copy_file_range.fh_in == fh1 &&173(off_t)in.body.copy_file_range.off_in == start1 &&174in.body.copy_file_range.nodeid_out == ino2 &&175in.body.copy_file_range.fh_out == fh2 &&176(off_t)in.body.copy_file_range.off_out == start2 &&177in.body.copy_file_range.len == (size_t)len &&178in.body.copy_file_range.flags == 0);179}, Eq(true)),180_)181).WillOnce(Invoke(ReturnErrno(EIO)));182183fd1 = open(FULLPATH1, O_RDONLY);184fd2 = open(FULLPATH2, O_WRONLY);185ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));186EXPECT_EQ(EIO, errno);187}188189/*190* copy_file_range should evict cached data for the modified region of the191* destination file.192*/193TEST_F(CopyFileRange, evicts_cache)194{195const char FULLPATH1[] = "mountpoint/src.txt";196const char RELPATH1[] = "src.txt";197const char FULLPATH2[] = "mountpoint/dst.txt";198const char RELPATH2[] = "dst.txt";199char *buf0, *buf1, *buf;200const uint64_t ino1 = 42;201const uint64_t ino2 = 43;202const uint64_t fh1 = 0xdeadbeef1a7ebabe;203const uint64_t fh2 = 0xdeadc0de88c0ffee;204off_t fsize1 = 1 << 20; /* 1 MiB */205off_t fsize2 = 1 << 19; /* 512 KiB */206off_t start1 = 1 << 18;207off_t start2 = 3 << 17;208ssize_t len = m_maxbcachebuf;209int fd1, fd2;210211buf0 = new char[m_maxbcachebuf];212memset(buf0, 42, m_maxbcachebuf);213214expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);215expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);216expect_open(ino1, 0, 1, fh1);217expect_open(ino2, 0, 1, fh2);218expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1,219fh2);220EXPECT_CALL(*m_mock, process(221ResultOf([=](auto in) {222return (in.header.opcode == FUSE_COPY_FILE_RANGE &&223in.header.nodeid == ino1 &&224in.body.copy_file_range.fh_in == fh1 &&225(off_t)in.body.copy_file_range.off_in == start1 &&226in.body.copy_file_range.nodeid_out == ino2 &&227in.body.copy_file_range.fh_out == fh2 &&228(off_t)in.body.copy_file_range.off_out == start2 &&229in.body.copy_file_range.len == (size_t)len &&230in.body.copy_file_range.flags == 0);231}, Eq(true)),232_)233).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {234SET_OUT_HEADER_LEN(out, write);235out.body.write.size = len;236})));237238fd1 = open(FULLPATH1, O_RDONLY);239fd2 = open(FULLPATH2, O_RDWR);240241// Prime cache242buf = new char[m_maxbcachebuf];243ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))244<< strerror(errno);245EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));246247// Tell the FUSE server overwrite the region we just read248ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));249250// Read again. This should bypass the cache and read direct from server251buf1 = new char[m_maxbcachebuf];252memset(buf1, 69, m_maxbcachebuf);253start2 -= len;254expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,255fh2);256ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))257<< strerror(errno);258EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));259260delete[] buf1;261delete[] buf0;262delete[] buf;263leak(fd1);264leak(fd2);265}266267/*268* If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should269* fallback to a read/write based implementation.270*/271TEST_F(CopyFileRange, fallback)272{273const char FULLPATH1[] = "mountpoint/src.txt";274const char RELPATH1[] = "src.txt";275const char FULLPATH2[] = "mountpoint/dst.txt";276const char RELPATH2[] = "dst.txt";277const uint64_t ino1 = 42;278const uint64_t ino2 = 43;279const uint64_t fh1 = 0xdeadbeef1a7ebabe;280const uint64_t fh2 = 0xdeadc0de88c0ffee;281off_t fsize2 = 0;282off_t start1 = 0;283off_t start2 = 0;284const char *contents = "Hello, world!";285ssize_t len;286int fd1, fd2;287288len = strlen(contents);289290/*291* Ensure that we read to EOF, just so the buffer cache's read size is292* predictable.293*/294expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);295expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);296expect_open(ino1, 0, 1, fh1);297expect_open(ino2, 0, 1, fh2);298EXPECT_CALL(*m_mock, process(299ResultOf([=](auto in) {300return (in.header.opcode == FUSE_COPY_FILE_RANGE &&301in.header.nodeid == ino1 &&302in.body.copy_file_range.fh_in == fh1 &&303(off_t)in.body.copy_file_range.off_in == start1 &&304in.body.copy_file_range.nodeid_out == ino2 &&305in.body.copy_file_range.fh_out == fh2 &&306(off_t)in.body.copy_file_range.off_out == start2 &&307in.body.copy_file_range.len == (size_t)len &&308in.body.copy_file_range.flags == 0);309}, Eq(true)),310_)311).WillOnce(Invoke(ReturnErrno(ENOSYS)));312expect_maybe_lseek(ino1);313expect_read(ino1, start1, len, len, contents, 0);314expect_write(ino2, start2, len, len, contents);315316fd1 = open(FULLPATH1, O_RDONLY);317ASSERT_GE(fd1, 0);318fd2 = open(FULLPATH2, O_WRONLY);319ASSERT_GE(fd2, 0);320ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));321}322323/*324* Writes via mmap should not conflict with using copy_file_range. Any dirty325* pages that overlap with copy_file_range's input should be flushed before326* FUSE_COPY_FILE_RANGE is sent.327*/328TEST_F(CopyFileRange, mmap_write)329{330const char FULLPATH[] = "mountpoint/src.txt";331const char RELPATH[] = "src.txt";332uint8_t *wbuf, *fbuf;333void *p;334size_t fsize = 0x6000;335size_t wsize = 0x3000;336ssize_t r;337off_t offset2_in = 0;338off_t offset2_out = wsize;339size_t copysize = wsize;340const uint64_t ino = 42;341const uint64_t fh = 0xdeadbeef1a7ebabe;342int fd;343const mode_t mode = 0644;344345fbuf = new uint8_t[fsize]();346wbuf = new uint8_t[wsize];347memset(wbuf, 1, wsize);348349expect_lookup(RELPATH, ino, S_IFREG | mode, fsize, 1);350expect_open(ino, 0, 1, fh);351/* This read is initiated by the mmap write */352expect_read(ino, 0, fsize, fsize, fbuf, -1, fh);353/* This write flushes the buffer filled by the mmap write */354expect_write(ino, 0, wsize, wsize, wbuf);355356EXPECT_CALL(*m_mock, process(357ResultOf([=](auto in) {358return (in.header.opcode == FUSE_COPY_FILE_RANGE &&359(off_t)in.body.copy_file_range.off_in == offset2_in &&360(off_t)in.body.copy_file_range.off_out == offset2_out &&361in.body.copy_file_range.len == copysize362);363}, Eq(true)),364_)365).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {366SET_OUT_HEADER_LEN(out, write);367out.body.write.size = copysize;368})));369370fd = open(FULLPATH, O_RDWR);371372/* First, write some data via mmap */373p = mmap(NULL, wsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);374ASSERT_NE(MAP_FAILED, p) << strerror(errno);375memmove((uint8_t*)p, wbuf, wsize);376ASSERT_EQ(0, munmap(p, wsize)) << strerror(errno);377378/*379* Then copy it around the file via copy_file_range. This should380* trigger a FUSE_WRITE to flush the pages written by mmap.381*/382r = copy_file_range(fd, &offset2_in, fd, &offset2_out, copysize, 0);383ASSERT_EQ(copysize, (size_t)r) << strerror(errno);384385delete[] wbuf;386delete[] fbuf;387}388389390/*391* copy_file_range should send SIGXFSZ and return EFBIG when the operation392* would exceed the limit imposed by RLIMIT_FSIZE.393*/394TEST_F(CopyFileRangeRlimitFsize, signal)395{396const char FULLPATH1[] = "mountpoint/src.txt";397const char RELPATH1[] = "src.txt";398const char FULLPATH2[] = "mountpoint/dst.txt";399const char RELPATH2[] = "dst.txt";400struct rlimit rl;401const uint64_t ino1 = 42;402const uint64_t ino2 = 43;403const uint64_t fh1 = 0xdeadbeef1a7ebabe;404const uint64_t fh2 = 0xdeadc0de88c0ffee;405off_t fsize1 = 1 << 20; /* 1 MiB */406off_t fsize2 = 1 << 19; /* 512 KiB */407off_t start1 = 1 << 18;408off_t start2 = fsize2;409ssize_t len = 65536;410int fd1, fd2;411412expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);413expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);414expect_open(ino1, 0, 1, fh1);415expect_open(ino2, 0, 1, fh2);416EXPECT_CALL(*m_mock, process(417ResultOf([=](auto in) {418return (in.header.opcode == FUSE_COPY_FILE_RANGE);419}, Eq(true)),420_)421).Times(0);422423rl.rlim_cur = fsize2;424rl.rlim_max = m_initial_limit.rlim_max;425ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);426ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);427428fd1 = open(FULLPATH1, O_RDONLY);429fd2 = open(FULLPATH2, O_WRONLY);430ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));431EXPECT_EQ(EFBIG, errno);432EXPECT_EQ(1, s_sigxfsz);433}434435/*436* When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not437* aborted.438* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611439*/440TEST_F(CopyFileRangeRlimitFsize, truncate)441{442const char FULLPATH1[] = "mountpoint/src.txt";443const char RELPATH1[] = "src.txt";444const char FULLPATH2[] = "mountpoint/dst.txt";445const char RELPATH2[] = "dst.txt";446struct rlimit rl;447const uint64_t ino1 = 42;448const uint64_t ino2 = 43;449const uint64_t fh1 = 0xdeadbeef1a7ebabe;450const uint64_t fh2 = 0xdeadc0de88c0ffee;451off_t fsize1 = 1 << 20; /* 1 MiB */452off_t fsize2 = 1 << 19; /* 512 KiB */453off_t start1 = 1 << 18;454off_t start2 = fsize2;455ssize_t len = 65536;456off_t limit = start2 + len / 2;457int fd1, fd2;458459expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);460expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);461expect_open(ino1, 0, 1, fh1);462expect_open(ino2, 0, 1, fh2);463EXPECT_CALL(*m_mock, process(464ResultOf([=](auto in) {465return (in.header.opcode == FUSE_COPY_FILE_RANGE &&466(off_t)in.body.copy_file_range.off_out == start2 &&467in.body.copy_file_range.len == (size_t)len / 2468);469}, Eq(true)),470_)471).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {472SET_OUT_HEADER_LEN(out, write);473out.body.write.size = len / 2;474})));475476rl.rlim_cur = limit;477rl.rlim_max = m_initial_limit.rlim_max;478ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);479ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);480481fd1 = open(FULLPATH1, O_RDONLY);482fd2 = open(FULLPATH2, O_WRONLY);483ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));484}485486TEST_F(CopyFileRange, ok)487{488const char FULLPATH1[] = "mountpoint/src.txt";489const char RELPATH1[] = "src.txt";490const char FULLPATH2[] = "mountpoint/dst.txt";491const char RELPATH2[] = "dst.txt";492const uint64_t ino1 = 42;493const uint64_t ino2 = 43;494const uint64_t fh1 = 0xdeadbeef1a7ebabe;495const uint64_t fh2 = 0xdeadc0de88c0ffee;496off_t fsize1 = 1 << 20; /* 1 MiB */497off_t fsize2 = 1 << 19; /* 512 KiB */498off_t start1 = 1 << 18;499off_t start2 = 3 << 17;500ssize_t len = 65536;501int fd1, fd2;502503expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);504expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);505expect_open(ino1, 0, 1, fh1);506expect_open(ino2, 0, 1, fh2);507EXPECT_CALL(*m_mock, process(508ResultOf([=](auto in) {509return (in.header.opcode == FUSE_COPY_FILE_RANGE &&510in.header.nodeid == ino1 &&511in.body.copy_file_range.fh_in == fh1 &&512(off_t)in.body.copy_file_range.off_in == start1 &&513in.body.copy_file_range.nodeid_out == ino2 &&514in.body.copy_file_range.fh_out == fh2 &&515(off_t)in.body.copy_file_range.off_out == start2 &&516in.body.copy_file_range.len == (size_t)len &&517in.body.copy_file_range.flags == 0);518}, Eq(true)),519_)520).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {521SET_OUT_HEADER_LEN(out, write);522out.body.write.size = len;523})));524525fd1 = open(FULLPATH1, O_RDONLY);526fd2 = open(FULLPATH2, O_WRONLY);527ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));528}529530/*531* copy_file_range can make copies within a single file, as long as the ranges532* don't overlap.533* */534TEST_F(CopyFileRange, same_file)535{536const char FULLPATH[] = "mountpoint/src.txt";537const char RELPATH[] = "src.txt";538const uint64_t ino = 4;539const uint64_t fh = 0xdeadbeefa7ebabe;540off_t fsize = 1 << 20; /* 1 MiB */541off_t off_in = 1 << 18;542off_t off_out = 3 << 17;543ssize_t len = 65536;544int fd;545546expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);547expect_open(ino, 0, 1, fh);548EXPECT_CALL(*m_mock, process(549ResultOf([=](auto in) {550return (in.header.opcode == FUSE_COPY_FILE_RANGE &&551in.header.nodeid == ino &&552in.body.copy_file_range.fh_in == fh &&553(off_t)in.body.copy_file_range.off_in == off_in &&554in.body.copy_file_range.nodeid_out == ino &&555in.body.copy_file_range.fh_out == fh &&556(off_t)in.body.copy_file_range.off_out == off_out &&557in.body.copy_file_range.len == (size_t)len &&558in.body.copy_file_range.flags == 0);559}, Eq(true)),560_)561).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {562SET_OUT_HEADER_LEN(out, write);563out.body.write.size = len;564})));565566fd = open(FULLPATH, O_RDWR);567ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));568569leak(fd);570}571572/*573* copy_file_range should update the destination's mtime and ctime, and574* the source's atime.575*/576TEST_F(CopyFileRange, timestamps)577{578const char FULLPATH1[] = "mountpoint/src.txt";579const char RELPATH1[] = "src.txt";580const char FULLPATH2[] = "mountpoint/dst.txt";581const char RELPATH2[] = "dst.txt";582struct stat sb1a, sb1b, sb2a, sb2b;583const uint64_t ino1 = 42;584const uint64_t ino2 = 43;585const uint64_t fh1 = 0xdeadbeef1a7ebabe;586const uint64_t fh2 = 0xdeadc0de88c0ffee;587off_t fsize1 = 1 << 20; /* 1 MiB */588off_t fsize2 = 1 << 19; /* 512 KiB */589off_t start1 = 1 << 18;590off_t start2 = 3 << 17;591ssize_t len = 65536;592int fd1, fd2;593594expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);595expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);596expect_open(ino1, 0, 1, fh1);597expect_open(ino2, 0, 1, fh2);598EXPECT_CALL(*m_mock, process(599ResultOf([=](auto in) {600return (in.header.opcode == FUSE_COPY_FILE_RANGE &&601in.header.nodeid == ino1 &&602in.body.copy_file_range.fh_in == fh1 &&603(off_t)in.body.copy_file_range.off_in == start1 &&604in.body.copy_file_range.nodeid_out == ino2 &&605in.body.copy_file_range.fh_out == fh2 &&606(off_t)in.body.copy_file_range.off_out == start2 &&607in.body.copy_file_range.len == (size_t)len &&608in.body.copy_file_range.flags == 0);609}, Eq(true)),610_)611).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {612SET_OUT_HEADER_LEN(out, write);613out.body.write.size = len;614})));615616fd1 = open(FULLPATH1, O_RDONLY);617ASSERT_GE(fd1, 0);618fd2 = open(FULLPATH2, O_WRONLY);619ASSERT_GE(fd2, 0);620ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);621ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);622623nap();624625ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));626ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);627ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);628629EXPECT_NE(sb1a.st_atime, sb1b.st_atime);630EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);631EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);632EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);633EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);634EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);635636leak(fd1);637leak(fd2);638}639640/*641* copy_file_range can extend the size of a file642* */643TEST_F(CopyFileRange, extend)644{645const char FULLPATH[] = "mountpoint/src.txt";646const char RELPATH[] = "src.txt";647struct stat sb;648const uint64_t ino = 4;649const uint64_t fh = 0xdeadbeefa7ebabe;650off_t fsize = 65536;651off_t off_in = 0;652off_t off_out = 65536;653ssize_t len = 65536;654int fd;655656expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);657expect_open(ino, 0, 1, fh);658EXPECT_CALL(*m_mock, process(659ResultOf([=](auto in) {660return (in.header.opcode == FUSE_COPY_FILE_RANGE &&661in.header.nodeid == ino &&662in.body.copy_file_range.fh_in == fh &&663(off_t)in.body.copy_file_range.off_in == off_in &&664in.body.copy_file_range.nodeid_out == ino &&665in.body.copy_file_range.fh_out == fh &&666(off_t)in.body.copy_file_range.off_out == off_out &&667in.body.copy_file_range.len == (size_t)len &&668in.body.copy_file_range.flags == 0);669}, Eq(true)),670_)671).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {672SET_OUT_HEADER_LEN(out, write);673out.body.write.size = len;674})));675676fd = open(FULLPATH, O_RDWR);677ASSERT_GE(fd, 0);678ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));679680/* Check that cached attributes were updated appropriately */681ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);682EXPECT_EQ(fsize + len, sb.st_size);683684leak(fd);685}686687/* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */688TEST_F(CopyFileRange_7_27, fallback)689{690const char FULLPATH1[] = "mountpoint/src.txt";691const char RELPATH1[] = "src.txt";692const char FULLPATH2[] = "mountpoint/dst.txt";693const char RELPATH2[] = "dst.txt";694const uint64_t ino1 = 42;695const uint64_t ino2 = 43;696const uint64_t fh1 = 0xdeadbeef1a7ebabe;697const uint64_t fh2 = 0xdeadc0de88c0ffee;698off_t fsize2 = 0;699off_t start1 = 0;700off_t start2 = 0;701const char *contents = "Hello, world!";702ssize_t len;703int fd1, fd2;704705len = strlen(contents);706707/*708* Ensure that we read to EOF, just so the buffer cache's read size is709* predictable.710*/711expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);712expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);713expect_open(ino1, 0, 1, fh1);714expect_open(ino2, 0, 1, fh2);715EXPECT_CALL(*m_mock, process(716ResultOf([=](auto in) {717return (in.header.opcode == FUSE_COPY_FILE_RANGE);718}, Eq(true)),719_)720).Times(0);721expect_maybe_lseek(ino1);722expect_read(ino1, start1, len, len, contents, 0);723expect_write(ino2, start2, len, len, contents);724725fd1 = open(FULLPATH1, O_RDONLY);726ASSERT_GE(fd1, 0);727fd2 = open(FULLPATH2, O_WRONLY);728ASSERT_GE(fd2, 0);729ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));730731leak(fd1);732leak(fd2);733}734735/*736* With -o noatime, copy_file_range should update the destination's mtime and737* ctime, but not the source's atime.738*/739TEST_F(CopyFileRangeNoAtime, timestamps)740{741const char FULLPATH1[] = "mountpoint/src.txt";742const char RELPATH1[] = "src.txt";743const char FULLPATH2[] = "mountpoint/dst.txt";744const char RELPATH2[] = "dst.txt";745struct stat sb1a, sb1b, sb2a, sb2b;746const uint64_t ino1 = 42;747const uint64_t ino2 = 43;748const uint64_t fh1 = 0xdeadbeef1a7ebabe;749const uint64_t fh2 = 0xdeadc0de88c0ffee;750off_t fsize1 = 1 << 20; /* 1 MiB */751off_t fsize2 = 1 << 19; /* 512 KiB */752off_t start1 = 1 << 18;753off_t start2 = 3 << 17;754ssize_t len = 65536;755int fd1, fd2;756757expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);758expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);759expect_open(ino1, 0, 1, fh1);760expect_open(ino2, 0, 1, fh2);761EXPECT_CALL(*m_mock, process(762ResultOf([=](auto in) {763return (in.header.opcode == FUSE_COPY_FILE_RANGE &&764in.header.nodeid == ino1 &&765in.body.copy_file_range.fh_in == fh1 &&766(off_t)in.body.copy_file_range.off_in == start1 &&767in.body.copy_file_range.nodeid_out == ino2 &&768in.body.copy_file_range.fh_out == fh2 &&769(off_t)in.body.copy_file_range.off_out == start2 &&770in.body.copy_file_range.len == (size_t)len &&771in.body.copy_file_range.flags == 0);772}, Eq(true)),773_)774).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {775SET_OUT_HEADER_LEN(out, write);776out.body.write.size = len;777})));778779fd1 = open(FULLPATH1, O_RDONLY);780ASSERT_GE(fd1, 0);781fd2 = open(FULLPATH2, O_WRONLY);782ASSERT_GE(fd2, 0);783ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);784ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);785786nap();787788ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));789ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);790ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);791792EXPECT_EQ(sb1a.st_atime, sb1b.st_atime);793EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);794EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);795EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);796EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);797EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);798799leak(fd1);800leak(fd2);801}802803804