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