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 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 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 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: 103 virtual void SetUp() { 104 m_kernel_minor_version = 27; 105 CopyFileRange::SetUp(); 106 } 107 }; 108 109 class CopyFileRangeNoAtime: public CopyFileRange { 110 public: 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 122 virtual void SetUp() { 123 s_sigxfsz = 0; 124 getrlimit(RLIMIT_FSIZE, &m_initial_limit); 125 CopyFileRange::SetUp(); 126 } 127 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 144 void sigxfsz_handler(int __unused sig) { 145 CopyFileRangeRlimitFsize::s_sigxfsz = 1; 146 } 147 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 * */ 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 */ 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 * */ 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 */ 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 */ 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