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 TEST_F(CopyFileRange, eio) 133 { 134 const char FULLPATH1[] = "mountpoint/src.txt"; 135 const char RELPATH1[] = "src.txt"; 136 const char FULLPATH2[] = "mountpoint/dst.txt"; 137 const char RELPATH2[] = "dst.txt"; 138 const uint64_t ino1 = 42; 139 const uint64_t ino2 = 43; 140 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 141 const uint64_t fh2 = 0xdeadc0de88c0ffee; 142 off_t fsize1 = 1 << 20; /* 1 MiB */ 143 off_t fsize2 = 1 << 19; /* 512 KiB */ 144 off_t start1 = 1 << 18; 145 off_t start2 = 3 << 17; 146 ssize_t len = 65536; 147 int fd1, fd2; 148 149 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 150 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 151 expect_open(ino1, 0, 1, fh1); 152 expect_open(ino2, 0, 1, fh2); 153 EXPECT_CALL(*m_mock, process( 154 ResultOf([=](auto in) { 155 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 156 in.header.nodeid == ino1 && 157 in.body.copy_file_range.fh_in == fh1 && 158 (off_t)in.body.copy_file_range.off_in == start1 && 159 in.body.copy_file_range.nodeid_out == ino2 && 160 in.body.copy_file_range.fh_out == fh2 && 161 (off_t)in.body.copy_file_range.off_out == start2 && 162 in.body.copy_file_range.len == (size_t)len && 163 in.body.copy_file_range.flags == 0); 164 }, Eq(true)), 165 _) 166 ).WillOnce(Invoke(ReturnErrno(EIO))); 167 168 fd1 = open(FULLPATH1, O_RDONLY); 169 fd2 = open(FULLPATH2, O_WRONLY); 170 ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 171 EXPECT_EQ(EIO, errno); 172 } 173 174 /* 175 * copy_file_range should evict cached data for the modified region of the 176 * destination file. 177 */ 178 TEST_F(CopyFileRange, evicts_cache) 179 { 180 const char FULLPATH1[] = "mountpoint/src.txt"; 181 const char RELPATH1[] = "src.txt"; 182 const char FULLPATH2[] = "mountpoint/dst.txt"; 183 const char RELPATH2[] = "dst.txt"; 184 void *buf0, *buf1, *buf; 185 const uint64_t ino1 = 42; 186 const uint64_t ino2 = 43; 187 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 188 const uint64_t fh2 = 0xdeadc0de88c0ffee; 189 off_t fsize1 = 1 << 20; /* 1 MiB */ 190 off_t fsize2 = 1 << 19; /* 512 KiB */ 191 off_t start1 = 1 << 18; 192 off_t start2 = 3 << 17; 193 ssize_t len = m_maxbcachebuf; 194 int fd1, fd2; 195 196 buf0 = malloc(m_maxbcachebuf); 197 memset(buf0, 42, m_maxbcachebuf); 198 199 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 200 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 201 expect_open(ino1, 0, 1, fh1); 202 expect_open(ino2, 0, 1, fh2); 203 expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1, 204 fh2); 205 EXPECT_CALL(*m_mock, process( 206 ResultOf([=](auto in) { 207 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 208 in.header.nodeid == ino1 && 209 in.body.copy_file_range.fh_in == fh1 && 210 (off_t)in.body.copy_file_range.off_in == start1 && 211 in.body.copy_file_range.nodeid_out == ino2 && 212 in.body.copy_file_range.fh_out == fh2 && 213 (off_t)in.body.copy_file_range.off_out == start2 && 214 in.body.copy_file_range.len == (size_t)len && 215 in.body.copy_file_range.flags == 0); 216 }, Eq(true)), 217 _) 218 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 219 SET_OUT_HEADER_LEN(out, write); 220 out.body.write.size = len; 221 }))); 222 223 fd1 = open(FULLPATH1, O_RDONLY); 224 fd2 = open(FULLPATH2, O_RDWR); 225 226 // Prime cache 227 buf = malloc(m_maxbcachebuf); 228 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 229 << strerror(errno); 230 EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf)); 231 232 // Tell the FUSE server overwrite the region we just read 233 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 234 235 // Read again. This should bypass the cache and read direct from server 236 buf1 = malloc(m_maxbcachebuf); 237 memset(buf1, 69, m_maxbcachebuf); 238 start2 -= len; 239 expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1, 240 fh2); 241 ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2)) 242 << strerror(errno); 243 EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf)); 244 245 free(buf1); 246 free(buf0); 247 free(buf); 248 leak(fd1); 249 leak(fd2); 250 } 251 252 /* 253 * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should 254 * fallback to a read/write based implementation. 255 */ 256 TEST_F(CopyFileRange, fallback) 257 { 258 const char FULLPATH1[] = "mountpoint/src.txt"; 259 const char RELPATH1[] = "src.txt"; 260 const char FULLPATH2[] = "mountpoint/dst.txt"; 261 const char RELPATH2[] = "dst.txt"; 262 const uint64_t ino1 = 42; 263 const uint64_t ino2 = 43; 264 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 265 const uint64_t fh2 = 0xdeadc0de88c0ffee; 266 off_t fsize2 = 0; 267 off_t start1 = 0; 268 off_t start2 = 0; 269 const char *contents = "Hello, world!"; 270 ssize_t len; 271 int fd1, fd2; 272 273 len = strlen(contents); 274 275 /* 276 * Ensure that we read to EOF, just so the buffer cache's read size is 277 * predictable. 278 */ 279 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 280 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 281 expect_open(ino1, 0, 1, fh1); 282 expect_open(ino2, 0, 1, fh2); 283 EXPECT_CALL(*m_mock, process( 284 ResultOf([=](auto in) { 285 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 286 in.header.nodeid == ino1 && 287 in.body.copy_file_range.fh_in == fh1 && 288 (off_t)in.body.copy_file_range.off_in == start1 && 289 in.body.copy_file_range.nodeid_out == ino2 && 290 in.body.copy_file_range.fh_out == fh2 && 291 (off_t)in.body.copy_file_range.off_out == start2 && 292 in.body.copy_file_range.len == (size_t)len && 293 in.body.copy_file_range.flags == 0); 294 }, Eq(true)), 295 _) 296 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 297 expect_maybe_lseek(ino1); 298 expect_read(ino1, start1, len, len, contents, 0); 299 expect_write(ino2, start2, len, len, contents); 300 301 fd1 = open(FULLPATH1, O_RDONLY); 302 ASSERT_GE(fd1, 0); 303 fd2 = open(FULLPATH2, O_WRONLY); 304 ASSERT_GE(fd2, 0); 305 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 306 } 307 308 /* fusefs should respect RLIMIT_FSIZE */ 309 TEST_F(CopyFileRange, rlimit_fsize) 310 { 311 const char FULLPATH1[] = "mountpoint/src.txt"; 312 const char RELPATH1[] = "src.txt"; 313 const char FULLPATH2[] = "mountpoint/dst.txt"; 314 const char RELPATH2[] = "dst.txt"; 315 struct rlimit rl; 316 const uint64_t ino1 = 42; 317 const uint64_t ino2 = 43; 318 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 319 const uint64_t fh2 = 0xdeadc0de88c0ffee; 320 off_t fsize1 = 1 << 20; /* 1 MiB */ 321 off_t fsize2 = 1 << 19; /* 512 KiB */ 322 off_t start1 = 1 << 18; 323 off_t start2 = fsize2; 324 ssize_t len = 65536; 325 int fd1, fd2; 326 327 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 328 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 329 expect_open(ino1, 0, 1, fh1); 330 expect_open(ino2, 0, 1, fh2); 331 EXPECT_CALL(*m_mock, process( 332 ResultOf([=](auto in) { 333 return (in.header.opcode == FUSE_COPY_FILE_RANGE); 334 }, Eq(true)), 335 _) 336 ).Times(0); 337 338 rl.rlim_cur = fsize2; 339 rl.rlim_max = 10 * fsize2; 340 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 341 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 342 343 fd1 = open(FULLPATH1, O_RDONLY); 344 fd2 = open(FULLPATH2, O_WRONLY); 345 ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 346 EXPECT_EQ(EFBIG, errno); 347 EXPECT_EQ(1, s_sigxfsz); 348 } 349 350 TEST_F(CopyFileRange, ok) 351 { 352 const char FULLPATH1[] = "mountpoint/src.txt"; 353 const char RELPATH1[] = "src.txt"; 354 const char FULLPATH2[] = "mountpoint/dst.txt"; 355 const char RELPATH2[] = "dst.txt"; 356 const uint64_t ino1 = 42; 357 const uint64_t ino2 = 43; 358 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 359 const uint64_t fh2 = 0xdeadc0de88c0ffee; 360 off_t fsize1 = 1 << 20; /* 1 MiB */ 361 off_t fsize2 = 1 << 19; /* 512 KiB */ 362 off_t start1 = 1 << 18; 363 off_t start2 = 3 << 17; 364 ssize_t len = 65536; 365 int fd1, fd2; 366 367 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 368 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 369 expect_open(ino1, 0, 1, fh1); 370 expect_open(ino2, 0, 1, fh2); 371 EXPECT_CALL(*m_mock, process( 372 ResultOf([=](auto in) { 373 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 374 in.header.nodeid == ino1 && 375 in.body.copy_file_range.fh_in == fh1 && 376 (off_t)in.body.copy_file_range.off_in == start1 && 377 in.body.copy_file_range.nodeid_out == ino2 && 378 in.body.copy_file_range.fh_out == fh2 && 379 (off_t)in.body.copy_file_range.off_out == start2 && 380 in.body.copy_file_range.len == (size_t)len && 381 in.body.copy_file_range.flags == 0); 382 }, Eq(true)), 383 _) 384 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 385 SET_OUT_HEADER_LEN(out, write); 386 out.body.write.size = len; 387 }))); 388 389 fd1 = open(FULLPATH1, O_RDONLY); 390 fd2 = open(FULLPATH2, O_WRONLY); 391 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 392 } 393 394 /* 395 * copy_file_range can make copies within a single file, as long as the ranges 396 * don't overlap. 397 * */ 398 TEST_F(CopyFileRange, same_file) 399 { 400 const char FULLPATH[] = "mountpoint/src.txt"; 401 const char RELPATH[] = "src.txt"; 402 const uint64_t ino = 4; 403 const uint64_t fh = 0xdeadbeefa7ebabe; 404 off_t fsize = 1 << 20; /* 1 MiB */ 405 off_t off_in = 1 << 18; 406 off_t off_out = 3 << 17; 407 ssize_t len = 65536; 408 int fd; 409 410 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 411 expect_open(ino, 0, 1, fh); 412 EXPECT_CALL(*m_mock, process( 413 ResultOf([=](auto in) { 414 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 415 in.header.nodeid == ino && 416 in.body.copy_file_range.fh_in == fh && 417 (off_t)in.body.copy_file_range.off_in == off_in && 418 in.body.copy_file_range.nodeid_out == ino && 419 in.body.copy_file_range.fh_out == fh && 420 (off_t)in.body.copy_file_range.off_out == off_out && 421 in.body.copy_file_range.len == (size_t)len && 422 in.body.copy_file_range.flags == 0); 423 }, Eq(true)), 424 _) 425 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 426 SET_OUT_HEADER_LEN(out, write); 427 out.body.write.size = len; 428 }))); 429 430 fd = open(FULLPATH, O_RDWR); 431 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 432 } 433 434 /* 435 * copy_file_range can extend the size of a file 436 * */ 437 TEST_F(CopyFileRange, extend) 438 { 439 const char FULLPATH[] = "mountpoint/src.txt"; 440 const char RELPATH[] = "src.txt"; 441 struct stat sb; 442 const uint64_t ino = 4; 443 const uint64_t fh = 0xdeadbeefa7ebabe; 444 off_t fsize = 65536; 445 off_t off_in = 0; 446 off_t off_out = 65536; 447 ssize_t len = 65536; 448 int fd; 449 450 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 451 expect_open(ino, 0, 1, fh); 452 EXPECT_CALL(*m_mock, process( 453 ResultOf([=](auto in) { 454 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 455 in.header.nodeid == ino && 456 in.body.copy_file_range.fh_in == fh && 457 (off_t)in.body.copy_file_range.off_in == off_in && 458 in.body.copy_file_range.nodeid_out == ino && 459 in.body.copy_file_range.fh_out == fh && 460 (off_t)in.body.copy_file_range.off_out == off_out && 461 in.body.copy_file_range.len == (size_t)len && 462 in.body.copy_file_range.flags == 0); 463 }, Eq(true)), 464 _) 465 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 466 SET_OUT_HEADER_LEN(out, write); 467 out.body.write.size = len; 468 }))); 469 470 fd = open(FULLPATH, O_RDWR); 471 ASSERT_GE(fd, 0); 472 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 473 474 /* Check that cached attributes were updated appropriately */ 475 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 476 EXPECT_EQ(fsize + len, sb.st_size); 477 478 leak(fd); 479 } 480 481 /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */ 482 TEST_F(CopyFileRange_7_27, fallback) 483 { 484 const char FULLPATH1[] = "mountpoint/src.txt"; 485 const char RELPATH1[] = "src.txt"; 486 const char FULLPATH2[] = "mountpoint/dst.txt"; 487 const char RELPATH2[] = "dst.txt"; 488 const uint64_t ino1 = 42; 489 const uint64_t ino2 = 43; 490 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 491 const uint64_t fh2 = 0xdeadc0de88c0ffee; 492 off_t fsize2 = 0; 493 off_t start1 = 0; 494 off_t start2 = 0; 495 const char *contents = "Hello, world!"; 496 ssize_t len; 497 int fd1, fd2; 498 499 len = strlen(contents); 500 501 /* 502 * Ensure that we read to EOF, just so the buffer cache's read size is 503 * predictable. 504 */ 505 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 506 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 507 expect_open(ino1, 0, 1, fh1); 508 expect_open(ino2, 0, 1, fh2); 509 EXPECT_CALL(*m_mock, process( 510 ResultOf([=](auto in) { 511 return (in.header.opcode == FUSE_COPY_FILE_RANGE); 512 }, Eq(true)), 513 _) 514 ).Times(0); 515 expect_maybe_lseek(ino1); 516 expect_read(ino1, start1, len, len, contents, 0); 517 expect_write(ino2, start2, len, len, contents); 518 519 fd1 = open(FULLPATH1, O_RDONLY); 520 ASSERT_GE(fd1, 0); 521 fd2 = open(FULLPATH2, O_WRONLY); 522 ASSERT_GE(fd2, 0); 523 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 524 } 525 526 527