xref: /freebsd/tests/sys/fs/fusefs/copy_file_range.cc (revision 65d70b3bae0c70798b0a2b8ed129bc146fed1cce)
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 
13292bbfe1fSAlan Somers TEST_F(CopyFileRange, eio)
13392bbfe1fSAlan Somers {
13492bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
13592bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
13692bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
13792bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
13892bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
13992bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
14092bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
14192bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
14292bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
14392bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
14492bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
14592bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
14692bbfe1fSAlan Somers 	ssize_t len = 65536;
14792bbfe1fSAlan Somers 	int fd1, fd2;
14892bbfe1fSAlan Somers 
14992bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
15092bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
15192bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
15292bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
15392bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
15492bbfe1fSAlan Somers 		ResultOf([=](auto in) {
15592bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
15692bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
15792bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
15892bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
15992bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
16092bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
16192bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
16292bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
16392bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
16492bbfe1fSAlan Somers 		}, Eq(true)),
16592bbfe1fSAlan Somers 		_)
16692bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(EIO)));
16792bbfe1fSAlan Somers 
16892bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
16992bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
17092bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
17192bbfe1fSAlan Somers 	EXPECT_EQ(EIO, errno);
17292bbfe1fSAlan Somers }
17392bbfe1fSAlan Somers 
17492bbfe1fSAlan Somers /*
17592bbfe1fSAlan Somers  * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
17692bbfe1fSAlan Somers  * fallback to a read/write based implementation.
17792bbfe1fSAlan Somers  */
17892bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback)
17992bbfe1fSAlan Somers {
18092bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
18192bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
18292bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
18392bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
18492bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
18592bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
18692bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
18792bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
18892bbfe1fSAlan Somers 	off_t fsize2 = 0;
18992bbfe1fSAlan Somers 	off_t start1 = 0;
19092bbfe1fSAlan Somers 	off_t start2 = 0;
19192bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
19292bbfe1fSAlan Somers 	ssize_t len;
19392bbfe1fSAlan Somers 	int fd1, fd2;
19492bbfe1fSAlan Somers 
19592bbfe1fSAlan Somers 	len = strlen(contents);
19692bbfe1fSAlan Somers 
19792bbfe1fSAlan Somers 	/*
19892bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
19992bbfe1fSAlan Somers 	 * predictable.
20092bbfe1fSAlan Somers 	 */
20192bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
20292bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
20392bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
20492bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
20592bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
20692bbfe1fSAlan Somers 		ResultOf([=](auto in) {
20792bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
20892bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
20992bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
21092bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
21192bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
21292bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
21392bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
21492bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
21592bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
21692bbfe1fSAlan Somers 		}, Eq(true)),
21792bbfe1fSAlan Somers 		_)
21892bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
21992bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
22092bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
22192bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
22292bbfe1fSAlan Somers 
22392bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
22492bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
22592bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
22692bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
22792bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
22892bbfe1fSAlan Somers }
22992bbfe1fSAlan Somers 
23092bbfe1fSAlan Somers /* fusefs should respect RLIMIT_FSIZE */
23192bbfe1fSAlan Somers TEST_F(CopyFileRange, rlimit_fsize)
23292bbfe1fSAlan Somers {
23392bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
23492bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
23592bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
23692bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
23792bbfe1fSAlan Somers 	struct rlimit rl;
23892bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
23992bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
24092bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
24192bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
24292bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
24392bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
24492bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
24592bbfe1fSAlan Somers 	off_t start2 = fsize2;
24692bbfe1fSAlan Somers 	ssize_t len = 65536;
24792bbfe1fSAlan Somers 	int fd1, fd2;
24892bbfe1fSAlan Somers 
24992bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
25092bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
25192bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
25292bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
25392bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
25492bbfe1fSAlan Somers 		ResultOf([=](auto in) {
25592bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
25692bbfe1fSAlan Somers 		}, Eq(true)),
25792bbfe1fSAlan Somers 		_)
25892bbfe1fSAlan Somers 	).Times(0);
25992bbfe1fSAlan Somers 
26092bbfe1fSAlan Somers 	rl.rlim_cur = fsize2;
26192bbfe1fSAlan Somers 	rl.rlim_max = 10 * fsize2;
26292bbfe1fSAlan Somers 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
26392bbfe1fSAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
26492bbfe1fSAlan Somers 
26592bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
26692bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
26792bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
26892bbfe1fSAlan Somers 	EXPECT_EQ(EFBIG, errno);
26992bbfe1fSAlan Somers 	EXPECT_EQ(1, s_sigxfsz);
27092bbfe1fSAlan Somers }
27192bbfe1fSAlan Somers 
27292bbfe1fSAlan Somers TEST_F(CopyFileRange, ok)
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 fsize1 = 1 << 20;		/* 1 MiB */
28392bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
28492bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
28592bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
28692bbfe1fSAlan Somers 	ssize_t len = 65536;
28792bbfe1fSAlan Somers 	int fd1, fd2;
28892bbfe1fSAlan Somers 
28992bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
29092bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
29192bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
29292bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
29392bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
29492bbfe1fSAlan Somers 		ResultOf([=](auto in) {
29592bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
29692bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
29792bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
29892bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
29992bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
30092bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
30192bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
30292bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
30392bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
30492bbfe1fSAlan Somers 		}, Eq(true)),
30592bbfe1fSAlan Somers 		_)
30692bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
30792bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
30892bbfe1fSAlan Somers 		out.body.write.size = len;
30992bbfe1fSAlan Somers 	})));
31092bbfe1fSAlan Somers 
31192bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
31292bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
31392bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
31492bbfe1fSAlan Somers }
31592bbfe1fSAlan Somers 
31692bbfe1fSAlan Somers /*
31792bbfe1fSAlan Somers  * copy_file_range can make copies within a single file, as long as the ranges
31892bbfe1fSAlan Somers  * don't overlap.
31992bbfe1fSAlan Somers  * */
32092bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file)
32192bbfe1fSAlan Somers {
32292bbfe1fSAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
32392bbfe1fSAlan Somers 	const char RELPATH[] = "src.txt";
32492bbfe1fSAlan Somers 	const uint64_t ino = 4;
32592bbfe1fSAlan Somers 	const uint64_t fh = 0xdeadbeefa7ebabe;
32692bbfe1fSAlan Somers 	off_t fsize = 1 << 20;		/* 1 MiB */
32792bbfe1fSAlan Somers 	off_t off_in = 1 << 18;
32892bbfe1fSAlan Somers 	off_t off_out = 3 << 17;
32992bbfe1fSAlan Somers 	ssize_t len = 65536;
33092bbfe1fSAlan Somers 	int fd;
33192bbfe1fSAlan Somers 
33292bbfe1fSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
33392bbfe1fSAlan Somers 	expect_open(ino, 0, 1, fh);
33492bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
33592bbfe1fSAlan Somers 		ResultOf([=](auto in) {
33692bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
33792bbfe1fSAlan Somers 				in.header.nodeid == ino &&
33892bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh &&
33992bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == off_in &&
34092bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino &&
34192bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh &&
34292bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == off_out &&
34392bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
34492bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
34592bbfe1fSAlan Somers 		}, Eq(true)),
34692bbfe1fSAlan Somers 		_)
34792bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
34892bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
34992bbfe1fSAlan Somers 		out.body.write.size = len;
35092bbfe1fSAlan Somers 	})));
35192bbfe1fSAlan Somers 
35292bbfe1fSAlan Somers 	fd = open(FULLPATH, O_RDWR);
35392bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
35492bbfe1fSAlan Somers }
35592bbfe1fSAlan Somers 
356*65d70b3bSAlan Somers /*
357*65d70b3bSAlan Somers  * copy_file_range can extend the size of a file
358*65d70b3bSAlan Somers  * */
359*65d70b3bSAlan Somers TEST_F(CopyFileRange, extend)
360*65d70b3bSAlan Somers {
361*65d70b3bSAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
362*65d70b3bSAlan Somers 	const char RELPATH[] = "src.txt";
363*65d70b3bSAlan Somers 	struct stat sb;
364*65d70b3bSAlan Somers 	const uint64_t ino = 4;
365*65d70b3bSAlan Somers 	const uint64_t fh = 0xdeadbeefa7ebabe;
366*65d70b3bSAlan Somers 	off_t fsize = 65536;
367*65d70b3bSAlan Somers 	off_t off_in = 0;
368*65d70b3bSAlan Somers 	off_t off_out = 65536;
369*65d70b3bSAlan Somers 	ssize_t len = 65536;
370*65d70b3bSAlan Somers 	int fd;
371*65d70b3bSAlan Somers 
372*65d70b3bSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
373*65d70b3bSAlan Somers 	expect_open(ino, 0, 1, fh);
374*65d70b3bSAlan Somers 	EXPECT_CALL(*m_mock, process(
375*65d70b3bSAlan Somers 		ResultOf([=](auto in) {
376*65d70b3bSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
377*65d70b3bSAlan Somers 				in.header.nodeid == ino &&
378*65d70b3bSAlan Somers 				in.body.copy_file_range.fh_in == fh &&
379*65d70b3bSAlan Somers 				(off_t)in.body.copy_file_range.off_in == off_in &&
380*65d70b3bSAlan Somers 				in.body.copy_file_range.nodeid_out == ino &&
381*65d70b3bSAlan Somers 				in.body.copy_file_range.fh_out == fh &&
382*65d70b3bSAlan Somers 				(off_t)in.body.copy_file_range.off_out == off_out &&
383*65d70b3bSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
384*65d70b3bSAlan Somers 				in.body.copy_file_range.flags == 0);
385*65d70b3bSAlan Somers 		}, Eq(true)),
386*65d70b3bSAlan Somers 		_)
387*65d70b3bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
388*65d70b3bSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
389*65d70b3bSAlan Somers 		out.body.write.size = len;
390*65d70b3bSAlan Somers 	})));
391*65d70b3bSAlan Somers 
392*65d70b3bSAlan Somers 	fd = open(FULLPATH, O_RDWR);
393*65d70b3bSAlan Somers 	ASSERT_GE(fd, 0);
394*65d70b3bSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
395*65d70b3bSAlan Somers 
396*65d70b3bSAlan Somers 	/* Check that cached attributes were updated appropriately */
397*65d70b3bSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
398*65d70b3bSAlan Somers 	EXPECT_EQ(fsize + len, sb.st_size);
399*65d70b3bSAlan Somers 
400*65d70b3bSAlan Somers 	leak(fd);
401*65d70b3bSAlan Somers }
402*65d70b3bSAlan Somers 
40392bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
40492bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback)
40592bbfe1fSAlan Somers {
40692bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
40792bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
40892bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
40992bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
41092bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
41192bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
41292bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
41392bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
41492bbfe1fSAlan Somers 	off_t fsize2 = 0;
41592bbfe1fSAlan Somers 	off_t start1 = 0;
41692bbfe1fSAlan Somers 	off_t start2 = 0;
41792bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
41892bbfe1fSAlan Somers 	ssize_t len;
41992bbfe1fSAlan Somers 	int fd1, fd2;
42092bbfe1fSAlan Somers 
42192bbfe1fSAlan Somers 	len = strlen(contents);
42292bbfe1fSAlan Somers 
42392bbfe1fSAlan Somers 	/*
42492bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
42592bbfe1fSAlan Somers 	 * predictable.
42692bbfe1fSAlan Somers 	 */
42792bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
42892bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
42992bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
43092bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
43192bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
43292bbfe1fSAlan Somers 		ResultOf([=](auto in) {
43392bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
43492bbfe1fSAlan Somers 		}, Eq(true)),
43592bbfe1fSAlan Somers 		_)
43692bbfe1fSAlan Somers 	).Times(0);
43792bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
43892bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
43992bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
44092bbfe1fSAlan Somers 
44192bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
44292bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
44392bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
44492bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
44592bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
44692bbfe1fSAlan Somers }
44792bbfe1fSAlan Somers 
44892bbfe1fSAlan Somers 
449