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