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