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