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