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