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