2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2020 Alan Somers
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
31 #include <sys/param.h>
33 #include <sys/resource.h>
43 using namespace testing;
45 class CopyFileRange: public FuseTest {
48 void expect_maybe_lseek(uint64_t ino)
50 EXPECT_CALL(*m_mock, process(
51 ResultOf([=](auto in) {
52 return (in.header.opcode == FUSE_LSEEK &&
53 in.header.nodeid == ino);
57 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
60 void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
62 EXPECT_CALL(*m_mock, process(
63 ResultOf([=](auto in) {
64 return (in.header.opcode == FUSE_OPEN &&
65 in.header.nodeid == ino);
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;
78 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
79 uint64_t osize, const void *contents)
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);
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));
93 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
94 SET_OUT_HEADER_LEN(out, write);
95 out.body.write.size = osize;
102 class CopyFileRange_7_27: public CopyFileRange {
104 virtual void SetUp() {
105 m_kernel_minor_version = 27;
106 CopyFileRange::SetUp();
110 class CopyFileRangeNoAtime: public CopyFileRange {
112 virtual void SetUp() {
114 CopyFileRange::SetUp();
118 class CopyFileRangeRlimitFsize: public CopyFileRange {
120 static sig_atomic_t s_sigxfsz;
121 struct rlimit m_initial_limit;
123 virtual void SetUp() {
125 getrlimit(RLIMIT_FSIZE, &m_initial_limit);
126 CopyFileRange::SetUp();
132 setrlimit(RLIMIT_FSIZE, &m_initial_limit);
134 bzero(&sa, sizeof(sa));
135 sa.sa_handler = SIG_DFL;
136 sigaction(SIGXFSZ, &sa, NULL);
138 FuseTest::TearDown();
143 sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
145 void sigxfsz_handler(int __unused sig) {
146 CopyFileRangeRlimitFsize::s_sigxfsz = 1;
149 TEST_F(CopyFileRange, eio)
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;
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);
183 ).WillOnce(Invoke(ReturnErrno(EIO)));
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);
192 * copy_file_range should evict cached data for the modified region of the
195 TEST_F(CopyFileRange, evicts_cache)
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;
213 buf0 = malloc(m_maxbcachebuf);
214 memset(buf0, 42, m_maxbcachebuf);
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,
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);
235 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
236 SET_OUT_HEADER_LEN(out, write);
237 out.body.write.size = len;
240 fd1 = open(FULLPATH1, O_RDONLY);
241 fd2 = open(FULLPATH2, O_RDWR);
244 buf = malloc(m_maxbcachebuf);
245 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
247 EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
249 // Tell the FUSE server overwrite the region we just read
250 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
252 // Read again. This should bypass the cache and read direct from server
253 buf1 = malloc(m_maxbcachebuf);
254 memset(buf1, 69, m_maxbcachebuf);
256 expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
258 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
260 EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
270 * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
271 * fallback to a read/write based implementation.
273 TEST_F(CopyFileRange, fallback)
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;
286 const char *contents = "Hello, world!";
290 len = strlen(contents);
293 * Ensure that we read to EOF, just so the buffer cache's read size is
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);
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);
318 fd1 = open(FULLPATH1, O_RDONLY);
320 fd2 = open(FULLPATH2, O_WRONLY);
322 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
326 * copy_file_range should send SIGXFSZ and return EFBIG when the operation
327 * would exceed the limit imposed by RLIMIT_FSIZE.
329 TEST_F(CopyFileRangeRlimitFsize, signal)
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";
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;
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);
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);
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);
371 * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
373 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
375 TEST_F(CopyFileRangeRlimitFsize, truncate)
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";
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;
391 off_t limit = start2 + len / 2;
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
406 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
407 SET_OUT_HEADER_LEN(out, write);
408 out.body.write.size = len / 2;
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);
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));
421 TEST_F(CopyFileRange, ok)
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;
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);
455 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
456 SET_OUT_HEADER_LEN(out, write);
457 out.body.write.size = len;
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));
466 * copy_file_range can make copies within a single file, as long as the ranges
469 TEST_F(CopyFileRange, same_file)
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;
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);
496 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
497 SET_OUT_HEADER_LEN(out, write);
498 out.body.write.size = len;
501 fd = open(FULLPATH, O_RDWR);
502 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
508 * copy_file_range should update the destination's mtime and ctime, and
509 * the source's atime.
511 TEST_F(CopyFileRange, timestamps)
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;
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);
546 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
547 SET_OUT_HEADER_LEN(out, write);
548 out.body.write.size = len;
551 fd1 = open(FULLPATH1, O_RDONLY);
553 fd2 = open(FULLPATH2, O_WRONLY);
555 ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
556 ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
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);
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);
576 * copy_file_range can extend the size of a file
578 TEST_F(CopyFileRange, extend)
580 const char FULLPATH[] = "mountpoint/src.txt";
581 const char RELPATH[] = "src.txt";
583 const uint64_t ino = 4;
584 const uint64_t fh = 0xdeadbeefa7ebabe;
587 off_t off_out = 65536;
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);
606 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
607 SET_OUT_HEADER_LEN(out, write);
608 out.body.write.size = len;
611 fd = open(FULLPATH, O_RDWR);
613 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
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);
622 /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
623 TEST_F(CopyFileRange_7_27, fallback)
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;
636 const char *contents = "Hello, world!";
640 len = strlen(contents);
643 * Ensure that we read to EOF, just so the buffer cache's read size is
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);
656 expect_maybe_lseek(ino1);
657 expect_read(ino1, start1, len, len, contents, 0);
658 expect_write(ino2, start2, len, len, contents);
660 fd1 = open(FULLPATH1, O_RDONLY);
662 fd2 = open(FULLPATH2, O_WRONLY);
664 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
671 * With -o noatime, copy_file_range should update the destination's mtime and
672 * ctime, but not the source's atime.
674 TEST_F(CopyFileRangeNoAtime, timestamps)
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;
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);
709 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
710 SET_OUT_HEADER_LEN(out, write);
711 out.body.write.size = len;
714 fd1 = open(FULLPATH1, O_RDONLY);
716 fd2 = open(FULLPATH2, O_WRONLY);
718 ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
719 ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
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);
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);