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