xref: /freebsd/tests/sys/fs/fusefs/copy_file_range.cc (revision 4ac4b12699fd423165a0ac0d05630a1fae022bbb)
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