]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/copy_file_range.cc
Merge llvm-project main llvmorg-15-init-17485-ga3e38b4a206b
[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
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 }