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 } 737