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