]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/read.cc
fuse(4): combine common code in the tests
[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)
52 {
53         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1);
54 }
55 };
56
57 class AioRead: public Read {
58 virtual void SetUp() {
59         const char *node = "vfs.aio.enable_unsafe";
60         int val = 0;
61         size_t size = sizeof(val);
62
63         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
64                 << strerror(errno);
65         // TODO: With GoogleTest 1.8.2, use SKIP instead
66         if (!val)
67                 FAIL() << "vfs.aio.enable_unsafe must be set for this test";
68         FuseTest::SetUp();
69 }
70 };
71
72 class ReadAhead: public Read, public WithParamInterface<uint32_t> {
73         virtual void SetUp() {
74                 m_maxreadahead = GetParam();
75                 Read::SetUp();
76         }
77 };
78
79 /* AIO reads need to set the header's pid field correctly */
80 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
81 TEST_F(AioRead, aio_read)
82 {
83         const char FULLPATH[] = "mountpoint/some_file.txt";
84         const char RELPATH[] = "some_file.txt";
85         const char *CONTENTS = "abcdefgh";
86         uint64_t ino = 42;
87         int fd;
88         ssize_t bufsize = strlen(CONTENTS);
89         char buf[bufsize];
90         struct aiocb iocb, *piocb;
91
92         expect_lookup(RELPATH, ino);
93         expect_open(ino, 0, 1);
94         expect_getattr(ino, bufsize);
95         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
96
97         fd = open(FULLPATH, O_RDONLY);
98         ASSERT_LE(0, fd) << strerror(errno);
99
100         iocb.aio_nbytes = bufsize;
101         iocb.aio_fildes = fd;
102         iocb.aio_buf = buf;
103         iocb.aio_offset = 0;
104         iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
105         ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
106         ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
107         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
108         /* Deliberately leak fd.  close(2) will be tested in release.cc */
109 }
110
111 /* 0-length reads shouldn't cause any confusion */
112 TEST_F(Read, direct_io_read_nothing)
113 {
114         const char FULLPATH[] = "mountpoint/some_file.txt";
115         const char RELPATH[] = "some_file.txt";
116         uint64_t ino = 42;
117         int fd;
118         uint64_t offset = 100;
119         char buf[80];
120
121         expect_lookup(RELPATH, ino);
122         expect_open(ino, FOPEN_DIRECT_IO, 1);
123         expect_getattr(ino, offset + 1000);
124
125         fd = open(FULLPATH, O_RDONLY);
126         ASSERT_LE(0, fd) << strerror(errno);
127
128         ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
129         /* Deliberately leak fd.  close(2) will be tested in release.cc */
130 }
131
132 /* 
133  * With direct_io, reads should not fill the cache.  They should go straight to
134  * the daemon
135  */
136 TEST_F(Read, direct_io_pread)
137 {
138         const char FULLPATH[] = "mountpoint/some_file.txt";
139         const char RELPATH[] = "some_file.txt";
140         const char *CONTENTS = "abcdefgh";
141         uint64_t ino = 42;
142         int fd;
143         uint64_t offset = 100;
144         ssize_t bufsize = strlen(CONTENTS);
145         char buf[bufsize];
146
147         expect_lookup(RELPATH, ino);
148         expect_open(ino, FOPEN_DIRECT_IO, 1);
149         expect_getattr(ino, offset + bufsize);
150         expect_read(ino, offset, bufsize, bufsize, CONTENTS);
151
152         fd = open(FULLPATH, O_RDONLY);
153         ASSERT_LE(0, fd) << strerror(errno);
154
155         ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
156         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
157         /* Deliberately leak fd.  close(2) will be tested in release.cc */
158 }
159
160 /* 
161  * With direct_io, filesystems are allowed to return less data than is
162  * requested.  fuse(4) should return a short read to userland.
163  */
164 TEST_F(Read, direct_io_short_read)
165 {
166         const char FULLPATH[] = "mountpoint/some_file.txt";
167         const char RELPATH[] = "some_file.txt";
168         const char *CONTENTS = "abcdefghijklmnop";
169         uint64_t ino = 42;
170         int fd;
171         uint64_t offset = 100;
172         ssize_t bufsize = strlen(CONTENTS);
173         ssize_t halfbufsize = bufsize / 2;
174         char buf[bufsize];
175
176         expect_lookup(RELPATH, ino);
177         expect_open(ino, FOPEN_DIRECT_IO, 1);
178         expect_getattr(ino, offset + bufsize);
179         expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
180
181         fd = open(FULLPATH, O_RDONLY);
182         ASSERT_LE(0, fd) << strerror(errno);
183
184         ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
185                 << strerror(errno);
186         ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
187         /* Deliberately leak fd.  close(2) will be tested in release.cc */
188 }
189
190 TEST_F(Read, eio)
191 {
192         const char FULLPATH[] = "mountpoint/some_file.txt";
193         const char RELPATH[] = "some_file.txt";
194         const char *CONTENTS = "abcdefgh";
195         uint64_t ino = 42;
196         int fd;
197         ssize_t bufsize = strlen(CONTENTS);
198         char buf[bufsize];
199
200         expect_lookup(RELPATH, ino);
201         expect_open(ino, 0, 1);
202         expect_getattr(ino, bufsize);
203         EXPECT_CALL(*m_mock, process(
204                 ResultOf([=](auto in) {
205                         return (in->header.opcode == FUSE_READ);
206                 }, Eq(true)),
207                 _)
208         ).WillOnce(Invoke(ReturnErrno(EIO)));
209
210         fd = open(FULLPATH, O_RDONLY);
211         ASSERT_LE(0, fd) << strerror(errno);
212
213         ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
214         ASSERT_EQ(EIO, errno);
215         /* Deliberately leak fd.  close(2) will be tested in release.cc */
216 }
217
218 TEST_F(Read, mmap)
219 {
220         const char FULLPATH[] = "mountpoint/some_file.txt";
221         const char RELPATH[] = "some_file.txt";
222         const char *CONTENTS = "abcdefgh";
223         uint64_t ino = 42;
224         int fd;
225         ssize_t len;
226         ssize_t bufsize = strlen(CONTENTS);
227         void *p;
228         //char buf[bufsize];
229
230         len = getpagesize();
231
232         expect_lookup(RELPATH, ino);
233         expect_open(ino, 0, 1);
234         expect_getattr(ino, bufsize);
235         /* mmap may legitimately try to read more data than is available */
236         EXPECT_CALL(*m_mock, process(
237                 ResultOf([=](auto in) {
238                         return (in->header.opcode == FUSE_READ &&
239                                 in->header.nodeid == ino &&
240                                 in->body.read.fh == Read::FH &&
241                                 in->body.read.offset == 0 &&
242                                 in->body.read.size >= bufsize);
243                 }, Eq(true)),
244                 _)
245         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
246                 out->header.len = sizeof(struct fuse_out_header) + bufsize;
247                 memmove(out->body.bytes, CONTENTS, bufsize);
248         })));
249
250         fd = open(FULLPATH, O_RDONLY);
251         ASSERT_LE(0, fd) << strerror(errno);
252
253         p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
254         ASSERT_NE(MAP_FAILED, p) << strerror(errno);
255
256         ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
257
258         ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
259         /* Deliberately leak fd.  close(2) will be tested in release.cc */
260 }
261
262 /*
263  * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
264  * cache and to straight to the daemon
265  */
266 TEST_F(Read, o_direct)
267 {
268         const char FULLPATH[] = "mountpoint/some_file.txt";
269         const char RELPATH[] = "some_file.txt";
270         const char *CONTENTS = "abcdefgh";
271         uint64_t ino = 42;
272         int fd;
273         ssize_t bufsize = strlen(CONTENTS);
274         char buf[bufsize];
275
276         expect_lookup(RELPATH, ino);
277         expect_open(ino, 0, 1);
278         expect_getattr(ino, bufsize);
279         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
280
281         fd = open(FULLPATH, O_RDONLY);
282         ASSERT_LE(0, fd) << strerror(errno);
283
284         // Fill the cache
285         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
286         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
287
288         // Reads with o_direct should bypass the cache
289         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
290         ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
291         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
292         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
293         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
294         
295         /* Deliberately leak fd.  close(2) will be tested in release.cc */
296 }
297
298 TEST_F(Read, 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         /* 
306          * Set offset to a maxbcachebuf boundary so we'll be sure what offset
307          * to read from.  Without this, the read might start at a lower offset.
308          */
309         uint64_t offset = m_maxbcachebuf;
310         ssize_t bufsize = strlen(CONTENTS);
311         char buf[bufsize];
312
313         expect_lookup(RELPATH, ino);
314         expect_open(ino, 0, 1);
315         expect_getattr(ino, offset + bufsize);
316         expect_read(ino, offset, bufsize, bufsize, CONTENTS);
317
318         fd = open(FULLPATH, O_RDONLY);
319         ASSERT_LE(0, fd) << strerror(errno);
320
321         ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
322         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
323         /* Deliberately leak fd.  close(2) will be tested in release.cc */
324 }
325
326 TEST_F(Read, read)
327 {
328         const char FULLPATH[] = "mountpoint/some_file.txt";
329         const char RELPATH[] = "some_file.txt";
330         const char *CONTENTS = "abcdefgh";
331         uint64_t ino = 42;
332         int fd;
333         ssize_t bufsize = strlen(CONTENTS);
334         char buf[bufsize];
335
336         expect_lookup(RELPATH, ino);
337         expect_open(ino, 0, 1);
338         expect_getattr(ino, bufsize);
339         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
340
341         fd = open(FULLPATH, O_RDONLY);
342         ASSERT_LE(0, fd) << strerror(errno);
343
344         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
345         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
346         /* Deliberately leak fd.  close(2) will be tested in release.cc */
347 }
348
349 /* If the filesystem allows it, the kernel should try to readahead */
350 TEST_F(Read, default_readahead)
351 {
352         const char FULLPATH[] = "mountpoint/some_file.txt";
353         const char RELPATH[] = "some_file.txt";
354         const char *CONTENTS0 = "abcdefghijklmnop";
355         uint64_t ino = 42;
356         int fd;
357         ssize_t bufsize = 8;
358         /* hard-coded in fuse_internal.c */
359         size_t default_maxreadahead = 65536;
360         ssize_t filesize = default_maxreadahead * 2;
361         char *contents;
362         char buf[bufsize];
363         const char *contents1 = CONTENTS0 + bufsize;
364
365         contents = (char*)calloc(1, filesize);
366         ASSERT_NE(NULL, contents);
367         memmove(contents, CONTENTS0, strlen(CONTENTS0));
368
369         expect_lookup(RELPATH, ino);
370         expect_open(ino, 0, 1);
371         expect_getattr(ino, filesize);
372         expect_read(ino, 0, default_maxreadahead, default_maxreadahead,
373                 contents);
374
375         fd = open(FULLPATH, O_RDONLY);
376         ASSERT_LE(0, fd) << strerror(errno);
377
378         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
379         ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
380
381         /* A subsequent read should be serviced by cache */
382         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
383         ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
384         /* Deliberately leak fd.  close(2) will be tested in release.cc */
385 }
386
387 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
388 TEST_F(Read, sendfile)
389 {
390         const char FULLPATH[] = "mountpoint/some_file.txt";
391         const char RELPATH[] = "some_file.txt";
392         const char *CONTENTS = "abcdefgh";
393         uint64_t ino = 42;
394         int fd;
395         ssize_t bufsize = strlen(CONTENTS);
396         char buf[bufsize];
397         int sp[2];
398         off_t sbytes;
399
400         expect_lookup(RELPATH, ino);
401         expect_open(ino, 0, 1);
402         expect_getattr(ino, bufsize);
403         /* Like mmap, sendfile may request more data than is available */
404         EXPECT_CALL(*m_mock, process(
405                 ResultOf([=](auto in) {
406                         return (in->header.opcode == FUSE_READ &&
407                                 in->header.nodeid == ino &&
408                                 in->body.read.fh == Read::FH &&
409                                 in->body.read.offset == 0 &&
410                                 in->body.read.size >= bufsize);
411                 }, Eq(true)),
412                 _)
413         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
414                 out->header.len = sizeof(struct fuse_out_header) + bufsize;
415                 memmove(out->body.bytes, CONTENTS, bufsize);
416         })));
417
418         ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
419                 << strerror(errno);
420         fd = open(FULLPATH, O_RDONLY);
421         ASSERT_LE(0, fd) << strerror(errno);
422
423         ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
424                 << strerror(errno);
425         ASSERT_EQ(bufsize, read(sp[0], buf, bufsize)) << strerror(errno);
426         ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
427
428         close(sp[1]);
429         close(sp[0]);
430         /* Deliberately leak fd.  close(2) will be tested in release.cc */
431 }
432
433 /* sendfile should fail gracefully if fuse declines the read */
434 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
435 TEST_F(Read, DISABLED_sendfile_eio)
436 {
437         const char FULLPATH[] = "mountpoint/some_file.txt";
438         const char RELPATH[] = "some_file.txt";
439         const char *CONTENTS = "abcdefgh";
440         uint64_t ino = 42;
441         int fd;
442         ssize_t bufsize = strlen(CONTENTS);
443         int sp[2];
444         off_t sbytes;
445
446         expect_lookup(RELPATH, ino);
447         expect_open(ino, 0, 1);
448         expect_getattr(ino, bufsize);
449         EXPECT_CALL(*m_mock, process(
450                 ResultOf([=](auto in) {
451                         return (in->header.opcode == FUSE_READ);
452                 }, Eq(true)),
453                 _)
454         ).WillOnce(Invoke(ReturnErrno(EIO)));
455
456         ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
457                 << strerror(errno);
458         fd = open(FULLPATH, O_RDONLY);
459         ASSERT_LE(0, fd) << strerror(errno);
460
461         ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
462
463         close(sp[1]);
464         close(sp[0]);
465         /* Deliberately leak fd.  close(2) will be tested in release.cc */
466 }
467
468 /* fuse(4) should honor the filesystem's requested m_readahead parameter */
469 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236472 */
470 TEST_P(ReadAhead, DISABLED_readahead) {
471         const char FULLPATH[] = "mountpoint/some_file.txt";
472         const char RELPATH[] = "some_file.txt";
473         const char *CONTENTS0 = "abcdefghijklmnop";
474         uint64_t ino = 42;
475         int fd;
476         ssize_t bufsize = 8;
477         ssize_t filesize = m_maxbcachebuf * 2;
478         char *contents;
479         char buf[bufsize];
480
481         ASSERT_TRUE(GetParam() < (uint32_t)m_maxbcachebuf)
482                 << "Test assumes that max_readahead < maxbcachebuf";
483
484         contents = (char*)calloc(1, filesize);
485         ASSERT_NE(NULL, contents);
486         memmove(contents, CONTENTS0, strlen(CONTENTS0));
487
488         expect_lookup(RELPATH, ino);
489         expect_open(ino, 0, 1);
490         expect_getattr(ino, filesize);
491         /* fuse(4) should only read ahead the allowed amount */
492         expect_read(ino, 0, GetParam(), GetParam(), contents);
493
494         fd = open(FULLPATH, O_RDONLY);
495         ASSERT_LE(0, fd) << strerror(errno);
496
497         ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
498         ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
499
500         /* Deliberately leak fd.  close(2) will be tested in release.cc */
501 }
502
503 INSTANTIATE_TEST_CASE_P(RA, ReadAhead, ::testing::Values(0u, 2048u));