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
34 #include <sys/param.h>
36 #include <sys/socket.h>
37 #include <sys/sysctl.h>
42 #include <semaphore.h>
49 using namespace testing;
51 class Read: public FuseTest {
54 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
56 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
60 class Read_7_8: public FuseTest {
62 virtual void SetUp() {
63 m_kernel_minor_version = 8;
67 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
69 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
73 class AioRead: public Read {
75 virtual void SetUp() {
76 const char *node = "vfs.aio.enable_unsafe";
78 size_t size = sizeof(val);
82 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
86 "vfs.aio.enable_unsafe must be set for this test";
90 class AsyncRead: public AioRead {
91 virtual void SetUp() {
92 m_init_flags = FUSE_ASYNC_READ;
97 class ReadAhead: public Read,
98 public WithParamInterface<tuple<bool, int>>
100 virtual void SetUp() {
102 const char *node = "vfs.maxbcachebuf";
103 size_t size = sizeof(val);
104 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
107 m_maxreadahead = val * get<1>(GetParam());
108 m_noclusterr = get<0>(GetParam());
113 /* AIO reads need to set the header's pid field correctly */
114 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
115 TEST_F(AioRead, aio_read)
117 const char FULLPATH[] = "mountpoint/some_file.txt";
118 const char RELPATH[] = "some_file.txt";
119 const char *CONTENTS = "abcdefgh";
122 ssize_t bufsize = strlen(CONTENTS);
124 struct aiocb iocb, *piocb;
126 expect_lookup(RELPATH, ino, bufsize);
127 expect_open(ino, 0, 1);
128 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
130 fd = open(FULLPATH, O_RDONLY);
131 ASSERT_LE(0, fd) << strerror(errno);
133 iocb.aio_nbytes = bufsize;
134 iocb.aio_fildes = fd;
137 iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
138 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
139 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
140 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
146 * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there
147 * is at most one outstanding read operation per file handle
149 TEST_F(AioRead, async_read_disabled)
151 const char FULLPATH[] = "mountpoint/some_file.txt";
152 const char RELPATH[] = "some_file.txt";
155 ssize_t bufsize = 50;
156 char buf0[bufsize], buf1[bufsize];
158 off_t off1 = m_maxbcachebuf;
159 struct aiocb iocb0, iocb1;
160 volatile sig_atomic_t read_count = 0;
162 expect_lookup(RELPATH, ino, 131072);
163 expect_open(ino, 0, 1);
164 EXPECT_CALL(*m_mock, process(
165 ResultOf([=](auto in) {
166 return (in.header.opcode == FUSE_READ &&
167 in.header.nodeid == ino &&
168 in.body.read.fh == FH &&
169 in.body.read.offset == (uint64_t)off0);
172 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
174 /* Filesystem is slow to respond */
176 EXPECT_CALL(*m_mock, process(
177 ResultOf([=](auto in) {
178 return (in.header.opcode == FUSE_READ &&
179 in.header.nodeid == ino &&
180 in.body.read.fh == FH &&
181 in.body.read.offset == (uint64_t)off1);
184 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
186 /* Filesystem is slow to respond */
189 fd = open(FULLPATH, O_RDONLY);
190 ASSERT_LE(0, fd) << strerror(errno);
193 * Submit two AIO read requests, and respond to neither. If the
194 * filesystem ever gets the second read request, then we failed to
195 * limit outstanding reads.
197 iocb0.aio_nbytes = bufsize;
198 iocb0.aio_fildes = fd;
199 iocb0.aio_buf = buf0;
200 iocb0.aio_offset = off0;
201 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
202 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
204 iocb1.aio_nbytes = bufsize;
205 iocb1.aio_fildes = fd;
206 iocb1.aio_buf = buf1;
207 iocb1.aio_offset = off1;
208 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
209 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
212 * Sleep for awhile to make sure the kernel has had a chance to issue
213 * the second read, even though the first has not yet returned
216 EXPECT_EQ(read_count, 1);
218 m_mock->kill_daemon();
219 /* Wait for AIO activity to complete, but ignore errors */
220 (void)aio_waitcomplete(NULL, NULL);
226 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple
227 * simultaneous read requests on the same file handle.
229 TEST_F(AsyncRead, async_read)
231 const char FULLPATH[] = "mountpoint/some_file.txt";
232 const char RELPATH[] = "some_file.txt";
235 ssize_t bufsize = 50;
236 char buf0[bufsize], buf1[bufsize];
238 off_t off1 = m_maxbcachebuf;
239 off_t fsize = 2 * m_maxbcachebuf;
240 struct aiocb iocb0, iocb1;
243 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
245 expect_lookup(RELPATH, ino, fsize);
246 expect_open(ino, 0, 1);
247 EXPECT_CALL(*m_mock, process(
248 ResultOf([=](auto in) {
249 return (in.header.opcode == FUSE_READ &&
250 in.header.nodeid == ino &&
251 in.body.read.fh == FH &&
252 in.body.read.offset == (uint64_t)off0);
255 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
257 /* Filesystem is slow to respond */
259 EXPECT_CALL(*m_mock, process(
260 ResultOf([=](auto in) {
261 return (in.header.opcode == FUSE_READ &&
262 in.header.nodeid == ino &&
263 in.body.read.fh == FH &&
264 in.body.read.offset == (uint64_t)off1);
267 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
269 /* Filesystem is slow to respond */
272 fd = open(FULLPATH, O_RDONLY);
273 ASSERT_LE(0, fd) << strerror(errno);
276 * Submit two AIO read requests, but respond to neither. Ensure that
279 iocb0.aio_nbytes = bufsize;
280 iocb0.aio_fildes = fd;
281 iocb0.aio_buf = buf0;
282 iocb0.aio_offset = off0;
283 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
284 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
286 iocb1.aio_nbytes = bufsize;
287 iocb1.aio_fildes = fd;
288 iocb1.aio_buf = buf1;
289 iocb1.aio_offset = off1;
290 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
291 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
293 /* Wait until both reads have reached the daemon */
294 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
295 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
297 m_mock->kill_daemon();
298 /* Wait for AIO activity to complete, but ignore errors */
299 (void)aio_waitcomplete(NULL, NULL);
304 /* 0-length reads shouldn't cause any confusion */
305 TEST_F(Read, direct_io_read_nothing)
307 const char FULLPATH[] = "mountpoint/some_file.txt";
308 const char RELPATH[] = "some_file.txt";
311 uint64_t offset = 100;
314 expect_lookup(RELPATH, ino, offset + 1000);
315 expect_open(ino, FOPEN_DIRECT_IO, 1);
317 fd = open(FULLPATH, O_RDONLY);
318 ASSERT_LE(0, fd) << strerror(errno);
320 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
325 * With direct_io, reads should not fill the cache. They should go straight to
328 TEST_F(Read, direct_io_pread)
330 const char FULLPATH[] = "mountpoint/some_file.txt";
331 const char RELPATH[] = "some_file.txt";
332 const char *CONTENTS = "abcdefgh";
335 uint64_t offset = 100;
336 ssize_t bufsize = strlen(CONTENTS);
339 expect_lookup(RELPATH, ino, offset + bufsize);
340 expect_open(ino, FOPEN_DIRECT_IO, 1);
341 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
343 fd = open(FULLPATH, O_RDONLY);
344 ASSERT_LE(0, fd) << strerror(errno);
346 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
347 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
349 // With FOPEN_DIRECT_IO, the cache should be bypassed. The server will
350 // get a 2nd read request.
351 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
352 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
353 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
358 * With direct_io, filesystems are allowed to return less data than is
359 * requested. fuse(4) should return a short read to userland.
361 TEST_F(Read, direct_io_short_read)
363 const char FULLPATH[] = "mountpoint/some_file.txt";
364 const char RELPATH[] = "some_file.txt";
365 const char *CONTENTS = "abcdefghijklmnop";
368 uint64_t offset = 100;
369 ssize_t bufsize = strlen(CONTENTS);
370 ssize_t halfbufsize = bufsize / 2;
373 expect_lookup(RELPATH, ino, offset + bufsize);
374 expect_open(ino, FOPEN_DIRECT_IO, 1);
375 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
377 fd = open(FULLPATH, O_RDONLY);
378 ASSERT_LE(0, fd) << strerror(errno);
380 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
382 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
388 const char FULLPATH[] = "mountpoint/some_file.txt";
389 const char RELPATH[] = "some_file.txt";
390 const char *CONTENTS = "abcdefgh";
393 ssize_t bufsize = strlen(CONTENTS);
396 expect_lookup(RELPATH, ino, bufsize);
397 expect_open(ino, 0, 1);
398 EXPECT_CALL(*m_mock, process(
399 ResultOf([=](auto in) {
400 return (in.header.opcode == FUSE_READ);
403 ).WillOnce(Invoke(ReturnErrno(EIO)));
405 fd = open(FULLPATH, O_RDONLY);
406 ASSERT_LE(0, fd) << strerror(errno);
408 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
409 ASSERT_EQ(EIO, errno);
414 * If the server returns a short read when direct io is not in use, that
415 * indicates EOF, because of a server-side truncation. We should invalidate
416 * all cached attributes. We may update the file size,
420 const char FULLPATH[] = "mountpoint/some_file.txt";
421 const char RELPATH[] = "some_file.txt";
422 const char *CONTENTS = "abcdefghijklmnop";
425 uint64_t offset = 100;
426 ssize_t bufsize = strlen(CONTENTS);
427 ssize_t partbufsize = 3 * bufsize / 4;
432 expect_lookup(RELPATH, ino, offset + bufsize);
433 expect_open(ino, 0, 1);
434 expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS);
435 expect_getattr(ino, offset + partbufsize);
437 fd = open(FULLPATH, O_RDONLY);
438 ASSERT_LE(0, fd) << strerror(errno);
440 r = pread(fd, buf, bufsize, offset);
441 ASSERT_LE(0, r) << strerror(errno);
442 EXPECT_EQ(partbufsize, r) << strerror(errno);
443 ASSERT_EQ(0, fstat(fd, &sb));
444 EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size);
448 /* Like Read.eof, but causes an entire buffer to be invalidated */
449 TEST_F(Read, eof_of_whole_buffer)
451 const char FULLPATH[] = "mountpoint/some_file.txt";
452 const char RELPATH[] = "some_file.txt";
453 const char *CONTENTS = "abcdefghijklmnop";
456 ssize_t bufsize = strlen(CONTENTS);
457 off_t old_filesize = m_maxbcachebuf * 2 + bufsize;
461 expect_lookup(RELPATH, ino, old_filesize);
462 expect_open(ino, 0, 1);
463 expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS);
464 expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS);
465 expect_getattr(ino, m_maxbcachebuf);
467 fd = open(FULLPATH, O_RDONLY);
468 ASSERT_LE(0, fd) << strerror(errno);
470 /* Cache the third block */
471 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2))
473 /* Try to read the 2nd block, but it's past EOF */
474 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf))
476 ASSERT_EQ(0, fstat(fd, &sb));
477 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size);
482 * With the keep_cache option, the kernel may keep its read cache across
485 TEST_F(Read, keep_cache)
487 const char FULLPATH[] = "mountpoint/some_file.txt";
488 const char RELPATH[] = "some_file.txt";
489 const char *CONTENTS = "abcdefgh";
492 ssize_t bufsize = strlen(CONTENTS);
495 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
496 expect_open(ino, FOPEN_KEEP_CACHE, 2);
497 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
499 fd0 = open(FULLPATH, O_RDONLY);
500 ASSERT_LE(0, fd0) << strerror(errno);
501 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
503 fd1 = open(FULLPATH, O_RDWR);
504 ASSERT_LE(0, fd1) << strerror(errno);
507 * This read should be serviced by cache, even though it's on the other
510 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
517 * Without the keep_cache option, the kernel should drop its read caches on
520 TEST_F(Read, keep_cache_disabled)
522 const char FULLPATH[] = "mountpoint/some_file.txt";
523 const char RELPATH[] = "some_file.txt";
524 const char *CONTENTS = "abcdefgh";
527 ssize_t bufsize = strlen(CONTENTS);
530 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
531 expect_open(ino, 0, 2);
532 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
534 fd0 = open(FULLPATH, O_RDONLY);
535 ASSERT_LE(0, fd0) << strerror(errno);
536 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
538 fd1 = open(FULLPATH, O_RDWR);
539 ASSERT_LE(0, fd1) << strerror(errno);
542 * This read should not be serviced by cache, even though it's on the
543 * original file descriptor
545 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
546 ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno);
547 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
555 const char FULLPATH[] = "mountpoint/some_file.txt";
556 const char RELPATH[] = "some_file.txt";
557 const char *CONTENTS = "abcdefgh";
561 size_t bufsize = strlen(CONTENTS);
566 expect_lookup(RELPATH, ino, bufsize);
567 expect_open(ino, 0, 1);
568 EXPECT_CALL(*m_mock, process(
569 ResultOf([=](auto in) {
570 return (in.header.opcode == FUSE_READ &&
571 in.header.nodeid == ino &&
572 in.body.read.fh == Read::FH &&
573 in.body.read.offset == 0 &&
574 in.body.read.size == bufsize);
577 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
578 out.header.len = sizeof(struct fuse_out_header) + bufsize;
579 memmove(out.body.bytes, CONTENTS, bufsize);
582 fd = open(FULLPATH, O_RDONLY);
583 ASSERT_LE(0, fd) << strerror(errno);
585 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
586 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
588 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
590 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
595 * A read via mmap comes up short, indicating that the file was truncated
598 TEST_F(Read, mmap_eof)
600 const char FULLPATH[] = "mountpoint/some_file.txt";
601 const char RELPATH[] = "some_file.txt";
602 const char *CONTENTS = "abcdefgh";
606 size_t bufsize = strlen(CONTENTS);
612 expect_lookup(RELPATH, ino, m_maxbcachebuf);
613 expect_open(ino, 0, 1);
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 == (uint32_t)m_maxbcachebuf);
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(nullptr, 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 EXPECT_CALL(*m_mock, process(
806 ResultOf([=](auto in) {
807 return (in.header.opcode == FUSE_READ &&
808 in.header.nodeid == ino &&
809 in.body.read.fh == Read::FH &&
810 in.body.read.offset == 0 &&
811 in.body.read.size == bufsize);
814 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
815 out.header.len = sizeof(struct fuse_out_header) + bufsize;
816 memmove(out.body.bytes, CONTENTS, bufsize);
819 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
821 fd = open(FULLPATH, O_RDONLY);
822 ASSERT_LE(0, fd) << strerror(errno);
824 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
826 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
828 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
835 /* sendfile should fail gracefully if fuse declines the read */
836 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
837 TEST_F(Read, sendfile_eio)
839 const char FULLPATH[] = "mountpoint/some_file.txt";
840 const char RELPATH[] = "some_file.txt";
841 const char *CONTENTS = "abcdefgh";
844 ssize_t bufsize = strlen(CONTENTS);
848 expect_lookup(RELPATH, ino, bufsize);
849 expect_open(ino, 0, 1);
850 EXPECT_CALL(*m_mock, process(
851 ResultOf([=](auto in) {
852 return (in.header.opcode == FUSE_READ);
855 ).WillOnce(Invoke(ReturnErrno(EIO)));
857 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
859 fd = open(FULLPATH, O_RDONLY);
860 ASSERT_LE(0, fd) << strerror(errno);
862 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
870 * Sequential reads should use readahead. And if allowed, large reads should
873 TEST_P(ReadAhead, readahead) {
874 const char FULLPATH[] = "mountpoint/some_file.txt";
875 const char RELPATH[] = "some_file.txt";
877 int fd, maxcontig, clustersize;
878 ssize_t bufsize = 4 * m_maxbcachebuf;
879 ssize_t filesize = bufsize;
881 char *rbuf, *contents;
884 contents = (char*)malloc(filesize);
885 ASSERT_NE(nullptr, contents);
886 memset(contents, 'X', filesize);
887 rbuf = (char*)calloc(1, bufsize);
889 expect_lookup(RELPATH, ino, filesize);
890 expect_open(ino, 0, 1);
891 maxcontig = m_noclusterr ? m_maxbcachebuf :
892 m_maxbcachebuf + m_maxreadahead;
893 clustersize = MIN(maxcontig, m_maxphys);
894 for (offs = 0; offs < bufsize; offs += clustersize) {
895 len = std::min((size_t)clustersize, (size_t)(filesize - offs));
896 expect_read(ino, offs, len, len, contents + offs);
899 fd = open(FULLPATH, O_RDONLY);
900 ASSERT_LE(0, fd) << strerror(errno);
902 /* Set the internal readahead counter to a "large" value */
903 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
905 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
906 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
911 INSTANTIATE_TEST_CASE_P(RA, ReadAhead,
912 Values(tuple<bool, int>(false, 0),
913 tuple<bool, int>(false, 1),
914 tuple<bool, int>(false, 2),
915 tuple<bool, int>(false, 3),
916 tuple<bool, int>(true, 0),
917 tuple<bool, int>(true, 1),
918 tuple<bool, int>(true, 2)));