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/types.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
46 using namespace testing;
48 class Read: public FuseTest {
51 void expect_lookup(const char *relpath, uint64_t ino)
53 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1);
57 class AioRead: public Read {
58 virtual void SetUp() {
59 const char *node = "vfs.aio.enable_unsafe";
61 size_t size = sizeof(val);
63 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
65 // TODO: With GoogleTest 1.8.2, use SKIP instead
67 FAIL() << "vfs.aio.enable_unsafe must be set for this test";
72 class ReadAhead: public Read, public WithParamInterface<uint32_t> {
73 virtual void SetUp() {
74 m_maxreadahead = GetParam();
79 /* AIO reads need to set the header's pid field correctly */
80 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
81 TEST_F(AioRead, aio_read)
83 const char FULLPATH[] = "mountpoint/some_file.txt";
84 const char RELPATH[] = "some_file.txt";
85 const char *CONTENTS = "abcdefgh";
88 ssize_t bufsize = strlen(CONTENTS);
90 struct aiocb iocb, *piocb;
92 expect_lookup(RELPATH, ino);
93 expect_open(ino, 0, 1);
94 expect_getattr(ino, bufsize);
95 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
97 fd = open(FULLPATH, O_RDONLY);
98 ASSERT_LE(0, fd) << strerror(errno);
100 iocb.aio_nbytes = bufsize;
101 iocb.aio_fildes = fd;
104 iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
105 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
106 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
107 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
108 /* Deliberately leak fd. close(2) will be tested in release.cc */
111 /* 0-length reads shouldn't cause any confusion */
112 TEST_F(Read, direct_io_read_nothing)
114 const char FULLPATH[] = "mountpoint/some_file.txt";
115 const char RELPATH[] = "some_file.txt";
118 uint64_t offset = 100;
121 expect_lookup(RELPATH, ino);
122 expect_open(ino, FOPEN_DIRECT_IO, 1);
123 expect_getattr(ino, offset + 1000);
125 fd = open(FULLPATH, O_RDONLY);
126 ASSERT_LE(0, fd) << strerror(errno);
128 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
129 /* Deliberately leak fd. close(2) will be tested in release.cc */
133 * With direct_io, reads should not fill the cache. They should go straight to
136 TEST_F(Read, direct_io_pread)
138 const char FULLPATH[] = "mountpoint/some_file.txt";
139 const char RELPATH[] = "some_file.txt";
140 const char *CONTENTS = "abcdefgh";
143 uint64_t offset = 100;
144 ssize_t bufsize = strlen(CONTENTS);
147 expect_lookup(RELPATH, ino);
148 expect_open(ino, FOPEN_DIRECT_IO, 1);
149 expect_getattr(ino, offset + bufsize);
150 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
152 fd = open(FULLPATH, O_RDONLY);
153 ASSERT_LE(0, fd) << strerror(errno);
155 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
156 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
157 /* Deliberately leak fd. close(2) will be tested in release.cc */
161 * With direct_io, filesystems are allowed to return less data than is
162 * requested. fuse(4) should return a short read to userland.
164 TEST_F(Read, direct_io_short_read)
166 const char FULLPATH[] = "mountpoint/some_file.txt";
167 const char RELPATH[] = "some_file.txt";
168 const char *CONTENTS = "abcdefghijklmnop";
171 uint64_t offset = 100;
172 ssize_t bufsize = strlen(CONTENTS);
173 ssize_t halfbufsize = bufsize / 2;
176 expect_lookup(RELPATH, ino);
177 expect_open(ino, FOPEN_DIRECT_IO, 1);
178 expect_getattr(ino, offset + bufsize);
179 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
181 fd = open(FULLPATH, O_RDONLY);
182 ASSERT_LE(0, fd) << strerror(errno);
184 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
186 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
187 /* Deliberately leak fd. close(2) will be tested in release.cc */
192 const char FULLPATH[] = "mountpoint/some_file.txt";
193 const char RELPATH[] = "some_file.txt";
194 const char *CONTENTS = "abcdefgh";
197 ssize_t bufsize = strlen(CONTENTS);
200 expect_lookup(RELPATH, ino);
201 expect_open(ino, 0, 1);
202 expect_getattr(ino, bufsize);
203 EXPECT_CALL(*m_mock, process(
204 ResultOf([=](auto in) {
205 return (in->header.opcode == FUSE_READ);
208 ).WillOnce(Invoke(ReturnErrno(EIO)));
210 fd = open(FULLPATH, O_RDONLY);
211 ASSERT_LE(0, fd) << strerror(errno);
213 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
214 ASSERT_EQ(EIO, errno);
215 /* Deliberately leak fd. close(2) will be tested in release.cc */
220 const char FULLPATH[] = "mountpoint/some_file.txt";
221 const char RELPATH[] = "some_file.txt";
222 const char *CONTENTS = "abcdefgh";
226 ssize_t bufsize = strlen(CONTENTS);
232 expect_lookup(RELPATH, ino);
233 expect_open(ino, 0, 1);
234 expect_getattr(ino, bufsize);
235 /* mmap may legitimately try to read more data than is available */
236 EXPECT_CALL(*m_mock, process(
237 ResultOf([=](auto in) {
238 return (in->header.opcode == FUSE_READ &&
239 in->header.nodeid == ino &&
240 in->body.read.fh == Read::FH &&
241 in->body.read.offset == 0 &&
242 in->body.read.size >= bufsize);
245 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
246 out->header.len = sizeof(struct fuse_out_header) + bufsize;
247 memmove(out->body.bytes, CONTENTS, bufsize);
250 fd = open(FULLPATH, O_RDONLY);
251 ASSERT_LE(0, fd) << strerror(errno);
253 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
254 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
256 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
258 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
259 /* Deliberately leak fd. close(2) will be tested in release.cc */
263 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
264 * cache and to straight to the daemon
266 TEST_F(Read, o_direct)
268 const char FULLPATH[] = "mountpoint/some_file.txt";
269 const char RELPATH[] = "some_file.txt";
270 const char *CONTENTS = "abcdefgh";
273 ssize_t bufsize = strlen(CONTENTS);
276 expect_lookup(RELPATH, ino);
277 expect_open(ino, 0, 1);
278 expect_getattr(ino, bufsize);
279 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
281 fd = open(FULLPATH, O_RDONLY);
282 ASSERT_LE(0, fd) << strerror(errno);
285 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
286 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
288 // Reads with o_direct should bypass the cache
289 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
290 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
291 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
292 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
293 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
295 /* Deliberately leak fd. close(2) will be tested in release.cc */
300 const char FULLPATH[] = "mountpoint/some_file.txt";
301 const char RELPATH[] = "some_file.txt";
302 const char *CONTENTS = "abcdefgh";
306 * Set offset to a maxbcachebuf boundary so we'll be sure what offset
307 * to read from. Without this, the read might start at a lower offset.
309 uint64_t offset = m_maxbcachebuf;
310 ssize_t bufsize = strlen(CONTENTS);
313 expect_lookup(RELPATH, ino);
314 expect_open(ino, 0, 1);
315 expect_getattr(ino, offset + bufsize);
316 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
318 fd = open(FULLPATH, O_RDONLY);
319 ASSERT_LE(0, fd) << strerror(errno);
321 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
322 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
323 /* Deliberately leak fd. close(2) will be tested in release.cc */
328 const char FULLPATH[] = "mountpoint/some_file.txt";
329 const char RELPATH[] = "some_file.txt";
330 const char *CONTENTS = "abcdefgh";
333 ssize_t bufsize = strlen(CONTENTS);
336 expect_lookup(RELPATH, ino);
337 expect_open(ino, 0, 1);
338 expect_getattr(ino, bufsize);
339 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
341 fd = open(FULLPATH, O_RDONLY);
342 ASSERT_LE(0, fd) << strerror(errno);
344 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
345 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
346 /* Deliberately leak fd. close(2) will be tested in release.cc */
349 /* If the filesystem allows it, the kernel should try to readahead */
350 TEST_F(Read, default_readahead)
352 const char FULLPATH[] = "mountpoint/some_file.txt";
353 const char RELPATH[] = "some_file.txt";
354 const char *CONTENTS0 = "abcdefghijklmnop";
358 /* hard-coded in fuse_internal.c */
359 size_t default_maxreadahead = 65536;
360 ssize_t filesize = default_maxreadahead * 2;
363 const char *contents1 = CONTENTS0 + bufsize;
365 contents = (char*)calloc(1, filesize);
366 ASSERT_NE(NULL, contents);
367 memmove(contents, CONTENTS0, strlen(CONTENTS0));
369 expect_lookup(RELPATH, ino);
370 expect_open(ino, 0, 1);
371 expect_getattr(ino, filesize);
372 expect_read(ino, 0, default_maxreadahead, default_maxreadahead,
375 fd = open(FULLPATH, O_RDONLY);
376 ASSERT_LE(0, fd) << strerror(errno);
378 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
379 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
381 /* A subsequent read should be serviced by cache */
382 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
383 ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
384 /* Deliberately leak fd. close(2) will be tested in release.cc */
387 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
388 TEST_F(Read, sendfile)
390 const char FULLPATH[] = "mountpoint/some_file.txt";
391 const char RELPATH[] = "some_file.txt";
392 const char *CONTENTS = "abcdefgh";
395 ssize_t bufsize = strlen(CONTENTS);
400 expect_lookup(RELPATH, ino);
401 expect_open(ino, 0, 1);
402 expect_getattr(ino, bufsize);
403 /* Like mmap, sendfile may request more data than is available */
404 EXPECT_CALL(*m_mock, process(
405 ResultOf([=](auto in) {
406 return (in->header.opcode == FUSE_READ &&
407 in->header.nodeid == ino &&
408 in->body.read.fh == Read::FH &&
409 in->body.read.offset == 0 &&
410 in->body.read.size >= bufsize);
413 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
414 out->header.len = sizeof(struct fuse_out_header) + bufsize;
415 memmove(out->body.bytes, CONTENTS, bufsize);
418 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
420 fd = open(FULLPATH, O_RDONLY);
421 ASSERT_LE(0, fd) << strerror(errno);
423 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
425 ASSERT_EQ(bufsize, read(sp[0], buf, bufsize)) << strerror(errno);
426 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
430 /* Deliberately leak fd. close(2) will be tested in release.cc */
433 /* sendfile should fail gracefully if fuse declines the read */
434 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
435 TEST_F(Read, DISABLED_sendfile_eio)
437 const char FULLPATH[] = "mountpoint/some_file.txt";
438 const char RELPATH[] = "some_file.txt";
439 const char *CONTENTS = "abcdefgh";
442 ssize_t bufsize = strlen(CONTENTS);
446 expect_lookup(RELPATH, ino);
447 expect_open(ino, 0, 1);
448 expect_getattr(ino, bufsize);
449 EXPECT_CALL(*m_mock, process(
450 ResultOf([=](auto in) {
451 return (in->header.opcode == FUSE_READ);
454 ).WillOnce(Invoke(ReturnErrno(EIO)));
456 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
458 fd = open(FULLPATH, O_RDONLY);
459 ASSERT_LE(0, fd) << strerror(errno);
461 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
465 /* Deliberately leak fd. close(2) will be tested in release.cc */
468 /* fuse(4) should honor the filesystem's requested m_readahead parameter */
469 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236472 */
470 TEST_P(ReadAhead, DISABLED_readahead) {
471 const char FULLPATH[] = "mountpoint/some_file.txt";
472 const char RELPATH[] = "some_file.txt";
473 const char *CONTENTS0 = "abcdefghijklmnop";
477 ssize_t filesize = m_maxbcachebuf * 2;
481 ASSERT_TRUE(GetParam() < (uint32_t)m_maxbcachebuf)
482 << "Test assumes that max_readahead < maxbcachebuf";
484 contents = (char*)calloc(1, filesize);
485 ASSERT_NE(NULL, contents);
486 memmove(contents, CONTENTS0, strlen(CONTENTS0));
488 expect_lookup(RELPATH, ino);
489 expect_open(ino, 0, 1);
490 expect_getattr(ino, filesize);
491 /* fuse(4) should only read ahead the allowed amount */
492 expect_read(ino, 0, GetParam(), GetParam(), contents);
494 fd = open(FULLPATH, O_RDONLY);
495 ASSERT_LE(0, fd) << strerror(errno);
497 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
498 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
500 /* Deliberately leak fd. close(2) will be tested in release.cc */
503 INSTANTIATE_TEST_CASE_P(RA, ReadAhead, ::testing::Values(0u, 2048u));