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