]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/read.cc
MFHead@r345880
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / read.cc
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/types.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 <unistd.h>
41 }
42
43 #include "mockfs.hh"
44 #include "utils.hh"
45
46 using namespace testing;
47
48 class Read: public FuseTest {
49
50 public:
51 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
52 {
53         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
54 }
55 };
56
57 class AioRead: public Read {
58 public:
59 virtual void SetUp() {
60         const char *node = "vfs.aio.enable_unsafe";
61         int val = 0;
62         size_t size = sizeof(val);
63
64         FuseTest::SetUp();
65
66         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
67                 << strerror(errno);
68         if (!val)
69                 GTEST_SKIP() <<
70                         "vfs.aio.enable_unsafe must be set for this test";
71 }
72 };
73
74 class AsyncRead: public AioRead {
75         virtual void SetUp() {
76                 m_init_flags = FUSE_ASYNC_READ;
77                 AioRead::SetUp();
78         }
79 };
80
81 class ReadCacheable: public Read {
82 public:
83 virtual void SetUp() {
84         const char *node = "vfs.fusefs.data_cache_mode";
85         int val = 0;
86         size_t size = sizeof(val);
87
88         FuseTest::SetUp();
89
90         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
91                 << strerror(errno);
92         if (val == 0)
93                 GTEST_SKIP() <<
94                         "fusefs data caching must be enabled for this test";
95 }
96 };
97
98 class ReadAhead: public ReadCacheable, public WithParamInterface<uint32_t> {
99         virtual void SetUp() {
100                 m_maxreadahead = GetParam();
101                 Read::SetUp();
102         }
103 };
104
105 /* AIO reads need to set the header's pid field correctly */
106 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
107 TEST_F(AioRead, aio_read)
108 {
109         const char FULLPATH[] = "mountpoint/some_file.txt";
110         const char RELPATH[] = "some_file.txt";
111         const char *CONTENTS = "abcdefgh";
112         uint64_t ino = 42;
113         int fd;
114         ssize_t bufsize = strlen(CONTENTS);
115         char buf[bufsize];
116         struct aiocb iocb, *piocb;
117
118         expect_lookup(RELPATH, ino, bufsize);
119         expect_open(ino, 0, 1);
120         expect_getattr(ino, bufsize);
121         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
122
123         fd = open(FULLPATH, O_RDONLY);
124         ASSERT_LE(0, fd) << strerror(errno);
125
126         iocb.aio_nbytes = bufsize;
127         iocb.aio_fildes = fd;
128         iocb.aio_buf = buf;
129         iocb.aio_offset = 0;
130         iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
131         ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
132         ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
133         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
134         /* Deliberately leak fd.  close(2) will be tested in release.cc */
135 }
136
137 /* 
138  * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there
139  * is at most one outstanding read operation per file handle
140  */
141 TEST_F(AioRead, async_read_disabled)
142 {
143         const char FULLPATH[] = "mountpoint/some_file.txt";
144         const char RELPATH[] = "some_file.txt";
145         uint64_t ino = 42;
146         int fd;
147         ssize_t bufsize = 50;
148         char buf0[bufsize], buf1[bufsize];
149         off_t off0 = 0;
150         off_t off1 = 4096;
151         struct aiocb iocb0, iocb1;
152
153         expect_lookup(RELPATH, ino, bufsize);
154         expect_open(ino, 0, 1);
155         expect_getattr(ino, bufsize);
156         EXPECT_CALL(*m_mock, process(
157                 ResultOf([=](auto in) {
158                         return (in->header.opcode == FUSE_READ &&
159                                 in->header.nodeid == ino &&
160                                 in->body.read.fh == FH &&
161                                 in->body.read.offset == (uint64_t)off0 &&
162                                 in->body.read.size == bufsize);
163                 }, Eq(true)),
164                 _)
165         ).WillOnce(Invoke([](auto in __unused, auto &out __unused) {
166                 /* Filesystem is slow to respond */
167         }));
168         EXPECT_CALL(*m_mock, process(
169                 ResultOf([=](auto in) {
170                         return (in->header.opcode == FUSE_READ &&
171                                 in->header.nodeid == ino &&
172                                 in->body.read.fh == FH &&
173                                 in->body.read.offset == (uint64_t)off1 &&
174                                 in->body.read.size == bufsize);
175                 }, Eq(true)),
176                 _)
177         ).Times(0);
178
179         fd = open(FULLPATH, O_RDONLY);
180         ASSERT_LE(0, fd) << strerror(errno);
181
182         /* 
183          * Submit two AIO read requests, and respond to neither.  If the
184          * filesystem ever gets the second read request, then we failed to
185          * limit outstanding reads.
186          */
187         iocb0.aio_nbytes = bufsize;
188         iocb0.aio_fildes = fd;
189         iocb0.aio_buf = buf0;
190         iocb0.aio_offset = off0;
191         iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
192         ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
193
194         iocb1.aio_nbytes = bufsize;
195         iocb1.aio_fildes = fd;
196         iocb1.aio_buf = buf1;
197         iocb1.aio_offset = off1;
198         iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
199         ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
200
201         /* 
202          * Sleep for awhile to make sure the kernel has had a chance to issue
203          * the second read, even though the first has not yet returned
204          */
205         usleep(250'000);
206         
207         /* Deliberately leak iocbs */
208         /* Deliberately leak fd.  close(2) will be tested in release.cc */
209 }
210
211 /* 
212  * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple
213  * simultaneous read requests on the same file handle.
214  */
215 /* 
216  * Disabled because we don't yet implement FUSE_ASYNC_READ.  No bugzilla
217  * entry, because that's a feature request, not a bug.
218  */
219 TEST_F(AsyncRead, DISABLED_async_read)
220 {
221         const char FULLPATH[] = "mountpoint/some_file.txt";
222         const char RELPATH[] = "some_file.txt";
223         uint64_t ino = 42;
224         int fd;
225         ssize_t bufsize = 50;
226         char buf0[bufsize], buf1[bufsize];
227         off_t off0 = 0;
228         off_t off1 = 4096;
229         struct aiocb iocb0, iocb1;
230
231         expect_lookup(RELPATH, ino, bufsize);
232         expect_open(ino, 0, 1);
233         expect_getattr(ino, bufsize);
234         EXPECT_CALL(*m_mock, process(
235                 ResultOf([=](auto in) {
236                         return (in->header.opcode == FUSE_READ &&
237                                 in->header.nodeid == ino &&
238                                 in->body.read.fh == FH &&
239                                 in->body.read.offset == (uint64_t)off0 &&
240                                 in->body.read.size == bufsize);
241                 }, Eq(true)),
242                 _)
243         ).WillOnce(Invoke([](auto in __unused, auto &out __unused) {
244                 /* Filesystem is slow to respond */
245         }));
246         EXPECT_CALL(*m_mock, process(
247                 ResultOf([=](auto in) {
248                         return (in->header.opcode == FUSE_READ &&
249                                 in->header.nodeid == ino &&
250                                 in->body.read.fh == FH &&
251                                 in->body.read.offset == (uint64_t)off1 &&
252                                 in->body.read.size == bufsize);
253                 }, Eq(true)),
254                 _)
255         ).WillOnce(Invoke([](auto in __unused, auto &out __unused) {
256                 /* Filesystem is slow to respond */
257         }));
258
259         fd = open(FULLPATH, O_RDONLY);
260         ASSERT_LE(0, fd) << strerror(errno);
261
262         /* 
263          * Submit two AIO read requests, but respond to neither.  Ensure that
264          * we received both.
265          */
266         iocb0.aio_nbytes = bufsize;
267         iocb0.aio_fildes = fd;
268         iocb0.aio_buf = buf0;
269         iocb0.aio_offset = off0;
270         iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
271         ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
272
273         iocb1.aio_nbytes = bufsize;
274         iocb1.aio_fildes = fd;
275         iocb1.aio_buf = buf1;
276         iocb1.aio_offset = off1;
277         iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
278         ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
279
280         /* 
281          * Sleep for awhile to make sure the kernel has had a chance to issue
282          * both reads.
283          */
284         usleep(250'000);
285         
286         /* Deliberately leak iocbs */
287         /* Deliberately leak fd.  close(2) will be tested in release.cc */
288 }
289
290 /* 0-length reads shouldn't cause any confusion */
291 TEST_F(Read, direct_io_read_nothing)
292 {
293         const char FULLPATH[] = "mountpoint/some_file.txt";
294         const char RELPATH[] = "some_file.txt";
295         uint64_t ino = 42;
296         int fd;
297         uint64_t offset = 100;
298         char buf[80];
299
300         expect_lookup(RELPATH, ino, offset + 1000);
301         expect_open(ino, FOPEN_DIRECT_IO, 1);
302         expect_getattr(ino, offset + 1000);
303
304         fd = open(FULLPATH, O_RDONLY);
305         ASSERT_LE(0, fd) << strerror(errno);
306
307         ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
308         /* Deliberately leak fd.  close(2) will be tested in release.cc */
309 }
310
311 /* 
312  * With direct_io, reads should not fill the cache.  They should go straight to
313  * the daemon
314  */
315 TEST_F(Read, direct_io_pread)
316 {
317         const char FULLPATH[] = "mountpoint/some_file.txt";
318         const char RELPATH[] = "some_file.txt";
319         const char *CONTENTS = "abcdefgh";
320         uint64_t ino = 42;
321         int fd;
322         uint64_t offset = 100;
323         ssize_t bufsize = strlen(CONTENTS);
324         char buf[bufsize];
325
326         expect_lookup(RELPATH, ino, offset + bufsize);
327         expect_open(ino, FOPEN_DIRECT_IO, 1);
328         expect_getattr(ino, offset + bufsize);
329         expect_read(ino, offset, bufsize, bufsize, CONTENTS);
330
331         fd = open(FULLPATH, O_RDONLY);
332         ASSERT_LE(0, fd) << strerror(errno);
333
334         ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
335         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
336         /* Deliberately leak fd.  close(2) will be tested in release.cc */
337 }
338
339 /* 
340  * With direct_io, filesystems are allowed to return less data than is
341  * requested.  fuse(4) should return a short read to userland.
342  */
343 TEST_F(Read, direct_io_short_read)
344 {
345         const char FULLPATH[] = "mountpoint/some_file.txt";
346         const char RELPATH[] = "some_file.txt";
347         const char *CONTENTS = "abcdefghijklmnop";
348         uint64_t ino = 42;
349         int fd;
350         uint64_t offset = 100;
351         ssize_t bufsize = strlen(CONTENTS);
352         ssize_t halfbufsize = bufsize / 2;
353         char buf[bufsize];
354
355         expect_lookup(RELPATH, ino, offset + bufsize);
356         expect_open(ino, FOPEN_DIRECT_IO, 1);
357         expect_getattr(ino, offset + bufsize);
358         expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
359
360         fd = open(FULLPATH, O_RDONLY);
361         ASSERT_LE(0, fd) << strerror(errno);
362
363         ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
364                 << strerror(errno);
365         ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
366         /* Deliberately leak fd.  close(2) will be tested in release.cc */
367 }
368
369 TEST_F(Read, eio)
370 {
371         const char FULLPATH[] = "mountpoint/some_file.txt";
372         const char RELPATH[] = "some_file.txt";
373         const char *CONTENTS = "abcdefgh";
374         uint64_t ino = 42;
375         int fd;
376         ssize_t bufsize = strlen(CONTENTS);
377         char buf[bufsize];
378
379         expect_lookup(RELPATH, ino, bufsize);
380         expect_open(ino, 0, 1);
381         expect_getattr(ino, bufsize);
382         EXPECT_CALL(*m_mock, process(
383                 ResultOf([=](auto in) {
384                         return (in->header.opcode == FUSE_READ);
385                 }, Eq(true)),
386                 _)
387         ).WillOnce(Invoke(ReturnErrno(EIO)));
388
389         fd = open(FULLPATH, O_RDONLY);
390         ASSERT_LE(0, fd) << strerror(errno);
391
392         ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
393         ASSERT_EQ(EIO, errno);
394         /* Deliberately leak fd.  close(2) will be tested in release.cc */
395 }
396
397 /* 
398  * With the keep_cache option, the kernel may keep its read cache across
399  * multiple open(2)s.
400  */
401 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236560 */
402 TEST_F(Read, DISABLED_keep_cache)
403 {
404         const char FULLPATH[] = "mountpoint/some_file.txt";
405         const char RELPATH[] = "some_file.txt";
406         const char *CONTENTS = "abcdefgh";
407         uint64_t ino = 42;
408         int fd0, fd1;
409         ssize_t bufsize = strlen(CONTENTS);
410         char buf[bufsize];
411
412         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
413         expect_open(ino, FOPEN_KEEP_CACHE, 1);
414         expect_getattr(ino, bufsize);
415         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
416
417         fd0 = open(FULLPATH, O_RDONLY);
418         ASSERT_LE(0, fd0) << strerror(errno);
419         ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
420
421         fd1 = open(FULLPATH, O_RDONLY);
422         ASSERT_LE(0, fd1) << strerror(errno);
423
424         /*
425          * This read should be serviced by cache, even though it's on the other
426          * file descriptor
427          */
428         ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
429
430         /* Deliberately leak fd0 and fd1. */
431 }
432
433 /* 
434  * Without the keep_cache option, the kernel should drop its read caches on
435  * every open
436  */
437 TEST_F(Read, keep_cache_disabled)
438 {
439         const char FULLPATH[] = "mountpoint/some_file.txt";
440         const char RELPATH[] = "some_file.txt";
441         const char *CONTENTS = "abcdefgh";
442         uint64_t ino = 42;
443         int fd0, fd1;
444         ssize_t bufsize = strlen(CONTENTS);
445         char buf[bufsize];
446
447         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
448         expect_open(ino, FOPEN_KEEP_CACHE, 1);
449         expect_getattr(ino, bufsize);
450         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
451
452         fd0 = open(FULLPATH, O_RDONLY);
453         ASSERT_LE(0, fd0) << strerror(errno);
454         ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
455
456         fd1 = open(FULLPATH, O_RDONLY);
457         ASSERT_LE(0, fd1) << strerror(errno);
458
459         /*
460          * This read should not be serviced by cache, even though it's on the
461          * original file descriptor
462          */
463         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
464         ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno);
465         ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
466
467         /* Deliberately leak fd0 and fd1. */
468 }
469
470 TEST_F(ReadCacheable, mmap)
471 {
472         const char FULLPATH[] = "mountpoint/some_file.txt";
473         const char RELPATH[] = "some_file.txt";
474         const char *CONTENTS = "abcdefgh";
475         uint64_t ino = 42;
476         int fd;
477         ssize_t len;
478         ssize_t bufsize = strlen(CONTENTS);
479         void *p;
480         //char buf[bufsize];
481
482         len = getpagesize();
483
484         expect_lookup(RELPATH, ino, bufsize);
485         expect_open(ino, 0, 1);
486         expect_getattr(ino, bufsize);
487         /* mmap may legitimately try to read more data than is available */
488         EXPECT_CALL(*m_mock, process(
489                 ResultOf([=](auto in) {
490                         return (in->header.opcode == FUSE_READ &&
491                                 in->header.nodeid == ino &&
492                                 in->body.read.fh == Read::FH &&
493                                 in->body.read.offset == 0 &&
494                                 in->body.read.size >= bufsize);
495                 }, Eq(true)),
496                 _)
497         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
498                 out->header.len = sizeof(struct fuse_out_header) + bufsize;
499                 memmove(out->body.bytes, CONTENTS, bufsize);
500         })));
501
502         fd = open(FULLPATH, O_RDONLY);
503         ASSERT_LE(0, fd) << strerror(errno);
504
505         p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
506         ASSERT_NE(MAP_FAILED, p) << strerror(errno);
507
508         ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
509
510         ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
511         /* Deliberately leak fd.  close(2) will be tested in release.cc */
512 }
513
514 /*
515  * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
516  * cache and to straight to the daemon
517  */
518 TEST_F(Read, o_direct)
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 fd;
525         ssize_t bufsize = strlen(CONTENTS);
526         char buf[bufsize];
527
528         expect_lookup(RELPATH, ino, bufsize);
529         expect_open(ino, 0, 1);
530         expect_getattr(ino, bufsize);
531         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
532
533         fd = open(FULLPATH, O_RDONLY);
534         ASSERT_LE(0, fd) << strerror(errno);
535
536         // Fill the cache
537         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
538         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
539
540         // Reads with o_direct should bypass the cache
541         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
542         ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
543         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
544         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
545         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
546         
547         /* Deliberately leak fd.  close(2) will be tested in release.cc */
548 }
549
550 TEST_F(Read, pread)
551 {
552         const char FULLPATH[] = "mountpoint/some_file.txt";
553         const char RELPATH[] = "some_file.txt";
554         const char *CONTENTS = "abcdefgh";
555         uint64_t ino = 42;
556         int fd;
557         /* 
558          * Set offset to a maxbcachebuf boundary so we'll be sure what offset
559          * to read from.  Without this, the read might start at a lower offset.
560          */
561         uint64_t offset = m_maxbcachebuf;
562         ssize_t bufsize = strlen(CONTENTS);
563         char buf[bufsize];
564
565         expect_lookup(RELPATH, ino, offset + bufsize);
566         expect_open(ino, 0, 1);
567         expect_getattr(ino, offset + bufsize);
568         expect_read(ino, offset, bufsize, bufsize, CONTENTS);
569
570         fd = open(FULLPATH, O_RDONLY);
571         ASSERT_LE(0, fd) << strerror(errno);
572
573         ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
574         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
575         /* Deliberately leak fd.  close(2) will be tested in release.cc */
576 }
577
578 TEST_F(Read, read)
579 {
580         const char FULLPATH[] = "mountpoint/some_file.txt";
581         const char RELPATH[] = "some_file.txt";
582         const char *CONTENTS = "abcdefgh";
583         uint64_t ino = 42;
584         int fd;
585         ssize_t bufsize = strlen(CONTENTS);
586         char buf[bufsize];
587
588         expect_lookup(RELPATH, ino, bufsize);
589         expect_open(ino, 0, 1);
590         expect_getattr(ino, bufsize);
591         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
592
593         fd = open(FULLPATH, O_RDONLY);
594         ASSERT_LE(0, fd) << strerror(errno);
595
596         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
597         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
598
599         /* Deliberately leak fd.  close(2) will be tested in release.cc */
600 }
601
602 /* If the filesystem allows it, the kernel should try to readahead */
603 TEST_F(ReadCacheable, default_readahead)
604 {
605         const char FULLPATH[] = "mountpoint/some_file.txt";
606         const char RELPATH[] = "some_file.txt";
607         const char *CONTENTS0 = "abcdefghijklmnop";
608         uint64_t ino = 42;
609         int fd;
610         ssize_t bufsize = 8;
611         /* hard-coded in fuse_internal.c */
612         size_t default_maxreadahead = 65536;
613         ssize_t filesize = default_maxreadahead * 2;
614         char *contents;
615         char buf[bufsize];
616         const char *contents1 = CONTENTS0 + bufsize;
617
618         contents = (char*)calloc(1, filesize);
619         ASSERT_NE(NULL, contents);
620         memmove(contents, CONTENTS0, strlen(CONTENTS0));
621
622         expect_lookup(RELPATH, ino, filesize);
623         expect_open(ino, 0, 1);
624         expect_getattr(ino, filesize);
625         expect_read(ino, 0, default_maxreadahead, default_maxreadahead,
626                 contents);
627
628         fd = open(FULLPATH, O_RDONLY);
629         ASSERT_LE(0, fd) << strerror(errno);
630
631         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
632         ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
633
634         /* A subsequent read should be serviced by cache */
635         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
636         ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
637         /* Deliberately leak fd.  close(2) will be tested in release.cc */
638 }
639
640 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
641 TEST_F(ReadCacheable, sendfile)
642 {
643         const char FULLPATH[] = "mountpoint/some_file.txt";
644         const char RELPATH[] = "some_file.txt";
645         const char *CONTENTS = "abcdefgh";
646         uint64_t ino = 42;
647         int fd;
648         ssize_t bufsize = strlen(CONTENTS);
649         char buf[bufsize];
650         int sp[2];
651         off_t sbytes;
652
653         expect_lookup(RELPATH, ino, bufsize);
654         expect_open(ino, 0, 1);
655         expect_getattr(ino, bufsize);
656         /* Like mmap, sendfile may request more data than is available */
657         EXPECT_CALL(*m_mock, process(
658                 ResultOf([=](auto in) {
659                         return (in->header.opcode == FUSE_READ &&
660                                 in->header.nodeid == ino &&
661                                 in->body.read.fh == Read::FH &&
662                                 in->body.read.offset == 0 &&
663                                 in->body.read.size >= bufsize);
664                 }, Eq(true)),
665                 _)
666         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
667                 out->header.len = sizeof(struct fuse_out_header) + bufsize;
668                 memmove(out->body.bytes, CONTENTS, bufsize);
669         })));
670
671         ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
672                 << strerror(errno);
673         fd = open(FULLPATH, O_RDONLY);
674         ASSERT_LE(0, fd) << strerror(errno);
675
676         ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
677                 << strerror(errno);
678         ASSERT_EQ(bufsize, read(sp[0], buf, bufsize)) << strerror(errno);
679         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
680
681         close(sp[1]);
682         close(sp[0]);
683         /* Deliberately leak fd.  close(2) will be tested in release.cc */
684 }
685
686 /* sendfile should fail gracefully if fuse declines the read */
687 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
688 TEST_F(ReadCacheable, DISABLED_sendfile_eio)
689 {
690         const char FULLPATH[] = "mountpoint/some_file.txt";
691         const char RELPATH[] = "some_file.txt";
692         const char *CONTENTS = "abcdefgh";
693         uint64_t ino = 42;
694         int fd;
695         ssize_t bufsize = strlen(CONTENTS);
696         int sp[2];
697         off_t sbytes;
698
699         expect_lookup(RELPATH, ino, bufsize);
700         expect_open(ino, 0, 1);
701         expect_getattr(ino, bufsize);
702         EXPECT_CALL(*m_mock, process(
703                 ResultOf([=](auto in) {
704                         return (in->header.opcode == FUSE_READ);
705                 }, Eq(true)),
706                 _)
707         ).WillOnce(Invoke(ReturnErrno(EIO)));
708
709         ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
710                 << strerror(errno);
711         fd = open(FULLPATH, O_RDONLY);
712         ASSERT_LE(0, fd) << strerror(errno);
713
714         ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
715
716         close(sp[1]);
717         close(sp[0]);
718         /* Deliberately leak fd.  close(2) will be tested in release.cc */
719 }
720
721 /* fuse(4) should honor the filesystem's requested m_readahead parameter */
722 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236472 */
723 TEST_P(ReadAhead, DISABLED_readahead) {
724         const char FULLPATH[] = "mountpoint/some_file.txt";
725         const char RELPATH[] = "some_file.txt";
726         const char *CONTENTS0 = "abcdefghijklmnop";
727         uint64_t ino = 42;
728         int fd;
729         ssize_t bufsize = 8;
730         ssize_t filesize = m_maxbcachebuf * 2;
731         char *contents;
732         char buf[bufsize];
733
734         ASSERT_TRUE(GetParam() < (uint32_t)m_maxbcachebuf)
735                 << "Test assumes that max_readahead < maxbcachebuf";
736
737         contents = (char*)calloc(1, filesize);
738         ASSERT_NE(NULL, contents);
739         memmove(contents, CONTENTS0, strlen(CONTENTS0));
740
741         expect_lookup(RELPATH, ino, filesize);
742         expect_open(ino, 0, 1);
743         expect_getattr(ino, filesize);
744         /* fuse(4) should only read ahead the allowed amount */
745         expect_read(ino, 0, GetParam(), GetParam(), contents);
746
747         fd = open(FULLPATH, O_RDONLY);
748         ASSERT_LE(0, fd) << strerror(errno);
749
750         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
751         ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
752
753         /* Deliberately leak fd.  close(2) will be tested in release.cc */
754 }
755
756 INSTANTIATE_TEST_CASE_P(RA, ReadAhead, ::testing::Values(0u, 2048u));