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