xref: /freebsd/tests/sys/fs/fusefs/copy_file_range.cc (revision 92bbfe1f0d1f1c4436d1f064a16e5aaf682526ba)
1*92bbfe1fSAlan Somers /*-
2*92bbfe1fSAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3*92bbfe1fSAlan Somers  *
4*92bbfe1fSAlan Somers  * Copyright (c) 2020 Alan Somers
5*92bbfe1fSAlan Somers  *
6*92bbfe1fSAlan Somers  * Redistribution and use in source and binary forms, with or without
7*92bbfe1fSAlan Somers  * modification, are permitted provided that the following conditions
8*92bbfe1fSAlan Somers  * are met:
9*92bbfe1fSAlan Somers  * 1. Redistributions of source code must retain the above copyright
10*92bbfe1fSAlan Somers  *    notice, this list of conditions and the following disclaimer.
11*92bbfe1fSAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
12*92bbfe1fSAlan Somers  *    notice, this list of conditions and the following disclaimer in the
13*92bbfe1fSAlan Somers  *    documentation and/or other materials provided with the distribution.
14*92bbfe1fSAlan Somers  *
15*92bbfe1fSAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*92bbfe1fSAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*92bbfe1fSAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*92bbfe1fSAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*92bbfe1fSAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*92bbfe1fSAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*92bbfe1fSAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*92bbfe1fSAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*92bbfe1fSAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*92bbfe1fSAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*92bbfe1fSAlan Somers  * SUCH DAMAGE.
26*92bbfe1fSAlan Somers  *
27*92bbfe1fSAlan Somers  * $FreeBSD$
28*92bbfe1fSAlan Somers  */
29*92bbfe1fSAlan Somers 
30*92bbfe1fSAlan Somers extern "C" {
31*92bbfe1fSAlan Somers #include <sys/param.h>
32*92bbfe1fSAlan Somers #include <sys/time.h>
33*92bbfe1fSAlan Somers #include <sys/resource.h>
34*92bbfe1fSAlan Somers 
35*92bbfe1fSAlan Somers #include <fcntl.h>
36*92bbfe1fSAlan Somers #include <signal.h>
37*92bbfe1fSAlan Somers #include <unistd.h>
38*92bbfe1fSAlan Somers }
39*92bbfe1fSAlan Somers 
40*92bbfe1fSAlan Somers #include "mockfs.hh"
41*92bbfe1fSAlan Somers #include "utils.hh"
42*92bbfe1fSAlan Somers 
43*92bbfe1fSAlan Somers using namespace testing;
44*92bbfe1fSAlan Somers 
45*92bbfe1fSAlan Somers class CopyFileRange: public FuseTest {
46*92bbfe1fSAlan Somers public:
47*92bbfe1fSAlan Somers static sig_atomic_t s_sigxfsz;
48*92bbfe1fSAlan Somers 
49*92bbfe1fSAlan Somers void SetUp() {
50*92bbfe1fSAlan Somers 	s_sigxfsz = 0;
51*92bbfe1fSAlan Somers 	FuseTest::SetUp();
52*92bbfe1fSAlan Somers }
53*92bbfe1fSAlan Somers 
54*92bbfe1fSAlan Somers void TearDown() {
55*92bbfe1fSAlan Somers 	struct sigaction sa;
56*92bbfe1fSAlan Somers 
57*92bbfe1fSAlan Somers 	bzero(&sa, sizeof(sa));
58*92bbfe1fSAlan Somers 	sa.sa_handler = SIG_DFL;
59*92bbfe1fSAlan Somers 	sigaction(SIGXFSZ, &sa, NULL);
60*92bbfe1fSAlan Somers 
61*92bbfe1fSAlan Somers 	FuseTest::TearDown();
62*92bbfe1fSAlan Somers }
63*92bbfe1fSAlan Somers 
64*92bbfe1fSAlan Somers void expect_maybe_lseek(uint64_t ino)
65*92bbfe1fSAlan Somers {
66*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
67*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
68*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_LSEEK &&
69*92bbfe1fSAlan Somers 				in.header.nodeid == ino);
70*92bbfe1fSAlan Somers 		}, Eq(true)),
71*92bbfe1fSAlan Somers 		_)
72*92bbfe1fSAlan Somers 	).Times(AtMost(1))
73*92bbfe1fSAlan Somers 	.WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
74*92bbfe1fSAlan Somers }
75*92bbfe1fSAlan Somers 
76*92bbfe1fSAlan Somers void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
77*92bbfe1fSAlan Somers {
78*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
79*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
80*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
81*92bbfe1fSAlan Somers 				in.header.nodeid == ino);
82*92bbfe1fSAlan Somers 		}, Eq(true)),
83*92bbfe1fSAlan Somers 		_)
84*92bbfe1fSAlan Somers 	).Times(times)
85*92bbfe1fSAlan Somers 	.WillRepeatedly(Invoke(
86*92bbfe1fSAlan Somers 		ReturnImmediate([=](auto in __unused, auto& out) {
87*92bbfe1fSAlan Somers 		out.header.len = sizeof(out.header);
88*92bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, open);
89*92bbfe1fSAlan Somers 		out.body.open.fh = fh;
90*92bbfe1fSAlan Somers 		out.body.open.open_flags = flags;
91*92bbfe1fSAlan Somers 	})));
92*92bbfe1fSAlan Somers }
93*92bbfe1fSAlan Somers 
94*92bbfe1fSAlan Somers void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
95*92bbfe1fSAlan Somers 	uint64_t osize, const void *contents)
96*92bbfe1fSAlan Somers {
97*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
98*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
99*92bbfe1fSAlan Somers 			const char *buf = (const char*)in.body.bytes +
100*92bbfe1fSAlan Somers 				sizeof(struct fuse_write_in);
101*92bbfe1fSAlan Somers 
102*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_WRITE &&
103*92bbfe1fSAlan Somers 				in.header.nodeid == ino &&
104*92bbfe1fSAlan Somers 				in.body.write.offset == offset  &&
105*92bbfe1fSAlan Somers 				in.body.write.size == isize &&
106*92bbfe1fSAlan Somers 				0 == bcmp(buf, contents, isize));
107*92bbfe1fSAlan Somers 		}, Eq(true)),
108*92bbfe1fSAlan Somers 		_)
109*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
110*92bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
111*92bbfe1fSAlan Somers 		out.body.write.size = osize;
112*92bbfe1fSAlan Somers 	})));
113*92bbfe1fSAlan Somers }
114*92bbfe1fSAlan Somers 
115*92bbfe1fSAlan Somers };
116*92bbfe1fSAlan Somers 
117*92bbfe1fSAlan Somers sig_atomic_t CopyFileRange::s_sigxfsz = 0;
118*92bbfe1fSAlan Somers 
119*92bbfe1fSAlan Somers void sigxfsz_handler(int __unused sig) {
120*92bbfe1fSAlan Somers 	CopyFileRange::s_sigxfsz = 1;
121*92bbfe1fSAlan Somers }
122*92bbfe1fSAlan Somers 
123*92bbfe1fSAlan Somers 
124*92bbfe1fSAlan Somers class CopyFileRange_7_27: public CopyFileRange {
125*92bbfe1fSAlan Somers public:
126*92bbfe1fSAlan Somers virtual void SetUp() {
127*92bbfe1fSAlan Somers 	m_kernel_minor_version = 27;
128*92bbfe1fSAlan Somers 	CopyFileRange::SetUp();
129*92bbfe1fSAlan Somers }
130*92bbfe1fSAlan Somers };
131*92bbfe1fSAlan Somers 
132*92bbfe1fSAlan Somers TEST_F(CopyFileRange, eio)
133*92bbfe1fSAlan Somers {
134*92bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
135*92bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
136*92bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
137*92bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
138*92bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
139*92bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
140*92bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
141*92bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
142*92bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
143*92bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
144*92bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
145*92bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
146*92bbfe1fSAlan Somers 	ssize_t len = 65536;
147*92bbfe1fSAlan Somers 	int fd1, fd2;
148*92bbfe1fSAlan Somers 
149*92bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
150*92bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
151*92bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
152*92bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
153*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
154*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
155*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
156*92bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
157*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
158*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
159*92bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
160*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
161*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
162*92bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
163*92bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
164*92bbfe1fSAlan Somers 		}, Eq(true)),
165*92bbfe1fSAlan Somers 		_)
166*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(EIO)));
167*92bbfe1fSAlan Somers 
168*92bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
169*92bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
170*92bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
171*92bbfe1fSAlan Somers 	EXPECT_EQ(EIO, errno);
172*92bbfe1fSAlan Somers }
173*92bbfe1fSAlan Somers 
174*92bbfe1fSAlan Somers /*
175*92bbfe1fSAlan Somers  * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
176*92bbfe1fSAlan Somers  * fallback to a read/write based implementation.
177*92bbfe1fSAlan Somers  */
178*92bbfe1fSAlan Somers TEST_F(CopyFileRange, fallback)
179*92bbfe1fSAlan Somers {
180*92bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
181*92bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
182*92bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
183*92bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
184*92bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
185*92bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
186*92bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
187*92bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
188*92bbfe1fSAlan Somers 	off_t fsize2 = 0;
189*92bbfe1fSAlan Somers 	off_t start1 = 0;
190*92bbfe1fSAlan Somers 	off_t start2 = 0;
191*92bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
192*92bbfe1fSAlan Somers 	ssize_t len;
193*92bbfe1fSAlan Somers 	int fd1, fd2;
194*92bbfe1fSAlan Somers 
195*92bbfe1fSAlan Somers 	len = strlen(contents);
196*92bbfe1fSAlan Somers 
197*92bbfe1fSAlan Somers 	/*
198*92bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
199*92bbfe1fSAlan Somers 	 * predictable.
200*92bbfe1fSAlan Somers 	 */
201*92bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
202*92bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
203*92bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
204*92bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
205*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
206*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
207*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
208*92bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
209*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
210*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
211*92bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
212*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
213*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
214*92bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
215*92bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
216*92bbfe1fSAlan Somers 		}, Eq(true)),
217*92bbfe1fSAlan Somers 		_)
218*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
219*92bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
220*92bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
221*92bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
222*92bbfe1fSAlan Somers 
223*92bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
224*92bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
225*92bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
226*92bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
227*92bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
228*92bbfe1fSAlan Somers }
229*92bbfe1fSAlan Somers 
230*92bbfe1fSAlan Somers /* fusefs should respect RLIMIT_FSIZE */
231*92bbfe1fSAlan Somers TEST_F(CopyFileRange, rlimit_fsize)
232*92bbfe1fSAlan Somers {
233*92bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
234*92bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
235*92bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
236*92bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
237*92bbfe1fSAlan Somers 	struct rlimit rl;
238*92bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
239*92bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
240*92bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
241*92bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
242*92bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
243*92bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
244*92bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
245*92bbfe1fSAlan Somers 	off_t start2 = fsize2;
246*92bbfe1fSAlan Somers 	ssize_t len = 65536;
247*92bbfe1fSAlan Somers 	int fd1, fd2;
248*92bbfe1fSAlan Somers 
249*92bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
250*92bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
251*92bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
252*92bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
253*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
254*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
255*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
256*92bbfe1fSAlan Somers 		}, Eq(true)),
257*92bbfe1fSAlan Somers 		_)
258*92bbfe1fSAlan Somers 	).Times(0);
259*92bbfe1fSAlan Somers 
260*92bbfe1fSAlan Somers 	rl.rlim_cur = fsize2;
261*92bbfe1fSAlan Somers 	rl.rlim_max = 10 * fsize2;
262*92bbfe1fSAlan Somers 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
263*92bbfe1fSAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
264*92bbfe1fSAlan Somers 
265*92bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
266*92bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
267*92bbfe1fSAlan Somers 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
268*92bbfe1fSAlan Somers 	EXPECT_EQ(EFBIG, errno);
269*92bbfe1fSAlan Somers 	EXPECT_EQ(1, s_sigxfsz);
270*92bbfe1fSAlan Somers }
271*92bbfe1fSAlan Somers 
272*92bbfe1fSAlan Somers TEST_F(CopyFileRange, ok)
273*92bbfe1fSAlan Somers {
274*92bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
275*92bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
276*92bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
277*92bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
278*92bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
279*92bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
280*92bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
281*92bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
282*92bbfe1fSAlan Somers 	off_t fsize1 = 1 << 20;		/* 1 MiB */
283*92bbfe1fSAlan Somers 	off_t fsize2 = 1 << 19;		/* 512 KiB */
284*92bbfe1fSAlan Somers 	off_t start1 = 1 << 18;
285*92bbfe1fSAlan Somers 	off_t start2 = 3 << 17;
286*92bbfe1fSAlan Somers 	ssize_t len = 65536;
287*92bbfe1fSAlan Somers 	int fd1, fd2;
288*92bbfe1fSAlan Somers 
289*92bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
290*92bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
291*92bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
292*92bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
293*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
294*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
295*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
296*92bbfe1fSAlan Somers 				in.header.nodeid == ino1 &&
297*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh1 &&
298*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == start1 &&
299*92bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino2 &&
300*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh2 &&
301*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == start2 &&
302*92bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
303*92bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
304*92bbfe1fSAlan Somers 		}, Eq(true)),
305*92bbfe1fSAlan Somers 		_)
306*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
307*92bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
308*92bbfe1fSAlan Somers 		out.body.write.size = len;
309*92bbfe1fSAlan Somers 	})));
310*92bbfe1fSAlan Somers 
311*92bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
312*92bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
313*92bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
314*92bbfe1fSAlan Somers }
315*92bbfe1fSAlan Somers 
316*92bbfe1fSAlan Somers /*
317*92bbfe1fSAlan Somers  * copy_file_range can make copies within a single file, as long as the ranges
318*92bbfe1fSAlan Somers  * don't overlap.
319*92bbfe1fSAlan Somers  * */
320*92bbfe1fSAlan Somers TEST_F(CopyFileRange, same_file)
321*92bbfe1fSAlan Somers {
322*92bbfe1fSAlan Somers 	const char FULLPATH[] = "mountpoint/src.txt";
323*92bbfe1fSAlan Somers 	const char RELPATH[] = "src.txt";
324*92bbfe1fSAlan Somers 	const uint64_t ino = 4;
325*92bbfe1fSAlan Somers 	const uint64_t fh = 0xdeadbeefa7ebabe;
326*92bbfe1fSAlan Somers 	off_t fsize = 1 << 20;		/* 1 MiB */
327*92bbfe1fSAlan Somers 	off_t off_in = 1 << 18;
328*92bbfe1fSAlan Somers 	off_t off_out = 3 << 17;
329*92bbfe1fSAlan Somers 	ssize_t len = 65536;
330*92bbfe1fSAlan Somers 	int fd;
331*92bbfe1fSAlan Somers 
332*92bbfe1fSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
333*92bbfe1fSAlan Somers 	expect_open(ino, 0, 1, fh);
334*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
335*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
336*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
337*92bbfe1fSAlan Somers 				in.header.nodeid == ino &&
338*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_in == fh &&
339*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_in == off_in &&
340*92bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino &&
341*92bbfe1fSAlan Somers 				in.body.copy_file_range.fh_out == fh &&
342*92bbfe1fSAlan Somers 				(off_t)in.body.copy_file_range.off_out == off_out &&
343*92bbfe1fSAlan Somers 				in.body.copy_file_range.len == (size_t)len &&
344*92bbfe1fSAlan Somers 				in.body.copy_file_range.flags == 0);
345*92bbfe1fSAlan Somers 		}, Eq(true)),
346*92bbfe1fSAlan Somers 		_)
347*92bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
348*92bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
349*92bbfe1fSAlan Somers 		out.body.write.size = len;
350*92bbfe1fSAlan Somers 	})));
351*92bbfe1fSAlan Somers 
352*92bbfe1fSAlan Somers 	fd = open(FULLPATH, O_RDWR);
353*92bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
354*92bbfe1fSAlan Somers }
355*92bbfe1fSAlan Somers 
356*92bbfe1fSAlan Somers /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
357*92bbfe1fSAlan Somers TEST_F(CopyFileRange_7_27, fallback)
358*92bbfe1fSAlan Somers {
359*92bbfe1fSAlan Somers 	const char FULLPATH1[] = "mountpoint/src.txt";
360*92bbfe1fSAlan Somers 	const char RELPATH1[] = "src.txt";
361*92bbfe1fSAlan Somers 	const char FULLPATH2[] = "mountpoint/dst.txt";
362*92bbfe1fSAlan Somers 	const char RELPATH2[] = "dst.txt";
363*92bbfe1fSAlan Somers 	const uint64_t ino1 = 42;
364*92bbfe1fSAlan Somers 	const uint64_t ino2 = 43;
365*92bbfe1fSAlan Somers 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
366*92bbfe1fSAlan Somers 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
367*92bbfe1fSAlan Somers 	off_t fsize2 = 0;
368*92bbfe1fSAlan Somers 	off_t start1 = 0;
369*92bbfe1fSAlan Somers 	off_t start2 = 0;
370*92bbfe1fSAlan Somers 	const char *contents = "Hello, world!";
371*92bbfe1fSAlan Somers 	ssize_t len;
372*92bbfe1fSAlan Somers 	int fd1, fd2;
373*92bbfe1fSAlan Somers 
374*92bbfe1fSAlan Somers 	len = strlen(contents);
375*92bbfe1fSAlan Somers 
376*92bbfe1fSAlan Somers 	/*
377*92bbfe1fSAlan Somers 	 * Ensure that we read to EOF, just so the buffer cache's read size is
378*92bbfe1fSAlan Somers 	 * predictable.
379*92bbfe1fSAlan Somers 	 */
380*92bbfe1fSAlan Somers 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
381*92bbfe1fSAlan Somers 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
382*92bbfe1fSAlan Somers 	expect_open(ino1, 0, 1, fh1);
383*92bbfe1fSAlan Somers 	expect_open(ino2, 0, 1, fh2);
384*92bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
385*92bbfe1fSAlan Somers 		ResultOf([=](auto in) {
386*92bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
387*92bbfe1fSAlan Somers 		}, Eq(true)),
388*92bbfe1fSAlan Somers 		_)
389*92bbfe1fSAlan Somers 	).Times(0);
390*92bbfe1fSAlan Somers 	expect_maybe_lseek(ino1);
391*92bbfe1fSAlan Somers 	expect_read(ino1, start1, len, len, contents, 0);
392*92bbfe1fSAlan Somers 	expect_write(ino2, start2, len, len, contents);
393*92bbfe1fSAlan Somers 
394*92bbfe1fSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
395*92bbfe1fSAlan Somers 	ASSERT_GE(fd1, 0);
396*92bbfe1fSAlan Somers 	fd2 = open(FULLPATH2, O_WRONLY);
397*92bbfe1fSAlan Somers 	ASSERT_GE(fd2, 0);
398*92bbfe1fSAlan Somers 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
399*92bbfe1fSAlan Somers }
400*92bbfe1fSAlan Somers 
401*92bbfe1fSAlan Somers 
402