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 <unistd.h> 44 } 45 46 #include "mockfs.hh" 47 #include "utils.hh" 48 49 using namespace testing; 50 51 class Read: public FuseTest { 52 53 public: 54 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 55 { 56 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1); 57 } 58 }; 59 60 class Read_7_8: public FuseTest { 61 public: 62 virtual void SetUp() { 63 m_kernel_minor_version = 8; 64 FuseTest::SetUp(); 65 } 66 67 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 68 { 69 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1); 70 } 71 }; 72 73 class AioRead: public Read { 74 public: 75 virtual void SetUp() { 76 if (!is_unsafe_aio_enabled()) 77 GTEST_SKIP() << 78 "vfs.aio.enable_unsafe must be set for this test"; 79 FuseTest::SetUp(); 80 } 81 }; 82 83 class AsyncRead: public AioRead { 84 virtual void SetUp() { 85 m_init_flags = FUSE_ASYNC_READ; 86 AioRead::SetUp(); 87 } 88 }; 89 90 class ReadAhead: public Read, 91 public WithParamInterface<tuple<bool, int>> 92 { 93 virtual void SetUp() { 94 int val; 95 const char *node = "vfs.maxbcachebuf"; 96 size_t size = sizeof(val); 97 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) 98 << strerror(errno); 99 100 m_maxreadahead = val * get<1>(GetParam()); 101 m_noclusterr = get<0>(GetParam()); 102 Read::SetUp(); 103 } 104 }; 105 106 /* AIO reads need to set the header's pid field correctly */ 107 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ 108 TEST_F(AioRead, aio_read) 109 { 110 const char FULLPATH[] = "mountpoint/some_file.txt"; 111 const char RELPATH[] = "some_file.txt"; 112 const char *CONTENTS = "abcdefgh"; 113 uint64_t ino = 42; 114 int fd; 115 ssize_t bufsize = strlen(CONTENTS); 116 uint8_t buf[bufsize]; 117 struct aiocb iocb, *piocb; 118 119 expect_lookup(RELPATH, ino, bufsize); 120 expect_open(ino, 0, 1); 121 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 122 123 fd = open(FULLPATH, O_RDONLY); 124 ASSERT_LE(0, fd) << strerror(errno); 125 126 iocb.aio_nbytes = bufsize; 127 iocb.aio_fildes = fd; 128 iocb.aio_buf = buf; 129 iocb.aio_offset = 0; 130 iocb.aio_sigevent.sigev_notify = SIGEV_NONE; 131 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno); 132 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); 133 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 134 135 leak(fd); 136 } 137 138 /* 139 * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there 140 * is at most one outstanding read operation per file handle 141 */ 142 TEST_F(AioRead, async_read_disabled) 143 { 144 const char FULLPATH[] = "mountpoint/some_file.txt"; 145 const char RELPATH[] = "some_file.txt"; 146 uint64_t ino = 42; 147 int fd; 148 ssize_t bufsize = 50; 149 char buf0[bufsize], buf1[bufsize]; 150 off_t off0 = 0; 151 off_t off1 = m_maxbcachebuf; 152 struct aiocb iocb0, iocb1; 153 volatile sig_atomic_t read_count = 0; 154 155 expect_lookup(RELPATH, ino, 131072); 156 expect_open(ino, 0, 1); 157 EXPECT_CALL(*m_mock, process( 158 ResultOf([=](auto in) { 159 return (in.header.opcode == FUSE_READ && 160 in.header.nodeid == ino && 161 in.body.read.fh == FH && 162 in.body.read.offset == (uint64_t)off0); 163 }, Eq(true)), 164 _) 165 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { 166 read_count++; 167 /* Filesystem is slow to respond */ 168 })); 169 EXPECT_CALL(*m_mock, process( 170 ResultOf([=](auto in) { 171 return (in.header.opcode == FUSE_READ && 172 in.header.nodeid == ino && 173 in.body.read.fh == FH && 174 in.body.read.offset == (uint64_t)off1); 175 }, Eq(true)), 176 _) 177 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { 178 read_count++; 179 /* Filesystem is slow to respond */ 180 })); 181 182 fd = open(FULLPATH, O_RDONLY); 183 ASSERT_LE(0, fd) << strerror(errno); 184 185 /* 186 * Submit two AIO read requests, and respond to neither. If the 187 * filesystem ever gets the second read request, then we failed to 188 * limit outstanding reads. 189 */ 190 iocb0.aio_nbytes = bufsize; 191 iocb0.aio_fildes = fd; 192 iocb0.aio_buf = buf0; 193 iocb0.aio_offset = off0; 194 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; 195 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); 196 197 iocb1.aio_nbytes = bufsize; 198 iocb1.aio_fildes = fd; 199 iocb1.aio_buf = buf1; 200 iocb1.aio_offset = off1; 201 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; 202 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); 203 204 /* 205 * Sleep for awhile to make sure the kernel has had a chance to issue 206 * the second read, even though the first has not yet returned 207 */ 208 nap(); 209 EXPECT_EQ(read_count, 1); 210 211 m_mock->kill_daemon(); 212 /* Wait for AIO activity to complete, but ignore errors */ 213 (void)aio_waitcomplete(NULL, NULL); 214 215 leak(fd); 216 } 217 218 /* 219 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple 220 * simultaneous read requests on the same file handle. 221 */ 222 TEST_F(AsyncRead, async_read) 223 { 224 const char FULLPATH[] = "mountpoint/some_file.txt"; 225 const char RELPATH[] = "some_file.txt"; 226 uint64_t ino = 42; 227 int fd; 228 ssize_t bufsize = 50; 229 char buf0[bufsize], buf1[bufsize]; 230 off_t off0 = 0; 231 off_t off1 = m_maxbcachebuf; 232 off_t fsize = 2 * m_maxbcachebuf; 233 struct aiocb iocb0, iocb1; 234 sem_t sem; 235 236 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 237 238 expect_lookup(RELPATH, ino, fsize); 239 expect_open(ino, 0, 1); 240 EXPECT_CALL(*m_mock, process( 241 ResultOf([=](auto in) { 242 return (in.header.opcode == FUSE_READ && 243 in.header.nodeid == ino && 244 in.body.read.fh == FH && 245 in.body.read.offset == (uint64_t)off0); 246 }, Eq(true)), 247 _) 248 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 249 sem_post(&sem); 250 /* Filesystem is slow to respond */ 251 })); 252 EXPECT_CALL(*m_mock, process( 253 ResultOf([=](auto in) { 254 return (in.header.opcode == FUSE_READ && 255 in.header.nodeid == ino && 256 in.body.read.fh == FH && 257 in.body.read.offset == (uint64_t)off1); 258 }, Eq(true)), 259 _) 260 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 261 sem_post(&sem); 262 /* Filesystem is slow to respond */ 263 })); 264 265 fd = open(FULLPATH, O_RDONLY); 266 ASSERT_LE(0, fd) << strerror(errno); 267 268 /* 269 * Submit two AIO read requests, but respond to neither. Ensure that 270 * we received both. 271 */ 272 iocb0.aio_nbytes = bufsize; 273 iocb0.aio_fildes = fd; 274 iocb0.aio_buf = buf0; 275 iocb0.aio_offset = off0; 276 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; 277 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); 278 279 iocb1.aio_nbytes = bufsize; 280 iocb1.aio_fildes = fd; 281 iocb1.aio_buf = buf1; 282 iocb1.aio_offset = off1; 283 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; 284 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); 285 286 /* Wait until both reads have reached the daemon */ 287 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); 288 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); 289 290 m_mock->kill_daemon(); 291 /* Wait for AIO activity to complete, but ignore errors */ 292 (void)aio_waitcomplete(NULL, NULL); 293 294 leak(fd); 295 } 296 297 /* 0-length reads shouldn't cause any confusion */ 298 TEST_F(Read, direct_io_read_nothing) 299 { 300 const char FULLPATH[] = "mountpoint/some_file.txt"; 301 const char RELPATH[] = "some_file.txt"; 302 uint64_t ino = 42; 303 int fd; 304 uint64_t offset = 100; 305 char buf[80]; 306 307 expect_lookup(RELPATH, ino, offset + 1000); 308 expect_open(ino, FOPEN_DIRECT_IO, 1); 309 310 fd = open(FULLPATH, O_RDONLY); 311 ASSERT_LE(0, fd) << strerror(errno); 312 313 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno); 314 leak(fd); 315 } 316 317 /* 318 * With direct_io, reads should not fill the cache. They should go straight to 319 * the daemon 320 */ 321 TEST_F(Read, direct_io_pread) 322 { 323 const char FULLPATH[] = "mountpoint/some_file.txt"; 324 const char RELPATH[] = "some_file.txt"; 325 const char *CONTENTS = "abcdefgh"; 326 uint64_t ino = 42; 327 int fd; 328 uint64_t offset = 100; 329 ssize_t bufsize = strlen(CONTENTS); 330 uint8_t buf[bufsize]; 331 332 expect_lookup(RELPATH, ino, offset + bufsize); 333 expect_open(ino, FOPEN_DIRECT_IO, 1); 334 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 335 336 fd = open(FULLPATH, O_RDONLY); 337 ASSERT_LE(0, fd) << strerror(errno); 338 339 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 340 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 341 342 // With FOPEN_DIRECT_IO, the cache should be bypassed. The server will 343 // get a 2nd read request. 344 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 345 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 346 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 347 leak(fd); 348 } 349 350 /* 351 * With direct_io, filesystems are allowed to return less data than is 352 * requested. fuse(4) should return a short read to userland. 353 */ 354 TEST_F(Read, direct_io_short_read) 355 { 356 const char FULLPATH[] = "mountpoint/some_file.txt"; 357 const char RELPATH[] = "some_file.txt"; 358 const char *CONTENTS = "abcdefghijklmnop"; 359 uint64_t ino = 42; 360 int fd; 361 uint64_t offset = 100; 362 ssize_t bufsize = strlen(CONTENTS); 363 ssize_t halfbufsize = bufsize / 2; 364 uint8_t buf[bufsize]; 365 366 expect_lookup(RELPATH, ino, offset + bufsize); 367 expect_open(ino, FOPEN_DIRECT_IO, 1); 368 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS); 369 370 fd = open(FULLPATH, O_RDONLY); 371 ASSERT_LE(0, fd) << strerror(errno); 372 373 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset)) 374 << strerror(errno); 375 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize)); 376 leak(fd); 377 } 378 379 TEST_F(Read, eio) 380 { 381 const char FULLPATH[] = "mountpoint/some_file.txt"; 382 const char RELPATH[] = "some_file.txt"; 383 const char *CONTENTS = "abcdefgh"; 384 uint64_t ino = 42; 385 int fd; 386 ssize_t bufsize = strlen(CONTENTS); 387 uint8_t buf[bufsize]; 388 389 expect_lookup(RELPATH, ino, bufsize); 390 expect_open(ino, 0, 1); 391 EXPECT_CALL(*m_mock, process( 392 ResultOf([=](auto in) { 393 return (in.header.opcode == FUSE_READ); 394 }, Eq(true)), 395 _) 396 ).WillOnce(Invoke(ReturnErrno(EIO))); 397 398 fd = open(FULLPATH, O_RDONLY); 399 ASSERT_LE(0, fd) << strerror(errno); 400 401 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno); 402 ASSERT_EQ(EIO, errno); 403 leak(fd); 404 } 405 406 /* 407 * If the server returns a short read when direct io is not in use, that 408 * indicates EOF, because of a server-side truncation. We should invalidate 409 * all cached attributes. We may update the file size, 410 */ 411 TEST_F(Read, eof) 412 { 413 const char FULLPATH[] = "mountpoint/some_file.txt"; 414 const char RELPATH[] = "some_file.txt"; 415 const char *CONTENTS = "abcdefghijklmnop"; 416 uint64_t ino = 42; 417 int fd; 418 uint64_t offset = 100; 419 ssize_t bufsize = strlen(CONTENTS); 420 ssize_t partbufsize = 3 * bufsize / 4; 421 ssize_t r; 422 uint8_t buf[bufsize]; 423 struct stat sb; 424 425 expect_lookup(RELPATH, ino, offset + bufsize); 426 expect_open(ino, 0, 1); 427 expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS); 428 expect_getattr(ino, offset + partbufsize); 429 430 fd = open(FULLPATH, O_RDONLY); 431 ASSERT_LE(0, fd) << strerror(errno); 432 433 r = pread(fd, buf, bufsize, offset); 434 ASSERT_LE(0, r) << strerror(errno); 435 EXPECT_EQ(partbufsize, r) << strerror(errno); 436 ASSERT_EQ(0, fstat(fd, &sb)); 437 EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size); 438 leak(fd); 439 } 440 441 /* Like Read.eof, but causes an entire buffer to be invalidated */ 442 TEST_F(Read, eof_of_whole_buffer) 443 { 444 const char FULLPATH[] = "mountpoint/some_file.txt"; 445 const char RELPATH[] = "some_file.txt"; 446 const char *CONTENTS = "abcdefghijklmnop"; 447 uint64_t ino = 42; 448 int fd; 449 ssize_t bufsize = strlen(CONTENTS); 450 off_t old_filesize = m_maxbcachebuf * 2 + bufsize; 451 uint8_t buf[bufsize]; 452 struct stat sb; 453 454 expect_lookup(RELPATH, ino, old_filesize); 455 expect_open(ino, 0, 1); 456 expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS); 457 expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS); 458 expect_getattr(ino, m_maxbcachebuf); 459 460 fd = open(FULLPATH, O_RDONLY); 461 ASSERT_LE(0, fd) << strerror(errno); 462 463 /* Cache the third block */ 464 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2)) 465 << strerror(errno); 466 /* Try to read the 2nd block, but it's past EOF */ 467 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf)) 468 << strerror(errno); 469 ASSERT_EQ(0, fstat(fd, &sb)); 470 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size); 471 leak(fd); 472 } 473 474 /* 475 * With the keep_cache option, the kernel may keep its read cache across 476 * multiple open(2)s. 477 */ 478 TEST_F(Read, keep_cache) 479 { 480 const char FULLPATH[] = "mountpoint/some_file.txt"; 481 const char RELPATH[] = "some_file.txt"; 482 const char *CONTENTS = "abcdefgh"; 483 uint64_t ino = 42; 484 int fd0, fd1; 485 ssize_t bufsize = strlen(CONTENTS); 486 uint8_t buf[bufsize]; 487 488 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); 489 expect_open(ino, FOPEN_KEEP_CACHE, 2); 490 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 491 492 fd0 = open(FULLPATH, O_RDONLY); 493 ASSERT_LE(0, fd0) << strerror(errno); 494 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 495 496 fd1 = open(FULLPATH, O_RDWR); 497 ASSERT_LE(0, fd1) << strerror(errno); 498 499 /* 500 * This read should be serviced by cache, even though it's on the other 501 * file descriptor 502 */ 503 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno); 504 505 leak(fd0); 506 leak(fd1); 507 } 508 509 /* 510 * Without the keep_cache option, the kernel should drop its read caches on 511 * every open 512 */ 513 TEST_F(Read, keep_cache_disabled) 514 { 515 const char FULLPATH[] = "mountpoint/some_file.txt"; 516 const char RELPATH[] = "some_file.txt"; 517 const char *CONTENTS = "abcdefgh"; 518 uint64_t ino = 42; 519 int fd0, fd1; 520 ssize_t bufsize = strlen(CONTENTS); 521 uint8_t buf[bufsize]; 522 523 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); 524 expect_open(ino, 0, 2); 525 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 526 527 fd0 = open(FULLPATH, O_RDONLY); 528 ASSERT_LE(0, fd0) << strerror(errno); 529 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 530 531 fd1 = open(FULLPATH, O_RDWR); 532 ASSERT_LE(0, fd1) << strerror(errno); 533 534 /* 535 * This read should not be serviced by cache, even though it's on the 536 * original file descriptor 537 */ 538 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 539 ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno); 540 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); 541 542 leak(fd0); 543 leak(fd1); 544 } 545 546 TEST_F(Read, mmap) 547 { 548 const char FULLPATH[] = "mountpoint/some_file.txt"; 549 const char RELPATH[] = "some_file.txt"; 550 const char *CONTENTS = "abcdefgh"; 551 uint64_t ino = 42; 552 int fd; 553 ssize_t len; 554 size_t bufsize = strlen(CONTENTS); 555 void *p; 556 557 len = getpagesize(); 558 559 expect_lookup(RELPATH, ino, bufsize); 560 expect_open(ino, 0, 1); 561 EXPECT_CALL(*m_mock, process( 562 ResultOf([=](auto in) { 563 return (in.header.opcode == FUSE_READ && 564 in.header.nodeid == ino && 565 in.body.read.fh == Read::FH && 566 in.body.read.offset == 0 && 567 in.body.read.size == bufsize); 568 }, Eq(true)), 569 _) 570 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 571 out.header.len = sizeof(struct fuse_out_header) + bufsize; 572 memmove(out.body.bytes, CONTENTS, bufsize); 573 }))); 574 575 fd = open(FULLPATH, O_RDONLY); 576 ASSERT_LE(0, fd) << strerror(errno); 577 578 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 579 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 580 581 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); 582 583 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 584 leak(fd); 585 } 586 587 /* 588 * A read via mmap comes up short, indicating that the file was truncated 589 * server-side. 590 */ 591 TEST_F(Read, mmap_eof) 592 { 593 const char FULLPATH[] = "mountpoint/some_file.txt"; 594 const char RELPATH[] = "some_file.txt"; 595 const char *CONTENTS = "abcdefgh"; 596 uint64_t ino = 42; 597 int fd; 598 ssize_t len; 599 size_t bufsize = strlen(CONTENTS); 600 struct stat sb; 601 void *p; 602 603 len = getpagesize(); 604 605 expect_lookup(RELPATH, ino, m_maxbcachebuf); 606 expect_open(ino, 0, 1); 607 EXPECT_CALL(*m_mock, process( 608 ResultOf([=](auto in) { 609 return (in.header.opcode == FUSE_READ && 610 in.header.nodeid == ino && 611 in.body.read.fh == Read::FH && 612 in.body.read.offset == 0 && 613 in.body.read.size == (uint32_t)m_maxbcachebuf); 614 }, Eq(true)), 615 _) 616 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 617 out.header.len = sizeof(struct fuse_out_header) + bufsize; 618 memmove(out.body.bytes, CONTENTS, bufsize); 619 }))); 620 expect_getattr(ino, bufsize); 621 622 fd = open(FULLPATH, O_RDONLY); 623 ASSERT_LE(0, fd) << strerror(errno); 624 625 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 626 ASSERT_NE(MAP_FAILED, p) << strerror(errno); 627 628 /* The file size should be automatically truncated */ 629 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); 630 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 631 EXPECT_EQ((off_t)bufsize, sb.st_size); 632 633 ASSERT_EQ(0, munmap(p, len)) << strerror(errno); 634 leak(fd); 635 } 636 637 /* 638 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass 639 * cache and to straight to the daemon 640 */ 641 TEST_F(Read, o_direct) 642 { 643 const char FULLPATH[] = "mountpoint/some_file.txt"; 644 const char RELPATH[] = "some_file.txt"; 645 const char *CONTENTS = "abcdefgh"; 646 uint64_t ino = 42; 647 int fd; 648 ssize_t bufsize = strlen(CONTENTS); 649 uint8_t buf[bufsize]; 650 651 expect_lookup(RELPATH, ino, bufsize); 652 expect_open(ino, 0, 1); 653 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 654 655 fd = open(FULLPATH, O_RDONLY); 656 ASSERT_LE(0, fd) << strerror(errno); 657 658 // Fill the cache 659 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 660 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 661 662 // Reads with o_direct should bypass the cache 663 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 664 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); 665 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); 666 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 667 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 668 669 leak(fd); 670 } 671 672 TEST_F(Read, pread) 673 { 674 const char FULLPATH[] = "mountpoint/some_file.txt"; 675 const char RELPATH[] = "some_file.txt"; 676 const char *CONTENTS = "abcdefgh"; 677 uint64_t ino = 42; 678 int fd; 679 /* 680 * Set offset to a maxbcachebuf boundary so we'll be sure what offset 681 * to read from. Without this, the read might start at a lower offset. 682 */ 683 uint64_t offset = m_maxbcachebuf; 684 ssize_t bufsize = strlen(CONTENTS); 685 uint8_t buf[bufsize]; 686 687 expect_lookup(RELPATH, ino, offset + bufsize); 688 expect_open(ino, 0, 1); 689 expect_read(ino, offset, bufsize, bufsize, CONTENTS); 690 691 fd = open(FULLPATH, O_RDONLY); 692 ASSERT_LE(0, fd) << strerror(errno); 693 694 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); 695 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 696 leak(fd); 697 } 698 699 TEST_F(Read, read) 700 { 701 const char FULLPATH[] = "mountpoint/some_file.txt"; 702 const char RELPATH[] = "some_file.txt"; 703 const char *CONTENTS = "abcdefgh"; 704 uint64_t ino = 42; 705 int fd; 706 ssize_t bufsize = strlen(CONTENTS); 707 uint8_t buf[bufsize]; 708 709 expect_lookup(RELPATH, ino, bufsize); 710 expect_open(ino, 0, 1); 711 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 712 713 fd = open(FULLPATH, O_RDONLY); 714 ASSERT_LE(0, fd) << strerror(errno); 715 716 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 717 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 718 719 leak(fd); 720 } 721 722 TEST_F(Read_7_8, read) 723 { 724 const char FULLPATH[] = "mountpoint/some_file.txt"; 725 const char RELPATH[] = "some_file.txt"; 726 const char *CONTENTS = "abcdefgh"; 727 uint64_t ino = 42; 728 int fd; 729 ssize_t bufsize = strlen(CONTENTS); 730 uint8_t buf[bufsize]; 731 732 expect_lookup(RELPATH, ino, bufsize); 733 expect_open(ino, 0, 1); 734 expect_read(ino, 0, bufsize, bufsize, CONTENTS); 735 736 fd = open(FULLPATH, O_RDONLY); 737 ASSERT_LE(0, fd) << strerror(errno); 738 739 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 740 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 741 742 leak(fd); 743 } 744 745 /* 746 * If cacheing is enabled, the kernel should try to read an entire cache block 747 * at a time. 748 */ 749 TEST_F(Read, cache_block) 750 { 751 const char FULLPATH[] = "mountpoint/some_file.txt"; 752 const char RELPATH[] = "some_file.txt"; 753 const char *CONTENTS0 = "abcdefghijklmnop"; 754 uint64_t ino = 42; 755 int fd; 756 ssize_t bufsize = 8; 757 ssize_t filesize = m_maxbcachebuf * 2; 758 char *contents; 759 char buf[bufsize]; 760 const char *contents1 = CONTENTS0 + bufsize; 761 762 contents = (char*)calloc(1, filesize); 763 ASSERT_NE(nullptr, contents); 764 memmove(contents, CONTENTS0, strlen(CONTENTS0)); 765 766 expect_lookup(RELPATH, ino, filesize); 767 expect_open(ino, 0, 1); 768 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf, 769 contents); 770 771 fd = open(FULLPATH, O_RDONLY); 772 ASSERT_LE(0, fd) << strerror(errno); 773 774 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 775 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); 776 777 /* A subsequent read should be serviced by cache */ 778 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); 779 ASSERT_EQ(0, memcmp(buf, contents1, bufsize)); 780 leak(fd); 781 free(contents); 782 } 783 784 /* Reading with sendfile should work (though it obviously won't be 0-copy) */ 785 TEST_F(Read, sendfile) 786 { 787 const char FULLPATH[] = "mountpoint/some_file.txt"; 788 const char RELPATH[] = "some_file.txt"; 789 const char *CONTENTS = "abcdefgh"; 790 uint64_t ino = 42; 791 int fd; 792 size_t bufsize = strlen(CONTENTS); 793 uint8_t buf[bufsize]; 794 int sp[2]; 795 off_t sbytes; 796 797 expect_lookup(RELPATH, ino, bufsize); 798 expect_open(ino, 0, 1); 799 EXPECT_CALL(*m_mock, process( 800 ResultOf([=](auto in) { 801 return (in.header.opcode == FUSE_READ && 802 in.header.nodeid == ino && 803 in.body.read.fh == Read::FH && 804 in.body.read.offset == 0 && 805 in.body.read.size == bufsize); 806 }, Eq(true)), 807 _) 808 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 809 out.header.len = sizeof(struct fuse_out_header) + bufsize; 810 memmove(out.body.bytes, CONTENTS, bufsize); 811 }))); 812 813 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) 814 << strerror(errno); 815 fd = open(FULLPATH, O_RDONLY); 816 ASSERT_LE(0, fd) << strerror(errno); 817 818 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)) 819 << strerror(errno); 820 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize)) 821 << strerror(errno); 822 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); 823 824 close(sp[1]); 825 close(sp[0]); 826 leak(fd); 827 } 828 829 /* sendfile should fail gracefully if fuse declines the read */ 830 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */ 831 TEST_F(Read, sendfile_eio) 832 { 833 const char FULLPATH[] = "mountpoint/some_file.txt"; 834 const char RELPATH[] = "some_file.txt"; 835 const char *CONTENTS = "abcdefgh"; 836 uint64_t ino = 42; 837 int fd; 838 ssize_t bufsize = strlen(CONTENTS); 839 int sp[2]; 840 off_t sbytes; 841 842 expect_lookup(RELPATH, ino, bufsize); 843 expect_open(ino, 0, 1); 844 EXPECT_CALL(*m_mock, process( 845 ResultOf([=](auto in) { 846 return (in.header.opcode == FUSE_READ); 847 }, Eq(true)), 848 _) 849 ).WillOnce(Invoke(ReturnErrno(EIO))); 850 851 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) 852 << strerror(errno); 853 fd = open(FULLPATH, O_RDONLY); 854 ASSERT_LE(0, fd) << strerror(errno); 855 856 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)); 857 858 close(sp[1]); 859 close(sp[0]); 860 leak(fd); 861 } 862 863 /* 864 * Sequential reads should use readahead. And if allowed, large reads should 865 * be clustered. 866 */ 867 TEST_P(ReadAhead, readahead) { 868 const char FULLPATH[] = "mountpoint/some_file.txt"; 869 const char RELPATH[] = "some_file.txt"; 870 uint64_t ino = 42; 871 int fd, maxcontig, clustersize; 872 ssize_t bufsize = 4 * m_maxbcachebuf; 873 ssize_t filesize = bufsize; 874 uint64_t len; 875 char *rbuf, *contents; 876 off_t offs; 877 878 contents = (char*)malloc(filesize); 879 ASSERT_NE(nullptr, contents); 880 memset(contents, 'X', filesize); 881 rbuf = (char*)calloc(1, bufsize); 882 883 expect_lookup(RELPATH, ino, filesize); 884 expect_open(ino, 0, 1); 885 maxcontig = m_noclusterr ? m_maxbcachebuf : 886 m_maxbcachebuf + m_maxreadahead; 887 clustersize = MIN(maxcontig, m_maxphys); 888 for (offs = 0; offs < bufsize; offs += clustersize) { 889 len = std::min((size_t)clustersize, (size_t)(filesize - offs)); 890 expect_read(ino, offs, len, len, contents + offs); 891 } 892 893 fd = open(FULLPATH, O_RDONLY); 894 ASSERT_LE(0, fd) << strerror(errno); 895 896 /* Set the internal readahead counter to a "large" value */ 897 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno); 898 899 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno); 900 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize)); 901 902 leak(fd); 903 free(rbuf); 904 free(contents); 905 } 906 907 INSTANTIATE_TEST_CASE_P(RA, ReadAhead, 908 Values(tuple<bool, int>(false, 0), 909 tuple<bool, int>(false, 1), 910 tuple<bool, int>(false, 2), 911 tuple<bool, int>(false, 3), 912 tuple<bool, int>(true, 0), 913 tuple<bool, int>(true, 1), 914 tuple<bool, int>(true, 2))); 915