xref: /freebsd/tests/sys/fs/fusefs/copy_file_range.cc (revision 8bae22bb)
192bbfe1fSAlan Somers /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
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 
2892bbfe1fSAlan Somers extern "C" {
2992bbfe1fSAlan Somers #include <sys/param.h>
301c909c30SAlan Somers #include <sys/mman.h>
3192bbfe1fSAlan Somers #include <sys/time.h>
3292bbfe1fSAlan Somers #include <sys/resource.h>
3392bbfe1fSAlan Somers 
3492bbfe1fSAlan Somers #include <fcntl.h>
3592bbfe1fSAlan Somers #include <signal.h>
3692bbfe1fSAlan Somers #include <unistd.h>
3792bbfe1fSAlan Somers }
3892bbfe1fSAlan Somers 
3992bbfe1fSAlan Somers #include "mockfs.hh"
4092bbfe1fSAlan Somers #include "utils.hh"
4192bbfe1fSAlan Somers 
4292bbfe1fSAlan Somers using namespace testing;
4392bbfe1fSAlan Somers 
4492bbfe1fSAlan Somers class CopyFileRange: public FuseTest {
4592bbfe1fSAlan Somers public:
4692bbfe1fSAlan Somers 
expect_maybe_lseek(uint64_t ino)4792bbfe1fSAlan Somers void expect_maybe_lseek(uint64_t ino)
4892bbfe1fSAlan Somers {
4992bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
5092bbfe1fSAlan Somers 		ResultOf([=](auto in) {
5192bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_LSEEK &&
5292bbfe1fSAlan Somers 				in.header.nodeid == ino);
5392bbfe1fSAlan Somers 		}, Eq(true)),
5492bbfe1fSAlan Somers 		_)
5592bbfe1fSAlan Somers 	).Times(AtMost(1))
5692bbfe1fSAlan Somers 	.WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
5792bbfe1fSAlan Somers }
5892bbfe1fSAlan Somers 
expect_open(uint64_t ino,uint32_t flags,int times,uint64_t fh)5992bbfe1fSAlan Somers void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
6092bbfe1fSAlan Somers {
6192bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
6292bbfe1fSAlan Somers 		ResultOf([=](auto in) {
6392bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
6492bbfe1fSAlan Somers 				in.header.nodeid == ino);
6592bbfe1fSAlan Somers 		}, Eq(true)),
6692bbfe1fSAlan Somers 		_)
6792bbfe1fSAlan Somers 	).Times(times)
6892bbfe1fSAlan Somers 	.WillRepeatedly(Invoke(
6992bbfe1fSAlan Somers 		ReturnImmediate([=](auto in __unused, auto& out) {
7092bbfe1fSAlan Somers 		out.header.len = sizeof(out.header);
7192bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, open);
7292bbfe1fSAlan Somers 		out.body.open.fh = fh;
7392bbfe1fSAlan Somers 		out.body.open.open_flags = flags;
7492bbfe1fSAlan Somers 	})));
7592bbfe1fSAlan Somers }
7692bbfe1fSAlan Somers 
expect_write(uint64_t ino,uint64_t offset,uint64_t isize,uint64_t osize,const void * contents)7792bbfe1fSAlan Somers void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
7892bbfe1fSAlan Somers 	uint64_t osize, const void *contents)
7992bbfe1fSAlan Somers {
8092bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
8192bbfe1fSAlan Somers 		ResultOf([=](auto in) {
8292bbfe1fSAlan Somers 			const char *buf = (const char*)in.body.bytes +
8392bbfe1fSAlan Somers 				sizeof(struct fuse_write_in);
8492bbfe1fSAlan Somers 
8592bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_WRITE &&
8692bbfe1fSAlan Somers 				in.header.nodeid == ino &&
8792bbfe1fSAlan Somers 				in.body.write.offset == offset  &&
8892bbfe1fSAlan Somers 				in.body.write.size == isize &&
8992bbfe1fSAlan Somers 				0 == bcmp(buf, contents, isize));
9092bbfe1fSAlan Somers 		}, Eq(true)),
9192bbfe1fSAlan Somers 		_)
9292bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
9392bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
9492bbfe1fSAlan Somers 		out.body.write.size = osize;
9592bbfe1fSAlan Somers 	})));
9692bbfe1fSAlan Somers }
9792bbfe1fSAlan Somers 
9892bbfe1fSAlan Somers };
9992bbfe1fSAlan Somers 
10092bbfe1fSAlan Somers 
10192bbfe1fSAlan Somers class CopyFileRange_7_27: public CopyFileRange {
10292bbfe1fSAlan Somers public:
SetUp()10392bbfe1fSAlan Somers virtual void SetUp() {
10492bbfe1fSAlan Somers 	m_kernel_minor_version = 27;
10592bbfe1fSAlan Somers 	CopyFileRange::SetUp();
10692bbfe1fSAlan Somers }
10792bbfe1fSAlan Somers };
10892bbfe1fSAlan Somers 
1095169832cSAlan Somers class CopyFileRangeNoAtime: public CopyFileRange {
1105169832cSAlan Somers public:
SetUp()1115169832cSAlan Somers virtual void SetUp() {
1125169832cSAlan Somers 	m_noatime = true;
1135169832cSAlan Somers 	CopyFileRange::SetUp();
1145169832cSAlan Somers }
1155169832cSAlan Somers };
1165169832cSAlan Somers 
11752360ca3SAlan Somers class CopyFileRangeRlimitFsize: public CopyFileRange {
11852360ca3SAlan Somers public:
11952360ca3SAlan Somers static sig_atomic_t s_sigxfsz;
12052360ca3SAlan Somers struct rlimit	m_initial_limit;
12152360ca3SAlan Somers 
SetUp()12252360ca3SAlan Somers virtual void SetUp() {
12352360ca3SAlan Somers 	s_sigxfsz = 0;
12452360ca3SAlan Somers 	getrlimit(RLIMIT_FSIZE, &m_initial_limit);
12552360ca3SAlan Somers 	CopyFileRange::SetUp();
12652360ca3SAlan Somers }
12752360ca3SAlan Somers 
TearDown()12852360ca3SAlan Somers void TearDown() {
12952360ca3SAlan Somers 	struct sigaction sa;
13052360ca3SAlan Somers 
13152360ca3SAlan Somers 	setrlimit(RLIMIT_FSIZE, &m_initial_limit);
13252360ca3SAlan Somers 
13352360ca3SAlan Somers 	bzero(&sa, sizeof(sa));
13452360ca3SAlan Somers 	sa.sa_handler = SIG_DFL;
13552360ca3SAlan Somers 	sigaction(SIGXFSZ, &sa, NULL);
13652360ca3SAlan Somers 
13752360ca3SAlan Somers 	FuseTest::TearDown();
13852360ca3SAlan Somers }
13952360ca3SAlan Somers 
14052360ca3SAlan Somers };
14152360ca3SAlan Somers 
14252360ca3SAlan Somers sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
14352360ca3SAlan Somers 
sigxfsz_handler(int __unused sig)14452360ca3SAlan Somers void sigxfsz_handler(int __unused sig) {
14552360ca3SAlan Somers 	CopyFileRangeRlimitFsize::s_sigxfsz = 1;
14652360ca3SAlan Somers }
14752360ca3SAlan Somers 
TEST_F(CopyFileRange,eio)14892bbfe1fSAlan Somers TEST_F(CopyFileRange, eio)
14992bbfe1fSAlan Somers {
15092bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
15192bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
15292bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
15392bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
15492bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
15592bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
15692bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
15792bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
15892bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
15992bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
16092bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
16192bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
16292bbfe1fSAlan Somers 	ssize_t len = 65536;
16392bbfe1fSAlan Somers 	int fd1, fd2;
16492bbfe1fSAlan Somers 
16592bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
16692bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
16792bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
16892bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
16992bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
17092bbfe1fSAlan Somers 		ResultOf([=](auto in) {
17192bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
17292bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
17392bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
17492bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
17592bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
17692bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
17792bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
17892bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
17992bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
18092bbfe1fSAlan Somers 		}, Eq(true)),
18192bbfe1fSAlan Somers 		_)
18292bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(EIO)));
18392bbfe1fSAlan Somers 
18492bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
18592bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
18692bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
18792bbfe1fSAlan Somers 	EXPECT_EQ(EIO, errno);
18892bbfe1fSAlan Somers }
18992bbfe1fSAlan Somers 
19092bbfe1fSAlan Somers /*
19141ae9f9eSAlan Somers  * copy_file_range should evict cached data for the modified region of the
19241ae9f9eSAlan Somers  * destination file.
19341ae9f9eSAlan Somers  */
TEST_F(CopyFileRange,evicts_cache)19441ae9f9eSAlan Somers TEST_F(CopyFileRange, evicts_cache)
19541ae9f9eSAlan Somers {
19641ae9f9eSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
19741ae9f9eSAlan Somers 	const char RELPATH1[] = "src.txt";
19841ae9f9eSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
19941ae9f9eSAlan Somers 	const char RELPATH2[] = "dst.txt";
2008bae22bbSAlan Somers 	char *buf0, *buf1, *buf;
20141ae9f9eSAlan Somers 	const uint64_t ino1 = 42;
20241ae9f9eSAlan Somers 	const uint64_t ino2 = 43;
20341ae9f9eSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
20441ae9f9eSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
20541ae9f9eSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
20641ae9f9eSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
20741ae9f9eSAlan Somers 	off_t start1 = 1 << 18;
20841ae9f9eSAlan Somers 	off_t start2 = 3 << 17;
20941ae9f9eSAlan Somers 	ssize_t len = m_maxbcachebuf;
21041ae9f9eSAlan Somers 	int fd1, fd2;
21141ae9f9eSAlan Somers 
2128bae22bbSAlan Somers 	buf0 = new char[m_maxbcachebuf];
21341ae9f9eSAlan Somers 	memset(buf0, 42, m_maxbcachebuf);
21441ae9f9eSAlan Somers 
21541ae9f9eSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
21641ae9f9eSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
21741ae9f9eSAlan Somers 	expect_open(ino1, 0, 1, fh1);
21841ae9f9eSAlan Somers 	expect_open(ino2, 0, 1, fh2);
21941ae9f9eSAlan Somers 	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1,
22041ae9f9eSAlan Somers 		fh2);
22141ae9f9eSAlan Somers 	EXPECT_CALL(*m_mock, process(
22241ae9f9eSAlan Somers 		ResultOf([=](auto in) {
22341ae9f9eSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
22441ae9f9eSAlan Somers 				in.header.nodeid == ino1 &&
22541ae9f9eSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
22641ae9f9eSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
22741ae9f9eSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
22841ae9f9eSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
22941ae9f9eSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
23041ae9f9eSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
23141ae9f9eSAlan Somers 				in.body.copy_file_range.flags == 0);
23241ae9f9eSAlan Somers 		}, Eq(true)),
23341ae9f9eSAlan Somers 		_)
23441ae9f9eSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
23541ae9f9eSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
23641ae9f9eSAlan Somers 		out.body.write.size = len;
23741ae9f9eSAlan Somers 	})));
23841ae9f9eSAlan Somers 
23941ae9f9eSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
24041ae9f9eSAlan Somers 	fd2 = open(FULLPATH2, O_RDWR);
24141ae9f9eSAlan Somers 
24241ae9f9eSAlan Somers 	// Prime cache
2438bae22bbSAlan Somers 	buf = new char[m_maxbcachebuf];
24441ae9f9eSAlan Somers 	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
24541ae9f9eSAlan Somers 		<< strerror(errno);
24641ae9f9eSAlan Somers 	EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
24741ae9f9eSAlan Somers 
24841ae9f9eSAlan Somers 	// Tell the FUSE server overwrite the region we just read
24941ae9f9eSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
25041ae9f9eSAlan Somers 
25141ae9f9eSAlan Somers 	// Read again.  This should bypass the cache and read direct from server
2528bae22bbSAlan Somers 	buf1 = new char[m_maxbcachebuf];
25341ae9f9eSAlan Somers 	memset(buf1, 69, m_maxbcachebuf);
25441ae9f9eSAlan Somers 	start2 -= len;
25541ae9f9eSAlan Somers 	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
25641ae9f9eSAlan Somers 		fh2);
25741ae9f9eSAlan Somers 	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
25841ae9f9eSAlan Somers 		<< strerror(errno);
25941ae9f9eSAlan Somers 	EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
26041ae9f9eSAlan Somers 
2618bae22bbSAlan Somers 	delete[] buf1;
2628bae22bbSAlan Somers 	delete[] buf0;
2638bae22bbSAlan Somers 	delete[] buf;
26441ae9f9eSAlan Somers 	leak(fd1);
26541ae9f9eSAlan Somers 	leak(fd2);
26641ae9f9eSAlan Somers }
26741ae9f9eSAlan Somers 
26841ae9f9eSAlan Somers /*
26992bbfe1fSAlan Somers  * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
27092bbfe1fSAlan Somers  * fallback to a read/write based implementation.
27192bbfe1fSAlan Somers  */
TEST_F(CopyFileRange,fallback)27292bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback)
27392bbfe1fSAlan Somers {
27492bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
27592bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
27692bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
27792bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
27892bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
27992bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
28092bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
28192bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
28292bbfe1fSAlan Somers 	off_t fsize2 = 0;
28392bbfe1fSAlan Somers 	off_t start1 = 0;
28492bbfe1fSAlan Somers 	off_t start2 = 0;
28592bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
28692bbfe1fSAlan Somers 	ssize_t len;
28792bbfe1fSAlan Somers 	int fd1, fd2;
28892bbfe1fSAlan Somers 
28992bbfe1fSAlan Somers 	len = strlen(contents);
29092bbfe1fSAlan Somers 
29192bbfe1fSAlan Somers 	/*
29292bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
29392bbfe1fSAlan Somers 	 * predictable.
29492bbfe1fSAlan Somers 	 */
29592bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
29692bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
29792bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
29892bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
29992bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
30092bbfe1fSAlan Somers 		ResultOf([=](auto in) {
30192bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
30292bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
30392bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
30492bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
30592bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
30692bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
30792bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
30892bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
30992bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
31092bbfe1fSAlan Somers 		}, Eq(true)),
31192bbfe1fSAlan Somers 		_)
31292bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
31392bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
31492bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
31592bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
31692bbfe1fSAlan Somers 
31792bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
31892bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
31992bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
32092bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
32192bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
32292bbfe1fSAlan Somers }
32392bbfe1fSAlan Somers 
32452360ca3SAlan Somers /*
3251c909c30SAlan Somers  * Writes via mmap should not conflict with using copy_file_range.  Any dirty
3261c909c30SAlan Somers  * pages that overlap with copy_file_range's input should be flushed before
3271c909c30SAlan Somers  * FUSE_COPY_FILE_RANGE is sent.
3281c909c30SAlan Somers  */
TEST_F(CopyFileRange,mmap_write)3291c909c30SAlan Somers TEST_F(CopyFileRange, mmap_write)
3301c909c30SAlan Somers {
3311c909c30SAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
3321c909c30SAlan Somers 	const char RELPATH[] = "src.txt";
3331c909c30SAlan Somers 	uint8_t *wbuf, *fbuf;
3341c909c30SAlan Somers 	void *p;
3351c909c30SAlan Somers 	size_t fsize = 0x6000;
3361c909c30SAlan Somers 	size_t wsize = 0x3000;
3371c909c30SAlan Somers 	ssize_t r;
3381c909c30SAlan Somers 	off_t offset2_in = 0;
3391c909c30SAlan Somers 	off_t offset2_out = wsize;
3401c909c30SAlan Somers 	size_t copysize = wsize;
3411c909c30SAlan Somers 	const uint64_t ino = 42;
3421c909c30SAlan Somers 	const uint64_t fh = 0xdeadbeef1a7ebabe;
3431c909c30SAlan Somers 	int fd;
3441c909c30SAlan Somers 	const mode_t mode = 0644;
3451c909c30SAlan Somers 
3468bae22bbSAlan Somers 	fbuf = new uint8_t[fsize]();
3478bae22bbSAlan Somers 	wbuf = new uint8_t[wsize];
3481c909c30SAlan Somers 	memset(wbuf, 1, wsize);
3491c909c30SAlan Somers 
3501c909c30SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, fsize, 1);
3511c909c30SAlan Somers 	expect_open(ino, 0, 1, fh);
3521c909c30SAlan Somers 	/* This read is initiated by the mmap write */
3531c909c30SAlan Somers 	expect_read(ino, 0, fsize, fsize, fbuf, -1, fh);
3541c909c30SAlan Somers 	/* This write flushes the buffer filled by the mmap write */
3551c909c30SAlan Somers 	expect_write(ino, 0, wsize, wsize, wbuf);
3561c909c30SAlan Somers 
3571c909c30SAlan Somers 	EXPECT_CALL(*m_mock, process(
3581c909c30SAlan Somers 		ResultOf([=](auto in) {
3591c909c30SAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
3601c909c30SAlan Somers 				(off_t)in.body.copy_file_range.off_in == offset2_in &&
3611c909c30SAlan Somers 				(off_t)in.body.copy_file_range.off_out == offset2_out &&
3621c909c30SAlan Somers 				in.body.copy_file_range.len == copysize
3631c909c30SAlan Somers 			);
3641c909c30SAlan Somers 		}, Eq(true)),
3651c909c30SAlan Somers 		_)
3661c909c30SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
3671c909c30SAlan Somers 		SET_OUT_HEADER_LEN(out, write);
3681c909c30SAlan Somers 		out.body.write.size = copysize;
3691c909c30SAlan Somers 	})));
3701c909c30SAlan Somers 
3711c909c30SAlan Somers 	fd = open(FULLPATH, O_RDWR);
3721c909c30SAlan Somers 
3731c909c30SAlan Somers 	/* First, write some data via mmap */
3741c909c30SAlan Somers 	p = mmap(NULL, wsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
3751c909c30SAlan Somers 	ASSERT_NE(MAP_FAILED, p) << strerror(errno);
3761c909c30SAlan Somers 	memmove((uint8_t*)p, wbuf, wsize);
3771c909c30SAlan Somers 	ASSERT_EQ(0, munmap(p, wsize)) << strerror(errno);
3781c909c30SAlan Somers 
3791c909c30SAlan Somers 	/*
3801c909c30SAlan Somers 	 * Then copy it around the file via copy_file_range.  This should
3811c909c30SAlan Somers 	 * trigger a FUSE_WRITE to flush the pages written by mmap.
3821c909c30SAlan Somers 	 */
3831c909c30SAlan Somers 	r = copy_file_range(fd, &offset2_in, fd, &offset2_out, copysize, 0);
3841c909c30SAlan Somers 	ASSERT_EQ(copysize, (size_t)r) << strerror(errno);
3851c909c30SAlan Somers 
3868bae22bbSAlan Somers 	delete[] wbuf;
3878bae22bbSAlan Somers 	delete[] fbuf;
3881c909c30SAlan Somers }
3891c909c30SAlan Somers 
3901c909c30SAlan Somers 
3911c909c30SAlan Somers /*
39252360ca3SAlan Somers  * copy_file_range should send SIGXFSZ and return EFBIG when the operation
39352360ca3SAlan Somers  * would exceed the limit imposed by RLIMIT_FSIZE.
39452360ca3SAlan Somers  */
TEST_F(CopyFileRangeRlimitFsize,signal)39552360ca3SAlan Somers TEST_F(CopyFileRangeRlimitFsize, signal)
39692bbfe1fSAlan Somers {
39792bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
39892bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
39992bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
40092bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
40192bbfe1fSAlan Somers 	struct rlimit rl;
40292bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
40392bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
40492bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
40592bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
40692bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
40792bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
40892bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
40992bbfe1fSAlan Somers 	off_t start2 = fsize2;
41092bbfe1fSAlan Somers 	ssize_t len = 65536;
41192bbfe1fSAlan Somers 	int fd1, fd2;
41292bbfe1fSAlan Somers 
41392bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
41492bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
41592bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
41692bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
41792bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
41892bbfe1fSAlan Somers 		ResultOf([=](auto in) {
41992bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
42092bbfe1fSAlan Somers 		}, Eq(true)),
42192bbfe1fSAlan Somers 		_)
42292bbfe1fSAlan Somers 	).Times(0);
42392bbfe1fSAlan Somers 
42492bbfe1fSAlan Somers 	rl.rlim_cur = fsize2;
42552360ca3SAlan Somers 	rl.rlim_max = m_initial_limit.rlim_max;
42692bbfe1fSAlan Somers 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
42792bbfe1fSAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
42892bbfe1fSAlan Somers 
42992bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
43092bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
43192bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
43292bbfe1fSAlan Somers 	EXPECT_EQ(EFBIG, errno);
43392bbfe1fSAlan Somers 	EXPECT_EQ(1, s_sigxfsz);
43492bbfe1fSAlan Somers }
43592bbfe1fSAlan Somers 
43652360ca3SAlan Somers /*
43752360ca3SAlan Somers  * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
43852360ca3SAlan Somers  * aborted.
43952360ca3SAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
44052360ca3SAlan Somers  */
TEST_F(CopyFileRangeRlimitFsize,truncate)44152360ca3SAlan Somers TEST_F(CopyFileRangeRlimitFsize, truncate)
44252360ca3SAlan Somers {
44352360ca3SAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
44452360ca3SAlan Somers 	const char RELPATH1[] = "src.txt";
44552360ca3SAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
44652360ca3SAlan Somers 	const char RELPATH2[] = "dst.txt";
44752360ca3SAlan Somers 	struct rlimit rl;
44852360ca3SAlan Somers 	const uint64_t ino1 = 42;
44952360ca3SAlan Somers 	const uint64_t ino2 = 43;
45052360ca3SAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
45152360ca3SAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
45252360ca3SAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
45352360ca3SAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
45452360ca3SAlan Somers 	off_t start1 = 1 << 18;
45552360ca3SAlan Somers 	off_t start2 = fsize2;
45652360ca3SAlan Somers 	ssize_t len = 65536;
45752360ca3SAlan Somers 	off_t limit = start2 + len / 2;
45852360ca3SAlan Somers 	int fd1, fd2;
45952360ca3SAlan Somers 
46052360ca3SAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
46152360ca3SAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
46252360ca3SAlan Somers 	expect_open(ino1, 0, 1, fh1);
46352360ca3SAlan Somers 	expect_open(ino2, 0, 1, fh2);
46452360ca3SAlan Somers 	EXPECT_CALL(*m_mock, process(
46552360ca3SAlan Somers 		ResultOf([=](auto in) {
46652360ca3SAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
46752360ca3SAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
46852360ca3SAlan Somers 				in.body.copy_file_range.len == (size_t)len / 2
46952360ca3SAlan Somers 			);
47052360ca3SAlan Somers 		}, Eq(true)),
47152360ca3SAlan Somers 		_)
47252360ca3SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
47352360ca3SAlan Somers 		SET_OUT_HEADER_LEN(out, write);
47452360ca3SAlan Somers 		out.body.write.size = len / 2;
47552360ca3SAlan Somers 	})));
47652360ca3SAlan Somers 
47752360ca3SAlan Somers 	rl.rlim_cur = limit;
47852360ca3SAlan Somers 	rl.rlim_max = m_initial_limit.rlim_max;
47952360ca3SAlan Somers 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
48052360ca3SAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
48152360ca3SAlan Somers 
48252360ca3SAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
48352360ca3SAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
48452360ca3SAlan Somers 	ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
48552360ca3SAlan Somers }
48652360ca3SAlan Somers 
TEST_F(CopyFileRange,ok)48792bbfe1fSAlan Somers TEST_F(CopyFileRange, ok)
48892bbfe1fSAlan Somers {
48992bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
49092bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
49192bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
49292bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
49392bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
49492bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
49592bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
49692bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
49792bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
49892bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
49992bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
50092bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
50192bbfe1fSAlan Somers 	ssize_t len = 65536;
50292bbfe1fSAlan Somers 	int fd1, fd2;
50392bbfe1fSAlan Somers 
50492bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
50592bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
50692bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
50792bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
50892bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
50992bbfe1fSAlan Somers 		ResultOf([=](auto in) {
51092bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
51192bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
51292bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
51392bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
51492bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
51592bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
51692bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
51792bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
51892bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
51992bbfe1fSAlan Somers 		}, Eq(true)),
52092bbfe1fSAlan Somers 		_)
52192bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
52292bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
52392bbfe1fSAlan Somers 		out.body.write.size = len;
52492bbfe1fSAlan Somers 	})));
52592bbfe1fSAlan Somers 
52692bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
52792bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
52892bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
52992bbfe1fSAlan Somers }
53092bbfe1fSAlan Somers 
53192bbfe1fSAlan Somers /*
53292bbfe1fSAlan Somers  * copy_file_range can make copies within a single file, as long as the ranges
53392bbfe1fSAlan Somers  * don't overlap.
53492bbfe1fSAlan Somers  * */
TEST_F(CopyFileRange,same_file)53592bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file)
53692bbfe1fSAlan Somers {
53792bbfe1fSAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
53892bbfe1fSAlan Somers 	const char RELPATH[] = "src.txt";
53992bbfe1fSAlan Somers 	const uint64_t ino = 4;
54092bbfe1fSAlan Somers 	const uint64_t fh = 0xdeadbeefa7ebabe;
54192bbfe1fSAlan Somers 	off_t fsize = 1 << 20;		/* 1 MiB */
54292bbfe1fSAlan Somers 	off_t off_in = 1 << 18;
54392bbfe1fSAlan Somers 	off_t off_out = 3 << 17;
54492bbfe1fSAlan Somers 	ssize_t len = 65536;
54592bbfe1fSAlan Somers 	int fd;
54692bbfe1fSAlan Somers 
54792bbfe1fSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
54892bbfe1fSAlan Somers 	expect_open(ino, 0, 1, fh);
54992bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
55092bbfe1fSAlan Somers 		ResultOf([=](auto in) {
55192bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
55292bbfe1fSAlan Somers 				in.header.nodeid == ino &&
55392bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh &&
55492bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == off_in &&
55592bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino &&
55692bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh &&
55792bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == off_out &&
55892bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
55992bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
56092bbfe1fSAlan Somers 		}, Eq(true)),
56192bbfe1fSAlan Somers 		_)
56292bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
56392bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
56492bbfe1fSAlan Somers 		out.body.write.size = len;
56592bbfe1fSAlan Somers 	})));
56692bbfe1fSAlan Somers 
56792bbfe1fSAlan Somers 	fd = open(FULLPATH, O_RDWR);
56892bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
5694ac4b126SAlan Somers 
5704ac4b126SAlan Somers 	leak(fd);
57192bbfe1fSAlan Somers }
57292bbfe1fSAlan Somers 
57365d70b3bSAlan Somers /*
5745169832cSAlan Somers  * copy_file_range should update the destination's mtime and ctime, and
5755169832cSAlan Somers  * the source's atime.
5765169832cSAlan Somers  */
TEST_F(CopyFileRange,timestamps)5775169832cSAlan Somers TEST_F(CopyFileRange, timestamps)
5785169832cSAlan Somers {
5795169832cSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
5805169832cSAlan Somers 	const char RELPATH1[] = "src.txt";
5815169832cSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
5825169832cSAlan Somers 	const char RELPATH2[] = "dst.txt";
5835169832cSAlan Somers 	struct stat sb1a, sb1b, sb2a, sb2b;
5845169832cSAlan Somers 	const uint64_t ino1 = 42;
5855169832cSAlan Somers 	const uint64_t ino2 = 43;
5865169832cSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
5875169832cSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
5885169832cSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
5895169832cSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
5905169832cSAlan Somers 	off_t start1 = 1 << 18;
5915169832cSAlan Somers 	off_t start2 = 3 << 17;
5925169832cSAlan Somers 	ssize_t len = 65536;
5935169832cSAlan Somers 	int fd1, fd2;
5945169832cSAlan Somers 
5955169832cSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
5965169832cSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
5975169832cSAlan Somers 	expect_open(ino1, 0, 1, fh1);
5985169832cSAlan Somers 	expect_open(ino2, 0, 1, fh2);
5995169832cSAlan Somers 	EXPECT_CALL(*m_mock, process(
6005169832cSAlan Somers 		ResultOf([=](auto in) {
6015169832cSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
6025169832cSAlan Somers 				in.header.nodeid == ino1 &&
6035169832cSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
6045169832cSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
6055169832cSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
6065169832cSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
6075169832cSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
6085169832cSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
6095169832cSAlan Somers 				in.body.copy_file_range.flags == 0);
6105169832cSAlan Somers 		}, Eq(true)),
6115169832cSAlan Somers 		_)
6125169832cSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6135169832cSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
6145169832cSAlan Somers 		out.body.write.size = len;
6155169832cSAlan Somers 	})));
6165169832cSAlan Somers 
6175169832cSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
6185169832cSAlan Somers 	ASSERT_GE(fd1, 0);
6195169832cSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
6205169832cSAlan Somers 	ASSERT_GE(fd2, 0);
6215169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
6225169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
6235169832cSAlan Somers 
6245169832cSAlan Somers 	nap();
6255169832cSAlan Somers 
6265169832cSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
6275169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
6285169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
6295169832cSAlan Somers 
6305169832cSAlan Somers 	EXPECT_NE(sb1a.st_atime, sb1b.st_atime);
6315169832cSAlan Somers 	EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
6325169832cSAlan Somers 	EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
6335169832cSAlan Somers 	EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
6345169832cSAlan Somers 	EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
6355169832cSAlan Somers 	EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
6365169832cSAlan Somers 
6375169832cSAlan Somers 	leak(fd1);
6385169832cSAlan Somers 	leak(fd2);
6395169832cSAlan Somers }
6405169832cSAlan Somers 
6415169832cSAlan Somers /*
64265d70b3bSAlan Somers  * copy_file_range can extend the size of a file
64365d70b3bSAlan Somers  * */
TEST_F(CopyFileRange,extend)64465d70b3bSAlan Somers TEST_F(CopyFileRange, extend)
64565d70b3bSAlan Somers {
64665d70b3bSAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
64765d70b3bSAlan Somers 	const char RELPATH[] = "src.txt";
64865d70b3bSAlan Somers 	struct stat sb;
64965d70b3bSAlan Somers 	const uint64_t ino = 4;
65065d70b3bSAlan Somers 	const uint64_t fh = 0xdeadbeefa7ebabe;
65165d70b3bSAlan Somers 	off_t fsize = 65536;
65265d70b3bSAlan Somers 	off_t off_in = 0;
65365d70b3bSAlan Somers 	off_t off_out = 65536;
65465d70b3bSAlan Somers 	ssize_t len = 65536;
65565d70b3bSAlan Somers 	int fd;
65665d70b3bSAlan Somers 
65765d70b3bSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
65865d70b3bSAlan Somers 	expect_open(ino, 0, 1, fh);
65965d70b3bSAlan Somers 	EXPECT_CALL(*m_mock, process(
66065d70b3bSAlan Somers 		ResultOf([=](auto in) {
66165d70b3bSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
66265d70b3bSAlan Somers 				in.header.nodeid == ino &&
66365d70b3bSAlan Somers 				in.body.copy_file_range.fh_in == fh &&
66465d70b3bSAlan Somers 				(off_t)in.body.copy_file_range.off_in == off_in &&
66565d70b3bSAlan Somers 				in.body.copy_file_range.nodeid_out == ino &&
66665d70b3bSAlan Somers 				in.body.copy_file_range.fh_out == fh &&
66765d70b3bSAlan Somers 				(off_t)in.body.copy_file_range.off_out == off_out &&
66865d70b3bSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
66965d70b3bSAlan Somers 				in.body.copy_file_range.flags == 0);
67065d70b3bSAlan Somers 		}, Eq(true)),
67165d70b3bSAlan Somers 		_)
67265d70b3bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
67365d70b3bSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
67465d70b3bSAlan Somers 		out.body.write.size = len;
67565d70b3bSAlan Somers 	})));
67665d70b3bSAlan Somers 
67765d70b3bSAlan Somers 	fd = open(FULLPATH, O_RDWR);
67865d70b3bSAlan Somers 	ASSERT_GE(fd, 0);
67965d70b3bSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
68065d70b3bSAlan Somers 
68165d70b3bSAlan Somers 	/* Check that cached attributes were updated appropriately */
68265d70b3bSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
68365d70b3bSAlan Somers 	EXPECT_EQ(fsize + len, sb.st_size);
68465d70b3bSAlan Somers 
68565d70b3bSAlan Somers 	leak(fd);
68665d70b3bSAlan Somers }
68765d70b3bSAlan Somers 
68892bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
TEST_F(CopyFileRange_7_27,fallback)68992bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback)
69092bbfe1fSAlan Somers {
69192bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
69292bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
69392bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
69492bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
69592bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
69692bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
69792bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
69892bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
69992bbfe1fSAlan Somers 	off_t fsize2 = 0;
70092bbfe1fSAlan Somers 	off_t start1 = 0;
70192bbfe1fSAlan Somers 	off_t start2 = 0;
70292bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
70392bbfe1fSAlan Somers 	ssize_t len;
70492bbfe1fSAlan Somers 	int fd1, fd2;
70592bbfe1fSAlan Somers 
70692bbfe1fSAlan Somers 	len = strlen(contents);
70792bbfe1fSAlan Somers 
70892bbfe1fSAlan Somers 	/*
70992bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
71092bbfe1fSAlan Somers 	 * predictable.
71192bbfe1fSAlan Somers 	 */
71292bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
71392bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
71492bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
71592bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
71692bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
71792bbfe1fSAlan Somers 		ResultOf([=](auto in) {
71892bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
71992bbfe1fSAlan Somers 		}, Eq(true)),
72092bbfe1fSAlan Somers 		_)
72192bbfe1fSAlan Somers 	).Times(0);
72292bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
72392bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
72492bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
72592bbfe1fSAlan Somers 
72692bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
72792bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
72892bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
72992bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
73092bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
7314ac4b126SAlan Somers 
7324ac4b126SAlan Somers 	leak(fd1);
7334ac4b126SAlan Somers 	leak(fd2);
73492bbfe1fSAlan Somers }
73592bbfe1fSAlan Somers 
7365169832cSAlan Somers /*
7375169832cSAlan Somers  * With -o noatime, copy_file_range should update the destination's mtime and
7385169832cSAlan Somers  * ctime, but not the source's atime.
7395169832cSAlan Somers  */
TEST_F(CopyFileRangeNoAtime,timestamps)7405169832cSAlan Somers TEST_F(CopyFileRangeNoAtime, timestamps)
7415169832cSAlan Somers {
7425169832cSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
7435169832cSAlan Somers 	const char RELPATH1[] = "src.txt";
7445169832cSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
7455169832cSAlan Somers 	const char RELPATH2[] = "dst.txt";
7465169832cSAlan Somers 	struct stat sb1a, sb1b, sb2a, sb2b;
7475169832cSAlan Somers 	const uint64_t ino1 = 42;
7485169832cSAlan Somers 	const uint64_t ino2 = 43;
7495169832cSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
7505169832cSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
7515169832cSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
7525169832cSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
7535169832cSAlan Somers 	off_t start1 = 1 << 18;
7545169832cSAlan Somers 	off_t start2 = 3 << 17;
7555169832cSAlan Somers 	ssize_t len = 65536;
7565169832cSAlan Somers 	int fd1, fd2;
75792bbfe1fSAlan Somers 
7585169832cSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
7595169832cSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
7605169832cSAlan Somers 	expect_open(ino1, 0, 1, fh1);
7615169832cSAlan Somers 	expect_open(ino2, 0, 1, fh2);
7625169832cSAlan Somers 	EXPECT_CALL(*m_mock, process(
7635169832cSAlan Somers 		ResultOf([=](auto in) {
7645169832cSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
7655169832cSAlan Somers 				in.header.nodeid == ino1 &&
7665169832cSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
7675169832cSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
7685169832cSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
7695169832cSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
7705169832cSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
7715169832cSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
7725169832cSAlan Somers 				in.body.copy_file_range.flags == 0);
7735169832cSAlan Somers 		}, Eq(true)),
7745169832cSAlan Somers 		_)
7755169832cSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
7765169832cSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
7775169832cSAlan Somers 		out.body.write.size = len;
7785169832cSAlan Somers 	})));
7795169832cSAlan Somers 
7805169832cSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
7815169832cSAlan Somers 	ASSERT_GE(fd1, 0);
7825169832cSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
7835169832cSAlan Somers 	ASSERT_GE(fd2, 0);
7845169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
7855169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
7865169832cSAlan Somers 
7875169832cSAlan Somers 	nap();
7885169832cSAlan Somers 
7895169832cSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
7905169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
7915169832cSAlan Somers 	ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
7925169832cSAlan Somers 
7935169832cSAlan Somers 	EXPECT_EQ(sb1a.st_atime, sb1b.st_atime);
7945169832cSAlan Somers 	EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
7955169832cSAlan Somers 	EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
7965169832cSAlan Somers 	EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
7975169832cSAlan Somers 	EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
7985169832cSAlan Somers 	EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
7995169832cSAlan Somers 
8005169832cSAlan Somers 	leak(fd1);
8015169832cSAlan Somers 	leak(fd2);
8025169832cSAlan Somers }
803