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