]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/copy_file_range.cc
contrib/tzdata: import tzdata 2022d
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / copy_file_range.cc
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 }