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