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