1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD$ 31 */ 32 33 extern "C" { 34 #include <sys/param.h> 35 #include <sys/mman.h> 36 #include <sys/socket.h> 37 #include <sys/sysctl.h> 38 #include <sys/uio.h> 39 40 #include <aio.h> 41 #include <fcntl.h> 42 #include <semaphore.h> 43 #include <setjmp.h> 44 #include <signal.h> 45 #include <unistd.h> 46 } 47 48 #include "mockfs.hh" 49 #include "utils.hh" 50 51 using namespace testing; 52 53 class Read: public FuseTest { 54 55 public: 56 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 57 { 58 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1); 59 } 60 }; 61 62 class Read_7_8: public FuseTest { 63 public: 64 virtual void SetUp() { 65 m_kernel_minor_version = 8; 66 FuseTest::SetUp(); 67 } 68 69 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 70 { 71 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1); 72 } 73 }; 74 75 class AioRead: public Read { 76 public: 77 virtual void SetUp() { 78 if (!is_unsafe_aio_enabled()) 79 GTEST_SKIP() << 80 "vfs.aio.enable_unsafe must be set for this test"; 81 FuseTest::SetUp(); 82 } 83 }; 84 85 class AsyncRead: public AioRead { 86 virtual void SetUp() { 87 m_init_flags = FUSE_ASYNC_READ; 88 AioRead::SetUp(); 89 } 90 }; 91 92 class ReadAhead: public Read, 93 public WithParamInterface<tuple<bool, int>> 94 { 95 virtual void SetUp() { 96 int val; 97 const char *node = "vfs.maxbcachebuf"; 98 size_t size = sizeof(val); 99 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) 100 << strerror(errno); 101 102 m_maxreadahead = val * get<1>(GetParam()); 103 m_noclusterr = get<0>(GetParam()); 104 Read::SetUp(); 105 } 106 }; 107 108 class ReadSigbus: public Read 109 { 110 public: 111 static jmp_buf s_jmpbuf; 112 static void *s_si_addr; 113 114 void TearDown() { 115 struct sigaction sa; 116 117 bzero(&sa, sizeof(sa)); 118 sa.sa_handler = SIG_DFL; 119 sigaction(SIGBUS, &sa, NULL); 120 121 FuseTest::TearDown(); 122 } 123 124 }; 125 126 static void 127 handle_sigbus(int signo __unused, siginfo_t *info, void *uap __unused) { 128 ReadSigbus::s_si_addr = info->si_addr; 129 longjmp(ReadSigbus::s_jmpbuf, 1); 130 } 131 132 jmp_buf ReadSigbus::s_jmpbuf; 133 void *ReadSigbus::s_si_addr; 134 135 /* AIO reads need to set the header's pid field correctly */ 136 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ 137 TEST_F(AioRead, aio_read) 138 { 139 const char FULLPATH[] = "mountpoint/some_file.txt"; 140 const char RELPATH[] = "some_file.txt"; 141 const char *CONTENTS = "abcdefgh"; 142 uint64_t ino = 42; 143 int fd; 144 ssize_t bufsize = strlen(CONTENTS); 145 uint8_t buf[bufsize]; 146 struct aiocb iocb, *piocb; 147 148 expect_lookup(RELPATH, ino, bufsize); 149 expect_open(ino, 0, 1); 150 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 151 152 fd = open(FULLPATH, O_RDONLY); 153 ASSERT_LE(0, fd) << strerror(errno); 154 155 iocb.aio_nbytes = bufsize; 156 iocb.aio_fildes = fd; 157 iocb.aio_buf = buf; 158 iocb.aio_offset = 0; 159 iocb.aio_sigevent.sigev_notify = SIGEV_NONE; 160 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno); 161 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); 162 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 163 164 leak(fd); 165 } 166 167 /* 168 * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there 169 * is at most one outstanding read operation per file handle 170 */ 171 TEST_F(AioRead, async_read_disabled) 172 { 173 const char FULLPATH[] = "mountpoint/some_file.txt"; 174 const char RELPATH[] = "some_file.txt"; 175 uint64_t ino = 42; 176 int fd; 177 ssize_t bufsize = 50; 178 char buf0[bufsize], buf1[bufsize]; 179 off_t off0 = 0; 180 off_t off1 = m_maxbcachebuf; 181 struct aiocb iocb0, iocb1; 182 volatile sig_atomic_t read_count = 0; 183 184 expect_lookup(RELPATH, ino, 131072); 185 expect_open(ino, 0, 1); 186 EXPECT_CALL(*m_mock, process( 187 ResultOf([=](auto in) { 188 return (in.header.opcode == FUSE_READ && 189 in.header.nodeid == ino && 190 in.body.read.fh == FH && 191 in.body.read.offset == (uint64_t)off0); 192 }, Eq(true)), 193 _) 194 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { 195 read_count++; 196 /* Filesystem is slow to respond */ 197 })); 198 EXPECT_CALL(*m_mock, process( 199 ResultOf([=](auto in) { 200 return (in.header.opcode == FUSE_READ && 201 in.header.nodeid == ino && 202 in.body.read.fh == FH && 203 in.body.read.offset == (uint64_t)off1); 204 }, Eq(true)), 205 _) 206 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { 207 read_count++; 208 /* Filesystem is slow to respond */ 209 })); 210 211 fd = open(FULLPATH, O_RDONLY); 212 ASSERT_LE(0, fd) << strerror(errno); 213 214 /* 215 * Submit two AIO read requests, and respond to neither. If the 216 * filesystem ever gets the second read request, then we failed to 217 * limit outstanding reads. 218 */ 219 iocb0.aio_nbytes = bufsize; 220 iocb0.aio_fildes = fd; 221 iocb0.aio_buf = buf0; 222 iocb0.aio_offset = off0; 223 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; 224 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); 225 226 iocb1.aio_nbytes = bufsize; 227 iocb1.aio_fildes = fd; 228 iocb1.aio_buf = buf1; 229 iocb1.aio_offset = off1; 230 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; 231 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); 232 233 /* 234 * Sleep for awhile to make sure the kernel has had a chance to issue 235 * the second read, even though the first has not yet returned 236 */ 237 nap(); 238 EXPECT_EQ(read_count, 1); 239 240 m_mock->kill_daemon(); 241 /* Wait for AIO activity to complete, but ignore errors */ 242 (void)aio_waitcomplete(NULL, NULL); 243 244 leak(fd); 245 } 246 247 /* 248 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple 249 * simultaneous read requests on the same file handle. 250 */ 251 TEST_F(AsyncRead, async_read) 252 { 253 const char FULLPATH[] = "mountpoint/some_file.txt"; 254 const char RELPATH[] = "some_file.txt"; 255 uint64_t ino = 42; 256 int fd; 257 ssize_t bufsize = 50; 258 char buf0[bufsize], buf1[bufsize]; 259 off_t off0 = 0; 260 off_t off1 = m_maxbcachebuf; 261 off_t fsize = 2 * m_maxbcachebuf; 262 struct aiocb iocb0, iocb1; 263 sem_t sem; 264 265 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 266 267 expect_lookup(RELPATH, ino, fsize); 268 expect_open(ino, 0, 1); 269 EXPECT_CALL(*m_mock, process( 270 ResultOf([=](auto in) { 271 return (in.header.opcode == FUSE_READ && 272 in.header.nodeid == ino && 273 in.body.read.fh == FH && 274 in.body.read.offset == (uint64_t)off0); 275 }, Eq(true)), 276 _) 277 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 278 sem_post(&sem); 279 /* Filesystem is slow to respond */ 280 })); 281 EXPECT_CALL(*m_mock, process( 282 ResultOf([=](auto in) { 283 return (in.header.opcode == FUSE_READ && 284 in.header.nodeid == ino && 285 in.body.read.fh == FH && 286 in.body.read.offset == (uint64_t)off1); 287 }, Eq(true)), 288 _) 289 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 290 sem_post(&sem); 291 /* Filesystem is slow to respond */ 292 })); 293 294 fd = open(FULLPATH, O_RDONLY); 295 ASSERT_LE(0, fd) << strerror(errno); 296 297 /* 298 * Submit two AIO read requests, but respond to neither. Ensure that 299 * we received both. 300 */ 301 iocb0.aio_nbytes = bufsize; 302 iocb0.aio_fildes = fd; 303 iocb0.aio_buf = buf0; 304 iocb0.aio_offset = off0; 305 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; 306 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); 307 308 iocb1.aio_nbytes = bufsize; 309 iocb1.aio_fildes = fd; 310 iocb1.aio_buf = buf1; 311 iocb1.aio_offset = off1; 312 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; 313 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); 314 315 /* Wait until both reads have reached the daemon */ 316 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); 317 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); 318 319 m_mock->kill_daemon(); 320 /* Wait for AIO activity to complete, but ignore errors */ 321 (void)aio_waitcomplete(NULL, NULL); 322 323 leak(fd); 324 } 325 326 /* 0-length reads shouldn't cause any confusion */ 327 TEST_F(Read, direct_io_read_nothing) 328 { 329 const char FULLPATH[] = "mountpoint/some_file.txt"; 330 const char RELPATH[] = "some_file.txt"; 331 uint64_t ino = 42; 332 int fd; 333 uint64_t offset = 100; 334 char buf[80]; 335 336 expect_lookup(RELPATH, ino, offset + 1000); 337 expect_open(ino, FOPEN_DIRECT_IO, 1); 338 339 fd = open(FULLPATH, O_RDONLY); 340 ASSERT_LE(0, fd) << strerror(errno); 341 342 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno); 343 leak(fd); 344 } 345 346 /* 347 * With direct_io, reads should not fill the cache. They should go straight to 348 * the daemon 349 */ 350 TEST_F(Read, direct_io_pread) 351 { 352 const char FULLPATH[] = "mountpoint/some_file.txt"; 353 const char RELPATH[] = "some_file.txt"; 354 const char *CONTENTS = "abcdefgh"; 355 uint64_t ino = 42; 356 int fd; 357 uint64_t offset = 100; 358 ssize_t bufsize = strlen(CONTENTS); 359 uint8_t buf[bufsize]; 360 361 expect_lookup(RELPATH, ino, offset + bufsize); 362 expect_open(ino, FOPEN_DIRECT_IO, 1); 363 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 364 365 fd = open(FULLPATH, O_RDONLY); 366 ASSERT_LE(0, fd) << strerror(errno); 367 368 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 369 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 370 371 // With FOPEN_DIRECT_IO, the cache should be bypassed. The server will 372 // get a 2nd read request. 373 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 374 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 375 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 376 leak(fd); 377 } 378 379 /* 380 * With direct_io, filesystems are allowed to return less data than is 381 * requested. fuse(4) should return a short read to userland. 382 */ 383 TEST_F(Read, direct_io_short_read) 384 { 385 const char FULLPATH[] = "mountpoint/some_file.txt"; 386 const char RELPATH[] = "some_file.txt"; 387 const char *CONTENTS = "abcdefghijklmnop"; 388 uint64_t ino = 42; 389 int fd; 390 uint64_t offset = 100; 391 ssize_t bufsize = strlen(CONTENTS); 392 ssize_t halfbufsize = bufsize / 2; 393 uint8_t buf[bufsize]; 394 395 expect_lookup(RELPATH, ino, offset + bufsize); 396 expect_open(ino, FOPEN_DIRECT_IO, 1); 397 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS); 398 399 fd = open(FULLPATH, O_RDONLY); 400 ASSERT_LE(0, fd) << strerror(errno); 401 402 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset)) 403 << strerror(errno); 404 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize)); 405 leak(fd); 406 } 407 408 TEST_F(Read, eio) 409 { 410 const char FULLPATH[] = "mountpoint/some_file.txt"; 411 const char RELPATH[] = "some_file.txt"; 412 const char *CONTENTS = "abcdefgh"; 413 uint64_t ino = 42; 414 int fd; 415 ssize_t bufsize = strlen(CONTENTS); 416 uint8_t buf[bufsize]; 417 418 expect_lookup(RELPATH, ino, bufsize); 419 expect_open(ino, 0, 1); 420 EXPECT_CALL(*m_mock, process( 421 ResultOf([=](auto in) { 422 return (in.header.opcode == FUSE_READ); 423 }, Eq(true)), 424 _) 425 ).WillOnce(Invoke(ReturnErrno(EIO))); 426 427 fd = open(FULLPATH, O_RDONLY); 428 ASSERT_LE(0, fd) << strerror(errno); 429 430 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno); 431 ASSERT_EQ(EIO, errno); 432 leak(fd); 433 } 434 435 /* 436 * If the server returns a short read when direct io is not in use, that 437 * indicates EOF, because of a server-side truncation. We should invalidate 438 * all cached attributes. We may update the file size, 439 */ 440 TEST_F(Read, eof) 441 { 442 const char FULLPATH[] = "mountpoint/some_file.txt"; 443 const char RELPATH[] = "some_file.txt"; 444 const char *CONTENTS = "abcdefghijklmnop"; 445 uint64_t ino = 42; 446 int fd; 447 uint64_t offset = 100; 448 ssize_t bufsize = strlen(CONTENTS); 449 ssize_t partbufsize = 3 * bufsize / 4; 450 ssize_t r; 451 uint8_t buf[bufsize]; 452 struct stat sb; 453 454 expect_lookup(RELPATH, ino, offset + bufsize); 455 expect_open(ino, 0, 1); 456 expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS); 457 expect_getattr(ino, offset + partbufsize); 458 459 fd = open(FULLPATH, O_RDONLY); 460 ASSERT_LE(0, fd) << strerror(errno); 461 462 r = pread(fd, buf, bufsize, offset); 463 ASSERT_LE(0, r) << strerror(errno); 464 EXPECT_EQ(partbufsize, r) << strerror(errno); 465 ASSERT_EQ(0, fstat(fd, &sb)); 466 EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size); 467 leak(fd); 468 } 469 470 /* Like Read.eof, but causes an entire buffer to be invalidated */ 471 TEST_F(Read, eof_of_whole_buffer) 472 { 473 const char FULLPATH[] = "mountpoint/some_file.txt"; 474 const char RELPATH[] = "some_file.txt"; 475 const char *CONTENTS = "abcdefghijklmnop"; 476 uint64_t ino = 42; 477 int fd; 478 ssize_t bufsize = strlen(CONTENTS); 479 off_t old_filesize = m_maxbcachebuf * 2 + bufsize; 480 uint8_t buf[bufsize]; 481 struct stat sb; 482 483 expect_lookup(RELPATH, ino, old_filesize); 484 expect_open(ino, 0, 1); 485 expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS); 486 expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS); 487 expect_getattr(ino, m_maxbcachebuf); 488 489 fd = open(FULLPATH, O_RDONLY); 490 ASSERT_LE(0, fd) << strerror(errno); 491 492 /* Cache the third block */ 493 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2)) 494 << strerror(errno); 495 /* Try to read the 2nd block, but it's past EOF */ 496 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf)) 497 << strerror(errno); 498 ASSERT_EQ(0, fstat(fd, &sb)); 499 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size); 500 leak(fd); 501 } 502 503 /* 504 * With the keep_cache option, the kernel may keep its read cache across 505 * multiple open(2)s. 506 */ 507 TEST_F(Read, keep_cache) 508 { 509 const char FULLPATH[] = "mountpoint/some_file.txt"; 510 const char RELPATH[] = "some_file.txt"; 511 const char *CONTENTS = "abcdefgh"; 512 uint64_t ino = 42; 513 int fd0, fd1; 514 ssize_t bufsize = strlen(CONTENTS); 515 uint8_t buf[bufsize]; 516 517 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); 518 expect_open(ino, FOPEN_KEEP_CACHE, 2); 519 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 520 521 fd0 = open(FULLPATH, O_RDONLY); 522 ASSERT_LE(0, fd0) << strerror(errno); 523 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 524 525 fd1 = open(FULLPATH, O_RDWR); 526 ASSERT_LE(0, fd1) << strerror(errno); 527 528 /* 529 * This read should be serviced by cache, even though it's on the other 530 * file descriptor 531 */ 532 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno); 533 534 leak(fd0); 535 leak(fd1); 536 } 537 538 /* 539 * Without the keep_cache option, the kernel should drop its read caches on 540 * every open 541 */ 542 TEST_F(Read, keep_cache_disabled) 543 { 544 const char FULLPATH[] = "mountpoint/some_file.txt"; 545 const char RELPATH[] = "some_file.txt"; 546 const char *CONTENTS = "abcdefgh"; 547 uint64_t ino = 42; 548 int fd0, fd1; 549 ssize_t bufsize = strlen(CONTENTS); 550 uint8_t buf[bufsize]; 551 552 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); 553 expect_open(ino, 0, 2); 554 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 555 556 fd0 = open(FULLPATH, O_RDONLY); 557 ASSERT_LE(0, fd0) << strerror(errno); 558 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 559 560 fd1 = open(FULLPATH, O_RDWR); 561 ASSERT_LE(0, fd1) << strerror(errno); 562 563 /* 564 * This read should not be serviced by cache, even though it's on the 565 * original file descriptor 566 */ 567 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 568 ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno); 569 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 570 571 leak(fd0); 572 leak(fd1); 573 } 574 575 TEST_F(Read, mmap) 576 { 577 const char FULLPATH[] = "mountpoint/some_file.txt"; 578 const char RELPATH[] = "some_file.txt"; 579 const char *CONTENTS = "abcdefgh"; 580 uint64_t ino = 42; 581 int fd; 582 ssize_t len; 583 size_t bufsize = strlen(CONTENTS); 584 void *p; 585 586 len = getpagesize(); 587 588 expect_lookup(RELPATH, ino, bufsize); 589 expect_open(ino, 0, 1); 590 EXPECT_CALL(*m_mock, process( 591 ResultOf([=](auto in) { 592 return (in.header.opcode == FUSE_READ && 593 in.header.nodeid == ino && 594 in.body.read.fh == Read::FH && 595 in.body.read.offset == 0 && 596 in.body.read.size == bufsize); 597 }, Eq(true)), 598 _) 599 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 600 out.header.len = sizeof(struct fuse_out_header) + bufsize; 601 memmove(out.body.bytes, CONTENTS, bufsize); 602 }))); 603 604 fd = open(FULLPATH, O_RDONLY); 605 ASSERT_LE(0, fd) << strerror(errno); 606 607 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 608 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 609 610 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); 611 612 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 613 leak(fd); 614 } 615 616 /* Read of an mmap()ed file fails */ 617 TEST_F(ReadSigbus, mmap_eio) 618 { 619 const char FULLPATH[] = "mountpoint/some_file.txt"; 620 const char RELPATH[] = "some_file.txt"; 621 const char *CONTENTS = "abcdefgh"; 622 struct sigaction sa; 623 uint64_t ino = 42; 624 int fd; 625 ssize_t len; 626 size_t bufsize = strlen(CONTENTS); 627 void *p; 628 629 len = getpagesize(); 630 631 expect_lookup(RELPATH, ino, bufsize); 632 expect_open(ino, 0, 1); 633 EXPECT_CALL(*m_mock, process( 634 ResultOf([=](auto in) { 635 return (in.header.opcode == FUSE_READ && 636 in.header.nodeid == ino && 637 in.body.read.fh == Read::FH); 638 }, Eq(true)), 639 _) 640 ).WillRepeatedly(Invoke(ReturnErrno(EIO))); 641 642 fd = open(FULLPATH, O_RDONLY); 643 ASSERT_LE(0, fd) << strerror(errno); 644 645 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 646 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 647 648 /* Accessing the mapped page should return SIGBUS. */ 649 650 bzero(&sa, sizeof(sa)); 651 sa.sa_handler = SIG_DFL; 652 sa.sa_sigaction = handle_sigbus; 653 sa.sa_flags = SA_RESETHAND | SA_SIGINFO; 654 ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno); 655 if (setjmp(ReadSigbus::s_jmpbuf) == 0) { 656 atomic_signal_fence(std::memory_order::memory_order_seq_cst); 657 volatile char x __unused = *(volatile char*)p; 658 FAIL() << "shouldn't get here"; 659 } 660 661 ASSERT_EQ(p, ReadSigbus::s_si_addr); 662 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 663 leak(fd); 664 } 665 666 /* 667 * A read via mmap comes up short, indicating that the file was truncated 668 * server-side. 669 */ 670 TEST_F(Read, mmap_eof) 671 { 672 const char FULLPATH[] = "mountpoint/some_file.txt"; 673 const char RELPATH[] = "some_file.txt"; 674 const char *CONTENTS = "abcdefgh"; 675 uint64_t ino = 42; 676 int fd; 677 ssize_t len; 678 size_t bufsize = strlen(CONTENTS); 679 struct stat sb; 680 void *p; 681 682 len = getpagesize(); 683 684 expect_lookup(RELPATH, ino, m_maxbcachebuf); 685 expect_open(ino, 0, 1); 686 EXPECT_CALL(*m_mock, process( 687 ResultOf([=](auto in) { 688 return (in.header.opcode == FUSE_READ && 689 in.header.nodeid == ino && 690 in.body.read.fh == Read::FH && 691 in.body.read.offset == 0 && 692 in.body.read.size == (uint32_t)m_maxbcachebuf); 693 }, Eq(true)), 694 _) 695 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 696 out.header.len = sizeof(struct fuse_out_header) + bufsize; 697 memmove(out.body.bytes, CONTENTS, bufsize); 698 }))); 699 expect_getattr(ino, bufsize); 700 701 fd = open(FULLPATH, O_RDONLY); 702 ASSERT_LE(0, fd) << strerror(errno); 703 704 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 705 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 706 707 /* The file size should be automatically truncated */ 708 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); 709 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 710 EXPECT_EQ((off_t)bufsize, sb.st_size); 711 712 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 713 leak(fd); 714 } 715 716 /* 717 * During VOP_GETPAGES, the FUSE server fails a FUSE_GETATTR operation. This 718 * almost certainly indicates a buggy FUSE server, and our goal should be not 719 * to panic. Instead, generate SIGBUS. 720 */ 721 TEST_F(ReadSigbus, mmap_getblksz_fail) 722 { 723 const char FULLPATH[] = "mountpoint/some_file.txt"; 724 const char RELPATH[] = "some_file.txt"; 725 const char *CONTENTS = "abcdefgh"; 726 struct sigaction sa; 727 Sequence seq; 728 uint64_t ino = 42; 729 int fd; 730 ssize_t len; 731 size_t bufsize = strlen(CONTENTS); 732 mode_t mode = S_IFREG | 0644; 733 void *p; 734 735 len = getpagesize(); 736 737 FuseTest::expect_lookup(RELPATH, ino, mode, bufsize, 1, 0); 738 /* Expect two GETATTR calls that succeed, followed by one that fail. */ 739 EXPECT_CALL(*m_mock, process( 740 ResultOf([=](auto in) { 741 return (in.header.opcode == FUSE_GETATTR && 742 in.header.nodeid == ino); 743 }, Eq(true)), 744 _) 745 ).Times(2) 746 .InSequence(seq) 747 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 748 SET_OUT_HEADER_LEN(out, attr); 749 out.body.attr.attr.ino = ino; 750 out.body.attr.attr.mode = mode; 751 out.body.attr.attr.size = bufsize; 752 out.body.attr.attr_valid = 0; 753 }))); 754 EXPECT_CALL(*m_mock, process( 755 ResultOf([=](auto in) { 756 return (in.header.opcode == FUSE_GETATTR && 757 in.header.nodeid == ino); 758 }, Eq(true)), 759 _) 760 ).InSequence(seq) 761 .WillRepeatedly(Invoke(ReturnErrno(EIO))); 762 expect_open(ino, 0, 1); 763 EXPECT_CALL(*m_mock, process( 764 ResultOf([=](auto in) { 765 return (in.header.opcode == FUSE_READ); 766 }, Eq(true)), 767 _) 768 ).Times(0); 769 770 fd = open(FULLPATH, O_RDONLY); 771 ASSERT_LE(0, fd) << strerror(errno); 772 773 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 774 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 775 776 /* Accessing the mapped page should return SIGBUS. */ 777 bzero(&sa, sizeof(sa)); 778 sa.sa_handler = SIG_DFL; 779 sa.sa_sigaction = handle_sigbus; 780 sa.sa_flags = SA_RESETHAND | SA_SIGINFO; 781 ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno); 782 if (setjmp(ReadSigbus::s_jmpbuf) == 0) { 783 atomic_signal_fence(std::memory_order::memory_order_seq_cst); 784 volatile char x __unused = *(volatile char*)p; 785 FAIL() << "shouldn't get here"; 786 } 787 788 ASSERT_EQ(p, ReadSigbus::s_si_addr); 789 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 790 leak(fd); 791 } 792 793 /* 794 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass 795 * cache and to straight to the daemon 796 */ 797 TEST_F(Read, o_direct) 798 { 799 const char FULLPATH[] = "mountpoint/some_file.txt"; 800 const char RELPATH[] = "some_file.txt"; 801 const char *CONTENTS = "abcdefgh"; 802 uint64_t ino = 42; 803 int fd; 804 ssize_t bufsize = strlen(CONTENTS); 805 uint8_t buf[bufsize]; 806 807 expect_lookup(RELPATH, ino, bufsize); 808 expect_open(ino, 0, 1); 809 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 810 811 fd = open(FULLPATH, O_RDONLY); 812 ASSERT_LE(0, fd) << strerror(errno); 813 814 // Fill the cache 815 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 816 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 817 818 // Reads with o_direct should bypass the cache 819 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 820 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); 821 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); 822 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 823 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 824 825 leak(fd); 826 } 827 828 TEST_F(Read, pread) 829 { 830 const char FULLPATH[] = "mountpoint/some_file.txt"; 831 const char RELPATH[] = "some_file.txt"; 832 const char *CONTENTS = "abcdefgh"; 833 uint64_t ino = 42; 834 int fd; 835 /* 836 * Set offset to a maxbcachebuf boundary so we'll be sure what offset 837 * to read from. Without this, the read might start at a lower offset. 838 */ 839 uint64_t offset = m_maxbcachebuf; 840 ssize_t bufsize = strlen(CONTENTS); 841 uint8_t buf[bufsize]; 842 843 expect_lookup(RELPATH, ino, offset + bufsize); 844 expect_open(ino, 0, 1); 845 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 846 847 fd = open(FULLPATH, O_RDONLY); 848 ASSERT_LE(0, fd) << strerror(errno); 849 850 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 851 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 852 leak(fd); 853 } 854 855 TEST_F(Read, read) 856 { 857 const char FULLPATH[] = "mountpoint/some_file.txt"; 858 const char RELPATH[] = "some_file.txt"; 859 const char *CONTENTS = "abcdefgh"; 860 uint64_t ino = 42; 861 int fd; 862 ssize_t bufsize = strlen(CONTENTS); 863 uint8_t buf[bufsize]; 864 865 expect_lookup(RELPATH, ino, bufsize); 866 expect_open(ino, 0, 1); 867 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 868 869 fd = open(FULLPATH, O_RDONLY); 870 ASSERT_LE(0, fd) << strerror(errno); 871 872 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 873 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 874 875 leak(fd); 876 } 877 878 TEST_F(Read_7_8, read) 879 { 880 const char FULLPATH[] = "mountpoint/some_file.txt"; 881 const char RELPATH[] = "some_file.txt"; 882 const char *CONTENTS = "abcdefgh"; 883 uint64_t ino = 42; 884 int fd; 885 ssize_t bufsize = strlen(CONTENTS); 886 uint8_t buf[bufsize]; 887 888 expect_lookup(RELPATH, ino, bufsize); 889 expect_open(ino, 0, 1); 890 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 891 892 fd = open(FULLPATH, O_RDONLY); 893 ASSERT_LE(0, fd) << strerror(errno); 894 895 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 896 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 897 898 leak(fd); 899 } 900 901 /* 902 * If cacheing is enabled, the kernel should try to read an entire cache block 903 * at a time. 904 */ 905 TEST_F(Read, cache_block) 906 { 907 const char FULLPATH[] = "mountpoint/some_file.txt"; 908 const char RELPATH[] = "some_file.txt"; 909 const char *CONTENTS0 = "abcdefghijklmnop"; 910 uint64_t ino = 42; 911 int fd; 912 ssize_t bufsize = 8; 913 ssize_t filesize = m_maxbcachebuf * 2; 914 char *contents; 915 char buf[bufsize]; 916 const char *contents1 = CONTENTS0 + bufsize; 917 918 contents = (char*)calloc(1, filesize); 919 ASSERT_NE(nullptr, contents); 920 memmove(contents, CONTENTS0, strlen(CONTENTS0)); 921 922 expect_lookup(RELPATH, ino, filesize); 923 expect_open(ino, 0, 1); 924 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf, 925 contents); 926 927 fd = open(FULLPATH, O_RDONLY); 928 ASSERT_LE(0, fd) << strerror(errno); 929 930 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 931 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); 932 933 /* A subsequent read should be serviced by cache */ 934 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 935 ASSERT_EQ(0, memcmp(buf, contents1, bufsize)); 936 leak(fd); 937 free(contents); 938 } 939 940 /* Reading with sendfile should work (though it obviously won't be 0-copy) */ 941 TEST_F(Read, sendfile) 942 { 943 const char FULLPATH[] = "mountpoint/some_file.txt"; 944 const char RELPATH[] = "some_file.txt"; 945 const char *CONTENTS = "abcdefgh"; 946 uint64_t ino = 42; 947 int fd; 948 size_t bufsize = strlen(CONTENTS); 949 uint8_t buf[bufsize]; 950 int sp[2]; 951 off_t sbytes; 952 953 expect_lookup(RELPATH, ino, bufsize); 954 expect_open(ino, 0, 1); 955 EXPECT_CALL(*m_mock, process( 956 ResultOf([=](auto in) { 957 return (in.header.opcode == FUSE_READ && 958 in.header.nodeid == ino && 959 in.body.read.fh == Read::FH && 960 in.body.read.offset == 0 && 961 in.body.read.size == bufsize); 962 }, Eq(true)), 963 _) 964 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 965 out.header.len = sizeof(struct fuse_out_header) + bufsize; 966 memmove(out.body.bytes, CONTENTS, bufsize); 967 }))); 968 969 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) 970 << strerror(errno); 971 fd = open(FULLPATH, O_RDONLY); 972 ASSERT_LE(0, fd) << strerror(errno); 973 974 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)) 975 << strerror(errno); 976 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize)) 977 << strerror(errno); 978 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 979 980 close(sp[1]); 981 close(sp[0]); 982 leak(fd); 983 } 984 985 /* sendfile should fail gracefully if fuse declines the read */ 986 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */ 987 TEST_F(Read, sendfile_eio) 988 { 989 const char FULLPATH[] = "mountpoint/some_file.txt"; 990 const char RELPATH[] = "some_file.txt"; 991 const char *CONTENTS = "abcdefgh"; 992 uint64_t ino = 42; 993 int fd; 994 ssize_t bufsize = strlen(CONTENTS); 995 int sp[2]; 996 off_t sbytes; 997 998 expect_lookup(RELPATH, ino, bufsize); 999 expect_open(ino, 0, 1); 1000 EXPECT_CALL(*m_mock, process( 1001 ResultOf([=](auto in) { 1002 return (in.header.opcode == FUSE_READ); 1003 }, Eq(true)), 1004 _) 1005 ).WillOnce(Invoke(ReturnErrno(EIO))); 1006 1007 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) 1008 << strerror(errno); 1009 fd = open(FULLPATH, O_RDONLY); 1010 ASSERT_LE(0, fd) << strerror(errno); 1011 1012 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)); 1013 1014 close(sp[1]); 1015 close(sp[0]); 1016 leak(fd); 1017 } 1018 1019 /* 1020 * Sequential reads should use readahead. And if allowed, large reads should 1021 * be clustered. 1022 */ 1023 TEST_P(ReadAhead, readahead) { 1024 const char FULLPATH[] = "mountpoint/some_file.txt"; 1025 const char RELPATH[] = "some_file.txt"; 1026 uint64_t ino = 42; 1027 int fd, maxcontig, clustersize; 1028 ssize_t bufsize = 4 * m_maxbcachebuf; 1029 ssize_t filesize = bufsize; 1030 uint64_t len; 1031 char *rbuf, *contents; 1032 off_t offs; 1033 1034 contents = (char*)malloc(filesize); 1035 ASSERT_NE(nullptr, contents); 1036 memset(contents, 'X', filesize); 1037 rbuf = (char*)calloc(1, bufsize); 1038 1039 expect_lookup(RELPATH, ino, filesize); 1040 expect_open(ino, 0, 1); 1041 maxcontig = m_noclusterr ? m_maxbcachebuf : 1042 m_maxbcachebuf + m_maxreadahead; 1043 clustersize = MIN(maxcontig, m_maxphys); 1044 for (offs = 0; offs < bufsize; offs += clustersize) { 1045 len = std::min((size_t)clustersize, (size_t)(filesize - offs)); 1046 expect_read(ino, offs, len, len, contents + offs); 1047 } 1048 1049 fd = open(FULLPATH, O_RDONLY); 1050 ASSERT_LE(0, fd) << strerror(errno); 1051 1052 /* Set the internal readahead counter to a "large" value */ 1053 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno); 1054 1055 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno); 1056 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize)); 1057 1058 leak(fd); 1059 free(rbuf); 1060 free(contents); 1061 } 1062 1063 INSTANTIATE_TEST_CASE_P(RA, ReadAhead, 1064 Values(tuple<bool, int>(false, 0), 1065 tuple<bool, int>(false, 1), 1066 tuple<bool, int>(false, 2), 1067 tuple<bool, int>(false, 3), 1068 tuple<bool, int>(true, 0), 1069 tuple<bool, int>(true, 1), 1070 tuple<bool, int>(true, 2))); 1071