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 /* 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);
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);
581 fd = open(FULLPATH, O_RDONLY);
582 ASSERT_LE(0, fd) << strerror(errno);
584 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
585 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
587 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
589 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
594 * A read via mmap comes up short, indicating that the file was truncated
597 TEST_F(Read, mmap_eof)
599 const char FULLPATH[] = "mountpoint/some_file.txt";
600 const char RELPATH[] = "some_file.txt";
601 const char *CONTENTS = "abcdefgh";
605 size_t bufsize = strlen(CONTENTS);
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);
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);
627 expect_getattr(ino, bufsize);
629 fd = open(FULLPATH, O_RDONLY);
630 ASSERT_LE(0, fd) << strerror(errno);
632 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
633 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
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);
640 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
645 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
646 * cache and to straight to the daemon
648 TEST_F(Read, o_direct)
650 const char FULLPATH[] = "mountpoint/some_file.txt";
651 const char RELPATH[] = "some_file.txt";
652 const char *CONTENTS = "abcdefgh";
655 ssize_t bufsize = strlen(CONTENTS);
658 expect_lookup(RELPATH, ino, bufsize);
659 expect_open(ino, 0, 1);
660 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
662 fd = open(FULLPATH, O_RDONLY);
663 ASSERT_LE(0, fd) << strerror(errno);
666 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
667 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
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));
681 const char FULLPATH[] = "mountpoint/some_file.txt";
682 const char RELPATH[] = "some_file.txt";
683 const char *CONTENTS = "abcdefgh";
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.
690 uint64_t offset = m_maxbcachebuf;
691 ssize_t bufsize = strlen(CONTENTS);
694 expect_lookup(RELPATH, ino, offset + bufsize);
695 expect_open(ino, 0, 1);
696 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
698 fd = open(FULLPATH, O_RDONLY);
699 ASSERT_LE(0, fd) << strerror(errno);
701 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
702 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
708 const char FULLPATH[] = "mountpoint/some_file.txt";
709 const char RELPATH[] = "some_file.txt";
710 const char *CONTENTS = "abcdefgh";
713 ssize_t bufsize = strlen(CONTENTS);
716 expect_lookup(RELPATH, ino, bufsize);
717 expect_open(ino, 0, 1);
718 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
720 fd = open(FULLPATH, O_RDONLY);
721 ASSERT_LE(0, fd) << strerror(errno);
723 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
724 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
729 TEST_F(Read_7_8, read)
731 const char FULLPATH[] = "mountpoint/some_file.txt";
732 const char RELPATH[] = "some_file.txt";
733 const char *CONTENTS = "abcdefgh";
736 ssize_t bufsize = strlen(CONTENTS);
739 expect_lookup(RELPATH, ino, bufsize);
740 expect_open(ino, 0, 1);
741 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
743 fd = open(FULLPATH, O_RDONLY);
744 ASSERT_LE(0, fd) << strerror(errno);
746 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
747 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
753 * If cacheing is enabled, the kernel should try to read an entire cache block
756 TEST_F(Read, cache_block)
758 const char FULLPATH[] = "mountpoint/some_file.txt";
759 const char RELPATH[] = "some_file.txt";
760 const char *CONTENTS0 = "abcdefghijklmnop";
764 ssize_t filesize = m_maxbcachebuf * 2;
767 const char *contents1 = CONTENTS0 + bufsize;
769 contents = (char*)calloc(1, filesize);
770 ASSERT_NE(NULL, contents);
771 memmove(contents, CONTENTS0, strlen(CONTENTS0));
773 expect_lookup(RELPATH, ino, filesize);
774 expect_open(ino, 0, 1);
775 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf,
778 fd = open(FULLPATH, O_RDONLY);
779 ASSERT_LE(0, fd) << strerror(errno);
781 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
782 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
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));
790 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
791 TEST_F(Read, sendfile)
793 const char FULLPATH[] = "mountpoint/some_file.txt";
794 const char RELPATH[] = "some_file.txt";
795 const char *CONTENTS = "abcdefgh";
798 size_t bufsize = strlen(CONTENTS);
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);
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);
820 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
822 fd = open(FULLPATH, O_RDONLY);
823 ASSERT_LE(0, fd) << strerror(errno);
825 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
827 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
829 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
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)
840 const char FULLPATH[] = "mountpoint/some_file.txt";
841 const char RELPATH[] = "some_file.txt";
842 const char *CONTENTS = "abcdefgh";
845 ssize_t bufsize = strlen(CONTENTS);
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);
856 ).WillOnce(Invoke(ReturnErrno(EIO)));
858 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
860 fd = open(FULLPATH, O_RDONLY);
861 ASSERT_LE(0, fd) << strerror(errno);
863 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
871 * Sequential reads should use readahead. And if allowed, large reads should
874 TEST_P(ReadAhead, readahead) {
875 const char FULLPATH[] = "mountpoint/some_file.txt";
876 const char RELPATH[] = "some_file.txt";
878 int fd, maxcontig, clustersize;
879 ssize_t bufsize = 4 * m_maxbcachebuf;
880 ssize_t filesize = bufsize;
882 char *rbuf, *contents;
885 contents = (char*)malloc(filesize);
886 ASSERT_NE(NULL, contents);
887 memset(contents, 'X', filesize);
888 rbuf = (char*)calloc(1, bufsize);
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);
900 fd = open(FULLPATH, O_RDONLY);
901 ASSERT_LE(0, fd) << strerror(errno);
903 /* Set the internal readahead counter to a "large" value */
904 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
906 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
907 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
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)));