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 {
47 static sig_atomic_t s_sigxfsz;
57 bzero(&sa, sizeof(sa));
58 sa.sa_handler = SIG_DFL;
59 sigaction(SIGXFSZ, &sa, NULL);
64 void expect_maybe_lseek(uint64_t ino)
66 EXPECT_CALL(*m_mock, process(
67 ResultOf([=](auto in) {
68 return (in.header.opcode == FUSE_LSEEK &&
69 in.header.nodeid == ino);
73 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
76 void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
78 EXPECT_CALL(*m_mock, process(
79 ResultOf([=](auto in) {
80 return (in.header.opcode == FUSE_OPEN &&
81 in.header.nodeid == ino);
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;
94 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
95 uint64_t osize, const void *contents)
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);
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));
109 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
110 SET_OUT_HEADER_LEN(out, write);
111 out.body.write.size = osize;
117 sig_atomic_t CopyFileRange::s_sigxfsz = 0;
119 void sigxfsz_handler(int __unused sig) {
120 CopyFileRange::s_sigxfsz = 1;
124 class CopyFileRange_7_27: public CopyFileRange {
126 virtual void SetUp() {
127 m_kernel_minor_version = 27;
128 CopyFileRange::SetUp();
132 class CopyFileRangeNoAtime: public CopyFileRange {
134 virtual void SetUp() {
136 CopyFileRange::SetUp();
140 TEST_F(CopyFileRange, eio)
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;
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);
174 ).WillOnce(Invoke(ReturnErrno(EIO)));
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);
183 * copy_file_range should evict cached data for the modified region of the
186 TEST_F(CopyFileRange, evicts_cache)
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;
204 buf0 = malloc(m_maxbcachebuf);
205 memset(buf0, 42, m_maxbcachebuf);
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,
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);
226 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
227 SET_OUT_HEADER_LEN(out, write);
228 out.body.write.size = len;
231 fd1 = open(FULLPATH1, O_RDONLY);
232 fd2 = open(FULLPATH2, O_RDWR);
235 buf = malloc(m_maxbcachebuf);
236 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
238 EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
240 // Tell the FUSE server overwrite the region we just read
241 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
243 // Read again. This should bypass the cache and read direct from server
244 buf1 = malloc(m_maxbcachebuf);
245 memset(buf1, 69, m_maxbcachebuf);
247 expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
249 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
251 EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
261 * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
262 * fallback to a read/write based implementation.
264 TEST_F(CopyFileRange, fallback)
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;
277 const char *contents = "Hello, world!";
281 len = strlen(contents);
284 * Ensure that we read to EOF, just so the buffer cache's read size is
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);
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);
309 fd1 = open(FULLPATH1, O_RDONLY);
311 fd2 = open(FULLPATH2, O_WRONLY);
313 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
316 /* fusefs should respect RLIMIT_FSIZE */
317 TEST_F(CopyFileRange, rlimit_fsize)
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";
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;
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);
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);
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);
358 TEST_F(CopyFileRange, ok)
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;
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);
392 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
393 SET_OUT_HEADER_LEN(out, write);
394 out.body.write.size = len;
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));
403 * copy_file_range can make copies within a single file, as long as the ranges
406 TEST_F(CopyFileRange, same_file)
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;
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);
433 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
434 SET_OUT_HEADER_LEN(out, write);
435 out.body.write.size = len;
438 fd = open(FULLPATH, O_RDWR);
439 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
445 * copy_file_range should update the destination's mtime and ctime, and
446 * the source's atime.
448 TEST_F(CopyFileRange, timestamps)
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;
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);
483 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
484 SET_OUT_HEADER_LEN(out, write);
485 out.body.write.size = len;
488 fd1 = open(FULLPATH1, O_RDONLY);
490 fd2 = open(FULLPATH2, O_WRONLY);
492 ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
493 ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
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);
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);
513 * copy_file_range can extend the size of a file
515 TEST_F(CopyFileRange, extend)
517 const char FULLPATH[] = "mountpoint/src.txt";
518 const char RELPATH[] = "src.txt";
520 const uint64_t ino = 4;
521 const uint64_t fh = 0xdeadbeefa7ebabe;
524 off_t off_out = 65536;
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);
543 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
544 SET_OUT_HEADER_LEN(out, write);
545 out.body.write.size = len;
548 fd = open(FULLPATH, O_RDWR);
550 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
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);
559 /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
560 TEST_F(CopyFileRange_7_27, fallback)
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;
573 const char *contents = "Hello, world!";
577 len = strlen(contents);
580 * Ensure that we read to EOF, just so the buffer cache's read size is
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);
593 expect_maybe_lseek(ino1);
594 expect_read(ino1, start1, len, len, contents, 0);
595 expect_write(ino2, start2, len, len, contents);
597 fd1 = open(FULLPATH1, O_RDONLY);
599 fd2 = open(FULLPATH2, O_WRONLY);
601 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
608 * With -o noatime, copy_file_range should update the destination's mtime and
609 * ctime, but not the source's atime.
611 TEST_F(CopyFileRangeNoAtime, timestamps)
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;
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);
646 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
647 SET_OUT_HEADER_LEN(out, write);
648 out.body.write.size = len;
651 fd1 = open(FULLPATH1, O_RDONLY);
653 fd2 = open(FULLPATH2, O_WRONLY);
655 ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
656 ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
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);
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);