xref: /freebsd/tests/sys/fs/fusefs/copy_file_range.cc (revision 8bae22bbbe6571da9259e0d43ffa8a56f4b3e171)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2020 Alan Somers
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 extern "C" {
29 #include <sys/param.h>
30 #include <sys/mman.h>
31 #include <sys/time.h>
32 #include <sys/resource.h>
33 
34 #include <fcntl.h>
35 #include <signal.h>
36 #include <unistd.h>
37 }
38 
39 #include "mockfs.hh"
40 #include "utils.hh"
41 
42 using namespace testing;
43 
44 class CopyFileRange: public FuseTest {
45 public:
46 
expect_maybe_lseek(uint64_t ino)47 void expect_maybe_lseek(uint64_t ino)
48 {
49 	EXPECT_CALL(*m_mock, process(
50 		ResultOf([=](auto in) {
51 			return (in.header.opcode == FUSE_LSEEK &&
52 				in.header.nodeid == ino);
53 		}, Eq(true)),
54 		_)
55 	).Times(AtMost(1))
56 	.WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
57 }
58 
expect_open(uint64_t ino,uint32_t flags,int times,uint64_t fh)59 void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
60 {
61 	EXPECT_CALL(*m_mock, process(
62 		ResultOf([=](auto in) {
63 			return (in.header.opcode == FUSE_OPEN &&
64 				in.header.nodeid == ino);
65 		}, Eq(true)),
66 		_)
67 	).Times(times)
68 	.WillRepeatedly(Invoke(
69 		ReturnImmediate([=](auto in __unused, auto& out) {
70 		out.header.len = sizeof(out.header);
71 		SET_OUT_HEADER_LEN(out, open);
72 		out.body.open.fh = fh;
73 		out.body.open.open_flags = flags;
74 	})));
75 }
76 
expect_write(uint64_t ino,uint64_t offset,uint64_t isize,uint64_t osize,const void * contents)77 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
78 	uint64_t osize, const void *contents)
79 {
80 	EXPECT_CALL(*m_mock, process(
81 		ResultOf([=](auto in) {
82 			const char *buf = (const char*)in.body.bytes +
83 				sizeof(struct fuse_write_in);
84 
85 			return (in.header.opcode == FUSE_WRITE &&
86 				in.header.nodeid == ino &&
87 				in.body.write.offset == offset  &&
88 				in.body.write.size == isize &&
89 				0 == bcmp(buf, contents, isize));
90 		}, Eq(true)),
91 		_)
92 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
93 		SET_OUT_HEADER_LEN(out, write);
94 		out.body.write.size = osize;
95 	})));
96 }
97 
98 };
99 
100 
101 class CopyFileRange_7_27: public CopyFileRange {
102 public:
SetUp()103 virtual void SetUp() {
104 	m_kernel_minor_version = 27;
105 	CopyFileRange::SetUp();
106 }
107 };
108 
109 class CopyFileRangeNoAtime: public CopyFileRange {
110 public:
SetUp()111 virtual void SetUp() {
112 	m_noatime = true;
113 	CopyFileRange::SetUp();
114 }
115 };
116 
117 class CopyFileRangeRlimitFsize: public CopyFileRange {
118 public:
119 static sig_atomic_t s_sigxfsz;
120 struct rlimit	m_initial_limit;
121 
SetUp()122 virtual void SetUp() {
123 	s_sigxfsz = 0;
124 	getrlimit(RLIMIT_FSIZE, &m_initial_limit);
125 	CopyFileRange::SetUp();
126 }
127 
TearDown()128 void TearDown() {
129 	struct sigaction sa;
130 
131 	setrlimit(RLIMIT_FSIZE, &m_initial_limit);
132 
133 	bzero(&sa, sizeof(sa));
134 	sa.sa_handler = SIG_DFL;
135 	sigaction(SIGXFSZ, &sa, NULL);
136 
137 	FuseTest::TearDown();
138 }
139 
140 };
141 
142 sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
143 
sigxfsz_handler(int __unused sig)144 void sigxfsz_handler(int __unused sig) {
145 	CopyFileRangeRlimitFsize::s_sigxfsz = 1;
146 }
147 
TEST_F(CopyFileRange,eio)148 TEST_F(CopyFileRange, eio)
149 {
150 	const char FULLPATH1[] = "mountpoint/src.txt";
151 	const char RELPATH1[] = "src.txt";
152 	const char FULLPATH2[] = "mountpoint/dst.txt";
153 	const char RELPATH2[] = "dst.txt";
154 	const uint64_t ino1 = 42;
155 	const uint64_t ino2 = 43;
156 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
157 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
158 	off_t fsize1 = 1 << 20;		/* 1 MiB */
159 	off_t fsize2 = 1 << 19;		/* 512 KiB */
160 	off_t start1 = 1 << 18;
161 	off_t start2 = 3 << 17;
162 	ssize_t len = 65536;
163 	int fd1, fd2;
164 
165 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
166 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
167 	expect_open(ino1, 0, 1, fh1);
168 	expect_open(ino2, 0, 1, fh2);
169 	EXPECT_CALL(*m_mock, process(
170 		ResultOf([=](auto in) {
171 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
172 				in.header.nodeid == ino1 &&
173 				in.body.copy_file_range.fh_in == fh1 &&
174 				(off_t)in.body.copy_file_range.off_in == start1 &&
175 				in.body.copy_file_range.nodeid_out == ino2 &&
176 				in.body.copy_file_range.fh_out == fh2 &&
177 				(off_t)in.body.copy_file_range.off_out == start2 &&
178 				in.body.copy_file_range.len == (size_t)len &&
179 				in.body.copy_file_range.flags == 0);
180 		}, Eq(true)),
181 		_)
182 	).WillOnce(Invoke(ReturnErrno(EIO)));
183 
184 	fd1 = open(FULLPATH1, O_RDONLY);
185 	fd2 = open(FULLPATH2, O_WRONLY);
186 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
187 	EXPECT_EQ(EIO, errno);
188 }
189 
190 /*
191  * copy_file_range should evict cached data for the modified region of the
192  * destination file.
193  */
TEST_F(CopyFileRange,evicts_cache)194 TEST_F(CopyFileRange, evicts_cache)
195 {
196 	const char FULLPATH1[] = "mountpoint/src.txt";
197 	const char RELPATH1[] = "src.txt";
198 	const char FULLPATH2[] = "mountpoint/dst.txt";
199 	const char RELPATH2[] = "dst.txt";
200 	char *buf0, *buf1, *buf;
201 	const uint64_t ino1 = 42;
202 	const uint64_t ino2 = 43;
203 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
204 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
205 	off_t fsize1 = 1 << 20;		/* 1 MiB */
206 	off_t fsize2 = 1 << 19;		/* 512 KiB */
207 	off_t start1 = 1 << 18;
208 	off_t start2 = 3 << 17;
209 	ssize_t len = m_maxbcachebuf;
210 	int fd1, fd2;
211 
212 	buf0 = new char[m_maxbcachebuf];
213 	memset(buf0, 42, m_maxbcachebuf);
214 
215 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
216 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
217 	expect_open(ino1, 0, 1, fh1);
218 	expect_open(ino2, 0, 1, fh2);
219 	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1,
220 		fh2);
221 	EXPECT_CALL(*m_mock, process(
222 		ResultOf([=](auto in) {
223 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
224 				in.header.nodeid == ino1 &&
225 				in.body.copy_file_range.fh_in == fh1 &&
226 				(off_t)in.body.copy_file_range.off_in == start1 &&
227 				in.body.copy_file_range.nodeid_out == ino2 &&
228 				in.body.copy_file_range.fh_out == fh2 &&
229 				(off_t)in.body.copy_file_range.off_out == start2 &&
230 				in.body.copy_file_range.len == (size_t)len &&
231 				in.body.copy_file_range.flags == 0);
232 		}, Eq(true)),
233 		_)
234 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
235 		SET_OUT_HEADER_LEN(out, write);
236 		out.body.write.size = len;
237 	})));
238 
239 	fd1 = open(FULLPATH1, O_RDONLY);
240 	fd2 = open(FULLPATH2, O_RDWR);
241 
242 	// Prime cache
243 	buf = new char[m_maxbcachebuf];
244 	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
245 		<< strerror(errno);
246 	EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
247 
248 	// Tell the FUSE server overwrite the region we just read
249 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
250 
251 	// Read again.  This should bypass the cache and read direct from server
252 	buf1 = new char[m_maxbcachebuf];
253 	memset(buf1, 69, m_maxbcachebuf);
254 	start2 -= len;
255 	expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
256 		fh2);
257 	ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
258 		<< strerror(errno);
259 	EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
260 
261 	delete[] buf1;
262 	delete[] buf0;
263 	delete[] buf;
264 	leak(fd1);
265 	leak(fd2);
266 }
267 
268 /*
269  * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
270  * fallback to a read/write based implementation.
271  */
TEST_F(CopyFileRange,fallback)272 TEST_F(CopyFileRange, fallback)
273 {
274 	const char FULLPATH1[] = "mountpoint/src.txt";
275 	const char RELPATH1[] = "src.txt";
276 	const char FULLPATH2[] = "mountpoint/dst.txt";
277 	const char RELPATH2[] = "dst.txt";
278 	const uint64_t ino1 = 42;
279 	const uint64_t ino2 = 43;
280 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
281 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
282 	off_t fsize2 = 0;
283 	off_t start1 = 0;
284 	off_t start2 = 0;
285 	const char *contents = "Hello, world!";
286 	ssize_t len;
287 	int fd1, fd2;
288 
289 	len = strlen(contents);
290 
291 	/*
292 	 * Ensure that we read to EOF, just so the buffer cache's read size is
293 	 * predictable.
294 	 */
295 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
296 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
297 	expect_open(ino1, 0, 1, fh1);
298 	expect_open(ino2, 0, 1, fh2);
299 	EXPECT_CALL(*m_mock, process(
300 		ResultOf([=](auto in) {
301 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
302 				in.header.nodeid == ino1 &&
303 				in.body.copy_file_range.fh_in == fh1 &&
304 				(off_t)in.body.copy_file_range.off_in == start1 &&
305 				in.body.copy_file_range.nodeid_out == ino2 &&
306 				in.body.copy_file_range.fh_out == fh2 &&
307 				(off_t)in.body.copy_file_range.off_out == start2 &&
308 				in.body.copy_file_range.len == (size_t)len &&
309 				in.body.copy_file_range.flags == 0);
310 		}, Eq(true)),
311 		_)
312 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
313 	expect_maybe_lseek(ino1);
314 	expect_read(ino1, start1, len, len, contents, 0);
315 	expect_write(ino2, start2, len, len, contents);
316 
317 	fd1 = open(FULLPATH1, O_RDONLY);
318 	ASSERT_GE(fd1, 0);
319 	fd2 = open(FULLPATH2, O_WRONLY);
320 	ASSERT_GE(fd2, 0);
321 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
322 }
323 
324 /*
325  * Writes via mmap should not conflict with using copy_file_range.  Any dirty
326  * pages that overlap with copy_file_range's input should be flushed before
327  * FUSE_COPY_FILE_RANGE is sent.
328  */
TEST_F(CopyFileRange,mmap_write)329 TEST_F(CopyFileRange, mmap_write)
330 {
331 	const char FULLPATH[] = "mountpoint/src.txt";
332 	const char RELPATH[] = "src.txt";
333 	uint8_t *wbuf, *fbuf;
334 	void *p;
335 	size_t fsize = 0x6000;
336 	size_t wsize = 0x3000;
337 	ssize_t r;
338 	off_t offset2_in = 0;
339 	off_t offset2_out = wsize;
340 	size_t copysize = wsize;
341 	const uint64_t ino = 42;
342 	const uint64_t fh = 0xdeadbeef1a7ebabe;
343 	int fd;
344 	const mode_t mode = 0644;
345 
346 	fbuf = new uint8_t[fsize]();
347 	wbuf = new uint8_t[wsize];
348 	memset(wbuf, 1, wsize);
349 
350 	expect_lookup(RELPATH, ino, S_IFREG | mode, fsize, 1);
351 	expect_open(ino, 0, 1, fh);
352 	/* This read is initiated by the mmap write */
353 	expect_read(ino, 0, fsize, fsize, fbuf, -1, fh);
354 	/* This write flushes the buffer filled by the mmap write */
355 	expect_write(ino, 0, wsize, wsize, wbuf);
356 
357 	EXPECT_CALL(*m_mock, process(
358 		ResultOf([=](auto in) {
359 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
360 				(off_t)in.body.copy_file_range.off_in == offset2_in &&
361 				(off_t)in.body.copy_file_range.off_out == offset2_out &&
362 				in.body.copy_file_range.len == copysize
363 			);
364 		}, Eq(true)),
365 		_)
366 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
367 		SET_OUT_HEADER_LEN(out, write);
368 		out.body.write.size = copysize;
369 	})));
370 
371 	fd = open(FULLPATH, O_RDWR);
372 
373 	/* First, write some data via mmap */
374 	p = mmap(NULL, wsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
375 	ASSERT_NE(MAP_FAILED, p) << strerror(errno);
376 	memmove((uint8_t*)p, wbuf, wsize);
377 	ASSERT_EQ(0, munmap(p, wsize)) << strerror(errno);
378 
379 	/*
380 	 * Then copy it around the file via copy_file_range.  This should
381 	 * trigger a FUSE_WRITE to flush the pages written by mmap.
382 	 */
383 	r = copy_file_range(fd, &offset2_in, fd, &offset2_out, copysize, 0);
384 	ASSERT_EQ(copysize, (size_t)r) << strerror(errno);
385 
386 	delete[] wbuf;
387 	delete[] fbuf;
388 }
389 
390 
391 /*
392  * copy_file_range should send SIGXFSZ and return EFBIG when the operation
393  * would exceed the limit imposed by RLIMIT_FSIZE.
394  */
TEST_F(CopyFileRangeRlimitFsize,signal)395 TEST_F(CopyFileRangeRlimitFsize, signal)
396 {
397 	const char FULLPATH1[] = "mountpoint/src.txt";
398 	const char RELPATH1[] = "src.txt";
399 	const char FULLPATH2[] = "mountpoint/dst.txt";
400 	const char RELPATH2[] = "dst.txt";
401 	struct rlimit rl;
402 	const uint64_t ino1 = 42;
403 	const uint64_t ino2 = 43;
404 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
405 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
406 	off_t fsize1 = 1 << 20;		/* 1 MiB */
407 	off_t fsize2 = 1 << 19;		/* 512 KiB */
408 	off_t start1 = 1 << 18;
409 	off_t start2 = fsize2;
410 	ssize_t len = 65536;
411 	int fd1, fd2;
412 
413 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
414 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
415 	expect_open(ino1, 0, 1, fh1);
416 	expect_open(ino2, 0, 1, fh2);
417 	EXPECT_CALL(*m_mock, process(
418 		ResultOf([=](auto in) {
419 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
420 		}, Eq(true)),
421 		_)
422 	).Times(0);
423 
424 	rl.rlim_cur = fsize2;
425 	rl.rlim_max = m_initial_limit.rlim_max;
426 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
427 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
428 
429 	fd1 = open(FULLPATH1, O_RDONLY);
430 	fd2 = open(FULLPATH2, O_WRONLY);
431 	ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
432 	EXPECT_EQ(EFBIG, errno);
433 	EXPECT_EQ(1, s_sigxfsz);
434 }
435 
436 /*
437  * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
438  * aborted.
439  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
440  */
TEST_F(CopyFileRangeRlimitFsize,truncate)441 TEST_F(CopyFileRangeRlimitFsize, truncate)
442 {
443 	const char FULLPATH1[] = "mountpoint/src.txt";
444 	const char RELPATH1[] = "src.txt";
445 	const char FULLPATH2[] = "mountpoint/dst.txt";
446 	const char RELPATH2[] = "dst.txt";
447 	struct rlimit rl;
448 	const uint64_t ino1 = 42;
449 	const uint64_t ino2 = 43;
450 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
451 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
452 	off_t fsize1 = 1 << 20;		/* 1 MiB */
453 	off_t fsize2 = 1 << 19;		/* 512 KiB */
454 	off_t start1 = 1 << 18;
455 	off_t start2 = fsize2;
456 	ssize_t len = 65536;
457 	off_t limit = start2 + len / 2;
458 	int fd1, fd2;
459 
460 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
461 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
462 	expect_open(ino1, 0, 1, fh1);
463 	expect_open(ino2, 0, 1, fh2);
464 	EXPECT_CALL(*m_mock, process(
465 		ResultOf([=](auto in) {
466 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
467 				(off_t)in.body.copy_file_range.off_out == start2 &&
468 				in.body.copy_file_range.len == (size_t)len / 2
469 			);
470 		}, Eq(true)),
471 		_)
472 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
473 		SET_OUT_HEADER_LEN(out, write);
474 		out.body.write.size = len / 2;
475 	})));
476 
477 	rl.rlim_cur = limit;
478 	rl.rlim_max = m_initial_limit.rlim_max;
479 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
480 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
481 
482 	fd1 = open(FULLPATH1, O_RDONLY);
483 	fd2 = open(FULLPATH2, O_WRONLY);
484 	ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
485 }
486 
TEST_F(CopyFileRange,ok)487 TEST_F(CopyFileRange, ok)
488 {
489 	const char FULLPATH1[] = "mountpoint/src.txt";
490 	const char RELPATH1[] = "src.txt";
491 	const char FULLPATH2[] = "mountpoint/dst.txt";
492 	const char RELPATH2[] = "dst.txt";
493 	const uint64_t ino1 = 42;
494 	const uint64_t ino2 = 43;
495 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
496 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
497 	off_t fsize1 = 1 << 20;		/* 1 MiB */
498 	off_t fsize2 = 1 << 19;		/* 512 KiB */
499 	off_t start1 = 1 << 18;
500 	off_t start2 = 3 << 17;
501 	ssize_t len = 65536;
502 	int fd1, fd2;
503 
504 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
505 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
506 	expect_open(ino1, 0, 1, fh1);
507 	expect_open(ino2, 0, 1, fh2);
508 	EXPECT_CALL(*m_mock, process(
509 		ResultOf([=](auto in) {
510 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
511 				in.header.nodeid == ino1 &&
512 				in.body.copy_file_range.fh_in == fh1 &&
513 				(off_t)in.body.copy_file_range.off_in == start1 &&
514 				in.body.copy_file_range.nodeid_out == ino2 &&
515 				in.body.copy_file_range.fh_out == fh2 &&
516 				(off_t)in.body.copy_file_range.off_out == start2 &&
517 				in.body.copy_file_range.len == (size_t)len &&
518 				in.body.copy_file_range.flags == 0);
519 		}, Eq(true)),
520 		_)
521 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
522 		SET_OUT_HEADER_LEN(out, write);
523 		out.body.write.size = len;
524 	})));
525 
526 	fd1 = open(FULLPATH1, O_RDONLY);
527 	fd2 = open(FULLPATH2, O_WRONLY);
528 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
529 }
530 
531 /*
532  * copy_file_range can make copies within a single file, as long as the ranges
533  * don't overlap.
534  * */
TEST_F(CopyFileRange,same_file)535 TEST_F(CopyFileRange, same_file)
536 {
537 	const char FULLPATH[] = "mountpoint/src.txt";
538 	const char RELPATH[] = "src.txt";
539 	const uint64_t ino = 4;
540 	const uint64_t fh = 0xdeadbeefa7ebabe;
541 	off_t fsize = 1 << 20;		/* 1 MiB */
542 	off_t off_in = 1 << 18;
543 	off_t off_out = 3 << 17;
544 	ssize_t len = 65536;
545 	int fd;
546 
547 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
548 	expect_open(ino, 0, 1, fh);
549 	EXPECT_CALL(*m_mock, process(
550 		ResultOf([=](auto in) {
551 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
552 				in.header.nodeid == ino &&
553 				in.body.copy_file_range.fh_in == fh &&
554 				(off_t)in.body.copy_file_range.off_in == off_in &&
555 				in.body.copy_file_range.nodeid_out == ino &&
556 				in.body.copy_file_range.fh_out == fh &&
557 				(off_t)in.body.copy_file_range.off_out == off_out &&
558 				in.body.copy_file_range.len == (size_t)len &&
559 				in.body.copy_file_range.flags == 0);
560 		}, Eq(true)),
561 		_)
562 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
563 		SET_OUT_HEADER_LEN(out, write);
564 		out.body.write.size = len;
565 	})));
566 
567 	fd = open(FULLPATH, O_RDWR);
568 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
569 
570 	leak(fd);
571 }
572 
573 /*
574  * copy_file_range should update the destination's mtime and ctime, and
575  * the source's atime.
576  */
TEST_F(CopyFileRange,timestamps)577 TEST_F(CopyFileRange, timestamps)
578 {
579 	const char FULLPATH1[] = "mountpoint/src.txt";
580 	const char RELPATH1[] = "src.txt";
581 	const char FULLPATH2[] = "mountpoint/dst.txt";
582 	const char RELPATH2[] = "dst.txt";
583 	struct stat sb1a, sb1b, sb2a, sb2b;
584 	const uint64_t ino1 = 42;
585 	const uint64_t ino2 = 43;
586 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
587 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
588 	off_t fsize1 = 1 << 20;		/* 1 MiB */
589 	off_t fsize2 = 1 << 19;		/* 512 KiB */
590 	off_t start1 = 1 << 18;
591 	off_t start2 = 3 << 17;
592 	ssize_t len = 65536;
593 	int fd1, fd2;
594 
595 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
596 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
597 	expect_open(ino1, 0, 1, fh1);
598 	expect_open(ino2, 0, 1, fh2);
599 	EXPECT_CALL(*m_mock, process(
600 		ResultOf([=](auto in) {
601 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
602 				in.header.nodeid == ino1 &&
603 				in.body.copy_file_range.fh_in == fh1 &&
604 				(off_t)in.body.copy_file_range.off_in == start1 &&
605 				in.body.copy_file_range.nodeid_out == ino2 &&
606 				in.body.copy_file_range.fh_out == fh2 &&
607 				(off_t)in.body.copy_file_range.off_out == start2 &&
608 				in.body.copy_file_range.len == (size_t)len &&
609 				in.body.copy_file_range.flags == 0);
610 		}, Eq(true)),
611 		_)
612 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
613 		SET_OUT_HEADER_LEN(out, write);
614 		out.body.write.size = len;
615 	})));
616 
617 	fd1 = open(FULLPATH1, O_RDONLY);
618 	ASSERT_GE(fd1, 0);
619 	fd2 = open(FULLPATH2, O_WRONLY);
620 	ASSERT_GE(fd2, 0);
621 	ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
622 	ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
623 
624 	nap();
625 
626 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
627 	ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
628 	ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
629 
630 	EXPECT_NE(sb1a.st_atime, sb1b.st_atime);
631 	EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
632 	EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
633 	EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
634 	EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
635 	EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
636 
637 	leak(fd1);
638 	leak(fd2);
639 }
640 
641 /*
642  * copy_file_range can extend the size of a file
643  * */
TEST_F(CopyFileRange,extend)644 TEST_F(CopyFileRange, extend)
645 {
646 	const char FULLPATH[] = "mountpoint/src.txt";
647 	const char RELPATH[] = "src.txt";
648 	struct stat sb;
649 	const uint64_t ino = 4;
650 	const uint64_t fh = 0xdeadbeefa7ebabe;
651 	off_t fsize = 65536;
652 	off_t off_in = 0;
653 	off_t off_out = 65536;
654 	ssize_t len = 65536;
655 	int fd;
656 
657 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
658 	expect_open(ino, 0, 1, fh);
659 	EXPECT_CALL(*m_mock, process(
660 		ResultOf([=](auto in) {
661 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
662 				in.header.nodeid == ino &&
663 				in.body.copy_file_range.fh_in == fh &&
664 				(off_t)in.body.copy_file_range.off_in == off_in &&
665 				in.body.copy_file_range.nodeid_out == ino &&
666 				in.body.copy_file_range.fh_out == fh &&
667 				(off_t)in.body.copy_file_range.off_out == off_out &&
668 				in.body.copy_file_range.len == (size_t)len &&
669 				in.body.copy_file_range.flags == 0);
670 		}, Eq(true)),
671 		_)
672 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
673 		SET_OUT_HEADER_LEN(out, write);
674 		out.body.write.size = len;
675 	})));
676 
677 	fd = open(FULLPATH, O_RDWR);
678 	ASSERT_GE(fd, 0);
679 	ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
680 
681 	/* Check that cached attributes were updated appropriately */
682 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
683 	EXPECT_EQ(fsize + len, sb.st_size);
684 
685 	leak(fd);
686 }
687 
688 /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
TEST_F(CopyFileRange_7_27,fallback)689 TEST_F(CopyFileRange_7_27, fallback)
690 {
691 	const char FULLPATH1[] = "mountpoint/src.txt";
692 	const char RELPATH1[] = "src.txt";
693 	const char FULLPATH2[] = "mountpoint/dst.txt";
694 	const char RELPATH2[] = "dst.txt";
695 	const uint64_t ino1 = 42;
696 	const uint64_t ino2 = 43;
697 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
698 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
699 	off_t fsize2 = 0;
700 	off_t start1 = 0;
701 	off_t start2 = 0;
702 	const char *contents = "Hello, world!";
703 	ssize_t len;
704 	int fd1, fd2;
705 
706 	len = strlen(contents);
707 
708 	/*
709 	 * Ensure that we read to EOF, just so the buffer cache's read size is
710 	 * predictable.
711 	 */
712 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
713 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
714 	expect_open(ino1, 0, 1, fh1);
715 	expect_open(ino2, 0, 1, fh2);
716 	EXPECT_CALL(*m_mock, process(
717 		ResultOf([=](auto in) {
718 			return (in.header.opcode == FUSE_COPY_FILE_RANGE);
719 		}, Eq(true)),
720 		_)
721 	).Times(0);
722 	expect_maybe_lseek(ino1);
723 	expect_read(ino1, start1, len, len, contents, 0);
724 	expect_write(ino2, start2, len, len, contents);
725 
726 	fd1 = open(FULLPATH1, O_RDONLY);
727 	ASSERT_GE(fd1, 0);
728 	fd2 = open(FULLPATH2, O_WRONLY);
729 	ASSERT_GE(fd2, 0);
730 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
731 
732 	leak(fd1);
733 	leak(fd2);
734 }
735 
736 /*
737  * With -o noatime, copy_file_range should update the destination's mtime and
738  * ctime, but not the source's atime.
739  */
TEST_F(CopyFileRangeNoAtime,timestamps)740 TEST_F(CopyFileRangeNoAtime, timestamps)
741 {
742 	const char FULLPATH1[] = "mountpoint/src.txt";
743 	const char RELPATH1[] = "src.txt";
744 	const char FULLPATH2[] = "mountpoint/dst.txt";
745 	const char RELPATH2[] = "dst.txt";
746 	struct stat sb1a, sb1b, sb2a, sb2b;
747 	const uint64_t ino1 = 42;
748 	const uint64_t ino2 = 43;
749 	const uint64_t fh1 = 0xdeadbeef1a7ebabe;
750 	const uint64_t fh2 = 0xdeadc0de88c0ffee;
751 	off_t fsize1 = 1 << 20;		/* 1 MiB */
752 	off_t fsize2 = 1 << 19;		/* 512 KiB */
753 	off_t start1 = 1 << 18;
754 	off_t start2 = 3 << 17;
755 	ssize_t len = 65536;
756 	int fd1, fd2;
757 
758 	expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
759 	expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
760 	expect_open(ino1, 0, 1, fh1);
761 	expect_open(ino2, 0, 1, fh2);
762 	EXPECT_CALL(*m_mock, process(
763 		ResultOf([=](auto in) {
764 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
765 				in.header.nodeid == ino1 &&
766 				in.body.copy_file_range.fh_in == fh1 &&
767 				(off_t)in.body.copy_file_range.off_in == start1 &&
768 				in.body.copy_file_range.nodeid_out == ino2 &&
769 				in.body.copy_file_range.fh_out == fh2 &&
770 				(off_t)in.body.copy_file_range.off_out == start2 &&
771 				in.body.copy_file_range.len == (size_t)len &&
772 				in.body.copy_file_range.flags == 0);
773 		}, Eq(true)),
774 		_)
775 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
776 		SET_OUT_HEADER_LEN(out, write);
777 		out.body.write.size = len;
778 	})));
779 
780 	fd1 = open(FULLPATH1, O_RDONLY);
781 	ASSERT_GE(fd1, 0);
782 	fd2 = open(FULLPATH2, O_WRONLY);
783 	ASSERT_GE(fd2, 0);
784 	ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
785 	ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
786 
787 	nap();
788 
789 	ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
790 	ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
791 	ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
792 
793 	EXPECT_EQ(sb1a.st_atime, sb1b.st_atime);
794 	EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
795 	EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
796 	EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
797 	EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
798 	EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
799 
800 	leak(fd1);
801 	leak(fd2);
802 }
803