2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/param.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
40 #include <semaphore.h>
47 using namespace testing;
49 class Read: public FuseTest {
52 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
54 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
58 class Read_7_8: public FuseTest {
60 virtual void SetUp() {
61 m_kernel_minor_version = 8;
65 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
67 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
71 class AioRead: public Read {
73 virtual void SetUp() {
74 const char *node = "vfs.aio.enable_unsafe";
76 size_t size = sizeof(val);
80 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
84 "vfs.aio.enable_unsafe must be set for this test";
88 class AsyncRead: public AioRead {
89 virtual void SetUp() {
90 m_init_flags = FUSE_ASYNC_READ;
95 class ReadAhead: public Read,
96 public WithParamInterface<tuple<bool, int>>
98 virtual void SetUp() {
100 const char *node = "vfs.maxbcachebuf";
101 size_t size = sizeof(val);
102 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
105 m_maxreadahead = val * get<1>(GetParam());
106 m_noclusterr = get<0>(GetParam());
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)
115 const char FULLPATH[] = "mountpoint/some_file.txt";
116 const char RELPATH[] = "some_file.txt";
117 const char *CONTENTS = "abcdefgh";
120 ssize_t bufsize = strlen(CONTENTS);
122 struct aiocb iocb, *piocb;
124 expect_lookup(RELPATH, ino, bufsize);
125 expect_open(ino, 0, 1);
126 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
128 fd = open(FULLPATH, O_RDONLY);
129 ASSERT_LE(0, fd) << strerror(errno);
131 iocb.aio_nbytes = bufsize;
132 iocb.aio_fildes = fd;
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));
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
147 TEST_F(AioRead, async_read_disabled)
149 const char FULLPATH[] = "mountpoint/some_file.txt";
150 const char RELPATH[] = "some_file.txt";
153 ssize_t bufsize = 50;
154 char buf0[bufsize], buf1[bufsize];
156 off_t off1 = m_maxbcachebuf;
157 struct aiocb iocb0, iocb1;
158 volatile sig_atomic_t read_count = 0;
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);
170 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
172 /* Filesystem is slow to respond */
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);
182 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
184 /* Filesystem is slow to respond */
187 fd = open(FULLPATH, O_RDONLY);
188 ASSERT_LE(0, fd) << strerror(errno);
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.
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);
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);
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
214 EXPECT_EQ(read_count, 1);
216 m_mock->kill_daemon();
217 /* Wait for AIO activity to complete, but ignore errors */
218 (void)aio_waitcomplete(NULL, NULL);
224 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple
225 * simultaneous read requests on the same file handle.
227 TEST_F(AsyncRead, async_read)
229 const char FULLPATH[] = "mountpoint/some_file.txt";
230 const char RELPATH[] = "some_file.txt";
233 ssize_t bufsize = 50;
234 char buf0[bufsize], buf1[bufsize];
236 off_t off1 = m_maxbcachebuf;
237 off_t fsize = 2 * m_maxbcachebuf;
238 struct aiocb iocb0, iocb1;
241 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
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);
253 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
255 /* Filesystem is slow to respond */
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);
265 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
267 /* Filesystem is slow to respond */
270 fd = open(FULLPATH, O_RDONLY);
271 ASSERT_LE(0, fd) << strerror(errno);
274 * Submit two AIO read requests, but respond to neither. Ensure that
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);
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);
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);
295 m_mock->kill_daemon();
296 /* Wait for AIO activity to complete, but ignore errors */
297 (void)aio_waitcomplete(NULL, NULL);
302 /* 0-length reads shouldn't cause any confusion */
303 TEST_F(Read, direct_io_read_nothing)
305 const char FULLPATH[] = "mountpoint/some_file.txt";
306 const char RELPATH[] = "some_file.txt";
309 uint64_t offset = 100;
312 expect_lookup(RELPATH, ino, offset + 1000);
313 expect_open(ino, FOPEN_DIRECT_IO, 1);
315 fd = open(FULLPATH, O_RDONLY);
316 ASSERT_LE(0, fd) << strerror(errno);
318 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
323 * With direct_io, reads should not fill the cache. They should go straight to
326 TEST_F(Read, direct_io_pread)
328 const char FULLPATH[] = "mountpoint/some_file.txt";
329 const char RELPATH[] = "some_file.txt";
330 const char *CONTENTS = "abcdefgh";
333 uint64_t offset = 100;
334 ssize_t bufsize = strlen(CONTENTS);
337 expect_lookup(RELPATH, ino, offset + bufsize);
338 expect_open(ino, FOPEN_DIRECT_IO, 1);
339 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
341 fd = open(FULLPATH, O_RDONLY);
342 ASSERT_LE(0, fd) << strerror(errno);
344 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
345 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
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));
356 * With direct_io, filesystems are allowed to return less data than is
357 * requested. fuse(4) should return a short read to userland.
359 TEST_F(Read, direct_io_short_read)
361 const char FULLPATH[] = "mountpoint/some_file.txt";
362 const char RELPATH[] = "some_file.txt";
363 const char *CONTENTS = "abcdefghijklmnop";
366 uint64_t offset = 100;
367 ssize_t bufsize = strlen(CONTENTS);
368 ssize_t halfbufsize = bufsize / 2;
371 expect_lookup(RELPATH, ino, offset + bufsize);
372 expect_open(ino, FOPEN_DIRECT_IO, 1);
373 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
375 fd = open(FULLPATH, O_RDONLY);
376 ASSERT_LE(0, fd) << strerror(errno);
378 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
380 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
386 const char FULLPATH[] = "mountpoint/some_file.txt";
387 const char RELPATH[] = "some_file.txt";
388 const char *CONTENTS = "abcdefgh";
391 ssize_t bufsize = strlen(CONTENTS);
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);
401 ).WillOnce(Invoke(ReturnErrno(EIO)));
403 fd = open(FULLPATH, O_RDONLY);
404 ASSERT_LE(0, fd) << strerror(errno);
406 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
407 ASSERT_EQ(EIO, errno);
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,
418 const char FULLPATH[] = "mountpoint/some_file.txt";
419 const char RELPATH[] = "some_file.txt";
420 const char *CONTENTS = "abcdefghijklmnop";
423 uint64_t offset = 100;
424 ssize_t bufsize = strlen(CONTENTS);
425 ssize_t partbufsize = 3 * bufsize / 4;
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);
435 fd = open(FULLPATH, O_RDONLY);
436 ASSERT_LE(0, fd) << strerror(errno);
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);
446 /* Like Read.eof, but causes an entire buffer to be invalidated */
447 TEST_F(Read, eof_of_whole_buffer)
449 const char FULLPATH[] = "mountpoint/some_file.txt";
450 const char RELPATH[] = "some_file.txt";
451 const char *CONTENTS = "abcdefghijklmnop";
454 ssize_t bufsize = strlen(CONTENTS);
455 off_t old_filesize = m_maxbcachebuf * 2 + bufsize;
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);
465 fd = open(FULLPATH, O_RDONLY);
466 ASSERT_LE(0, fd) << strerror(errno);
468 /* Cache the third block */
469 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2))
471 /* Try to read the 2nd block, but it's past EOF */
472 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf))
474 ASSERT_EQ(0, fstat(fd, &sb));
475 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size);
480 * With the keep_cache option, the kernel may keep its read cache across
483 TEST_F(Read, keep_cache)
485 const char FULLPATH[] = "mountpoint/some_file.txt";
486 const char RELPATH[] = "some_file.txt";
487 const char *CONTENTS = "abcdefgh";
490 ssize_t bufsize = strlen(CONTENTS);
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);
497 fd0 = open(FULLPATH, O_RDONLY);
498 ASSERT_LE(0, fd0) << strerror(errno);
499 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
501 fd1 = open(FULLPATH, O_RDWR);
502 ASSERT_LE(0, fd1) << strerror(errno);
505 * This read should be serviced by cache, even though it's on the other
508 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
515 * Without the keep_cache option, the kernel should drop its read caches on
518 TEST_F(Read, keep_cache_disabled)
520 const char FULLPATH[] = "mountpoint/some_file.txt";
521 const char RELPATH[] = "some_file.txt";
522 const char *CONTENTS = "abcdefgh";
525 ssize_t bufsize = strlen(CONTENTS);
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);
532 fd0 = open(FULLPATH, O_RDONLY);
533 ASSERT_LE(0, fd0) << strerror(errno);
534 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
536 fd1 = open(FULLPATH, O_RDWR);
537 ASSERT_LE(0, fd1) << strerror(errno);
540 * This read should not be serviced by cache, even though it's on the
541 * original file descriptor
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);
553 const char FULLPATH[] = "mountpoint/some_file.txt";
554 const char RELPATH[] = "some_file.txt";
555 const char *CONTENTS = "abcdefgh";
559 size_t bufsize = strlen(CONTENTS);
564 expect_lookup(RELPATH, ino, bufsize);
565 expect_open(ino, 0, 1);
566 EXPECT_CALL(*m_mock, process(
567 ResultOf([=](auto in) {
568 return (in.header.opcode == FUSE_READ &&
569 in.header.nodeid == ino &&
570 in.body.read.fh == Read::FH &&
571 in.body.read.offset == 0 &&
572 in.body.read.size == bufsize);
575 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
576 out.header.len = sizeof(struct fuse_out_header) + bufsize;
577 memmove(out.body.bytes, CONTENTS, bufsize);
580 fd = open(FULLPATH, O_RDONLY);
581 ASSERT_LE(0, fd) << strerror(errno);
583 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
584 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
586 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
588 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
593 * A read via mmap comes up short, indicating that the file was truncated
596 TEST_F(Read, mmap_eof)
598 const char FULLPATH[] = "mountpoint/some_file.txt";
599 const char RELPATH[] = "some_file.txt";
600 const char *CONTENTS = "abcdefgh";
604 size_t bufsize = strlen(CONTENTS);
610 expect_lookup(RELPATH, ino, m_maxbcachebuf);
611 expect_open(ino, 0, 1);
612 EXPECT_CALL(*m_mock, process(
613 ResultOf([=](auto in) {
614 return (in.header.opcode == FUSE_READ &&
615 in.header.nodeid == ino &&
616 in.body.read.fh == Read::FH &&
617 in.body.read.offset == 0 &&
618 in.body.read.size == (uint32_t)m_maxbcachebuf);
621 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
622 out.header.len = sizeof(struct fuse_out_header) + bufsize;
623 memmove(out.body.bytes, CONTENTS, bufsize);
625 expect_getattr(ino, bufsize);
627 fd = open(FULLPATH, O_RDONLY);
628 ASSERT_LE(0, fd) << strerror(errno);
630 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
631 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
633 /* The file size should be automatically truncated */
634 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
635 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
636 EXPECT_EQ((off_t)bufsize, sb.st_size);
638 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
643 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
644 * cache and to straight to the daemon
646 TEST_F(Read, o_direct)
648 const char FULLPATH[] = "mountpoint/some_file.txt";
649 const char RELPATH[] = "some_file.txt";
650 const char *CONTENTS = "abcdefgh";
653 ssize_t bufsize = strlen(CONTENTS);
656 expect_lookup(RELPATH, ino, bufsize);
657 expect_open(ino, 0, 1);
658 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
660 fd = open(FULLPATH, O_RDONLY);
661 ASSERT_LE(0, fd) << strerror(errno);
664 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
665 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
667 // Reads with o_direct should bypass the cache
668 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
669 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
670 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
671 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
672 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
679 const char FULLPATH[] = "mountpoint/some_file.txt";
680 const char RELPATH[] = "some_file.txt";
681 const char *CONTENTS = "abcdefgh";
685 * Set offset to a maxbcachebuf boundary so we'll be sure what offset
686 * to read from. Without this, the read might start at a lower offset.
688 uint64_t offset = m_maxbcachebuf;
689 ssize_t bufsize = strlen(CONTENTS);
692 expect_lookup(RELPATH, ino, offset + bufsize);
693 expect_open(ino, 0, 1);
694 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
696 fd = open(FULLPATH, O_RDONLY);
697 ASSERT_LE(0, fd) << strerror(errno);
699 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
700 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
706 const char FULLPATH[] = "mountpoint/some_file.txt";
707 const char RELPATH[] = "some_file.txt";
708 const char *CONTENTS = "abcdefgh";
711 ssize_t bufsize = strlen(CONTENTS);
714 expect_lookup(RELPATH, ino, bufsize);
715 expect_open(ino, 0, 1);
716 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
718 fd = open(FULLPATH, O_RDONLY);
719 ASSERT_LE(0, fd) << strerror(errno);
721 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
722 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
727 TEST_F(Read_7_8, read)
729 const char FULLPATH[] = "mountpoint/some_file.txt";
730 const char RELPATH[] = "some_file.txt";
731 const char *CONTENTS = "abcdefgh";
734 ssize_t bufsize = strlen(CONTENTS);
737 expect_lookup(RELPATH, ino, bufsize);
738 expect_open(ino, 0, 1);
739 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
741 fd = open(FULLPATH, O_RDONLY);
742 ASSERT_LE(0, fd) << strerror(errno);
744 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
745 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
751 * If cacheing is enabled, the kernel should try to read an entire cache block
754 TEST_F(Read, cache_block)
756 const char FULLPATH[] = "mountpoint/some_file.txt";
757 const char RELPATH[] = "some_file.txt";
758 const char *CONTENTS0 = "abcdefghijklmnop";
762 ssize_t filesize = m_maxbcachebuf * 2;
765 const char *contents1 = CONTENTS0 + bufsize;
767 contents = (char*)calloc(1, filesize);
768 ASSERT_NE(NULL, contents);
769 memmove(contents, CONTENTS0, strlen(CONTENTS0));
771 expect_lookup(RELPATH, ino, filesize);
772 expect_open(ino, 0, 1);
773 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf,
776 fd = open(FULLPATH, O_RDONLY);
777 ASSERT_LE(0, fd) << strerror(errno);
779 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
780 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
782 /* A subsequent read should be serviced by cache */
783 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
784 ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
788 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
789 TEST_F(Read, sendfile)
791 const char FULLPATH[] = "mountpoint/some_file.txt";
792 const char RELPATH[] = "some_file.txt";
793 const char *CONTENTS = "abcdefgh";
796 size_t bufsize = strlen(CONTENTS);
801 expect_lookup(RELPATH, ino, bufsize);
802 expect_open(ino, 0, 1);
803 EXPECT_CALL(*m_mock, process(
804 ResultOf([=](auto in) {
805 return (in.header.opcode == FUSE_READ &&
806 in.header.nodeid == ino &&
807 in.body.read.fh == Read::FH &&
808 in.body.read.offset == 0 &&
809 in.body.read.size == bufsize);
812 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
813 out.header.len = sizeof(struct fuse_out_header) + bufsize;
814 memmove(out.body.bytes, CONTENTS, bufsize);
817 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
819 fd = open(FULLPATH, O_RDONLY);
820 ASSERT_LE(0, fd) << strerror(errno);
822 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
824 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
826 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
833 /* sendfile should fail gracefully if fuse declines the read */
834 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
835 TEST_F(Read, DISABLED_sendfile_eio)
837 const char FULLPATH[] = "mountpoint/some_file.txt";
838 const char RELPATH[] = "some_file.txt";
839 const char *CONTENTS = "abcdefgh";
842 ssize_t bufsize = strlen(CONTENTS);
846 expect_lookup(RELPATH, ino, bufsize);
847 expect_open(ino, 0, 1);
848 EXPECT_CALL(*m_mock, process(
849 ResultOf([=](auto in) {
850 return (in.header.opcode == FUSE_READ);
853 ).WillOnce(Invoke(ReturnErrno(EIO)));
855 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
857 fd = open(FULLPATH, O_RDONLY);
858 ASSERT_LE(0, fd) << strerror(errno);
860 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
868 * Sequential reads should use readahead. And if allowed, large reads should
871 TEST_P(ReadAhead, readahead) {
872 const char FULLPATH[] = "mountpoint/some_file.txt";
873 const char RELPATH[] = "some_file.txt";
875 int fd, maxcontig, clustersize;
876 ssize_t bufsize = 4 * m_maxbcachebuf;
877 ssize_t filesize = bufsize;
879 char *rbuf, *contents;
882 contents = (char*)malloc(filesize);
883 ASSERT_NE(NULL, contents);
884 memset(contents, 'X', filesize);
885 rbuf = (char*)calloc(1, bufsize);
887 expect_lookup(RELPATH, ino, filesize);
888 expect_open(ino, 0, 1);
889 maxcontig = m_noclusterr ? m_maxbcachebuf :
890 m_maxbcachebuf + m_maxreadahead;
891 clustersize = MIN(maxcontig, m_maxphys);
892 for (offs = 0; offs < bufsize; offs += clustersize) {
893 len = std::min((size_t)clustersize, (size_t)(filesize - offs));
894 expect_read(ino, offs, len, len, contents + offs);
897 fd = open(FULLPATH, O_RDONLY);
898 ASSERT_LE(0, fd) << strerror(errno);
900 /* Set the internal readahead counter to a "large" value */
901 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
903 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
904 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
909 INSTANTIATE_TEST_CASE_P(RA, ReadAhead,
910 Values(tuple<bool, int>(false, 0),
911 tuple<bool, int>(false, 1),
912 tuple<bool, int>(false, 2),
913 tuple<bool, int>(false, 3),
914 tuple<bool, int>(true, 0),
915 tuple<bool, int>(true, 1),
916 tuple<bool, int>(true, 2)));