Super User's BSD Cross Reference: /FreeBSD/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 * $FreeBSD$
31 */
32
33 extern "C" {
34#include <sys/param.h>
35#include <sys/mman.h>
36#include <sys/socket.h>
37#include <sys/sysctl.h>
38#include <sys/uio.h>
39
40#include <aio.h>
41#include <fcntl.h>
42#include <semaphore.h>
43#include <unistd.h>
44}
45
46#include "mockfs.hh"
47#include "utils.hh"
48
49 using namespace testing;
50
51 class Read: public FuseTest {
52
53 public:
54 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
55{
56 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
57}
58};
59
60 class Read_7_8: public FuseTest {
61 public:
62 virtual void SetUp() {
63 m_kernel_minor_version = 8;
64 FuseTest::SetUp();
65}
66
67 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
68{
69 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
70}
71};
72
73 class AioRead: public Read {
74 public:
75 virtual void SetUp() {
76 if (!is_unsafe_aio_enabled())
77 GTEST_SKIP() <<
78 "vfs.aio.enable_unsafe must be set for this test";
79 FuseTest::SetUp();
80}
81};
82
83 class AsyncRead: public AioRead {
84 virtual void SetUp() {
85 m_init_flags = FUSE_ASYNC_READ;
86 AioRead::SetUp();
87 }
88};
89
90 class ReadAhead: public Read,
91 public WithParamInterface<tuple<bool, int>>
92{
93 virtual void SetUp() {
94 int val;
95 const char *node = "vfs.maxbcachebuf";
96 size_t size = sizeof(val);
97 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
98 << strerror(errno);
99
100 m_maxreadahead = val * get<1>(GetParam());
101 m_noclusterr = get<0>(GetParam());
102 Read::SetUp();
103 }
104};
105
106 /* AIO reads need to set the header's pid field correctly */
107 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
108 TEST_F(AioRead, aio_read)
109{
110 const char FULLPATH[] = "mountpoint/some_file.txt";
111 const char RELPATH[] = "some_file.txt";
112 const char *CONTENTS = "abcdefgh";
113 uint64_t ino = 42;
114 int fd;
115 ssize_t bufsize = strlen(CONTENTS);
116 uint8_t buf[bufsize];
117 struct aiocb iocb, *piocb;
118
119 expect_lookup(RELPATH, ino, bufsize);
120 expect_open(ino, 0, 1);
121 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
122
123 fd = open(FULLPATH, O_RDONLY);
124 ASSERT_LE(0, fd) << strerror(errno);
125
126 iocb.aio_nbytes = bufsize;
127 iocb.aio_fildes = fd;
128 iocb.aio_buf = buf;
129 iocb.aio_offset = 0;
130 iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
131 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
132 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
133 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
134
135 leak(fd);
136}
137
138 /*
139 * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there
140 * is at most one outstanding read operation per file handle
141 */
142 TEST_F(AioRead, async_read_disabled)
143{
144 const char FULLPATH[] = "mountpoint/some_file.txt";
145 const char RELPATH[] = "some_file.txt";
146 uint64_t ino = 42;
147 int fd;
148 ssize_t bufsize = 50;
149 char buf0[bufsize], buf1[bufsize];
150 off_t off0 = 0;
151 off_t off1 = m_maxbcachebuf;
152 struct aiocb iocb0, iocb1;
153 volatile sig_atomic_t read_count = 0;
154
155 expect_lookup(RELPATH, ino, 131072);
156 expect_open(ino, 0, 1);
157 EXPECT_CALL(*m_mock, process(
158 ResultOf([=](auto in) {
159 return (in.header.opcode == FUSE_READ &&
160 in.header.nodeid == ino &&
161 in.body.read.fh == FH &&
162 in.body.read.offset == (uint64_t)off0);
163 }, Eq(true)),
164 _)
165 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
166 read_count++;
167 /* Filesystem is slow to respond */
168 }));
169 EXPECT_CALL(*m_mock, process(
170 ResultOf([=](auto in) {
171 return (in.header.opcode == FUSE_READ &&
172 in.header.nodeid == ino &&
173 in.body.read.fh == FH &&
174 in.body.read.offset == (uint64_t)off1);
175 }, Eq(true)),
176 _)
177 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
178 read_count++;
179 /* Filesystem is slow to respond */
180 }));
181
182 fd = open(FULLPATH, O_RDONLY);
183 ASSERT_LE(0, fd) << strerror(errno);
184
185 /*
186 * Submit two AIO read requests, and respond to neither. If the
187 * filesystem ever gets the second read request, then we failed to
188 * limit outstanding reads.
189 */
190 iocb0.aio_nbytes = bufsize;
191 iocb0.aio_fildes = fd;
192 iocb0.aio_buf = buf0;
193 iocb0.aio_offset = off0;
194 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
195 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
196
197 iocb1.aio_nbytes = bufsize;
198 iocb1.aio_fildes = fd;
199 iocb1.aio_buf = buf1;
200 iocb1.aio_offset = off1;
201 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
202 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
203
204 /*
205 * Sleep for awhile to make sure the kernel has had a chance to issue
206 * the second read, even though the first has not yet returned
207 */
208 nap();
209 EXPECT_EQ(read_count, 1);
210
211 m_mock->kill_daemon();
212 /* Wait for AIO activity to complete, but ignore errors */
213 (void)aio_waitcomplete(NULL, NULL);
214
215 leak(fd);
216}
217
218 /*
219 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple
220 * simultaneous read requests on the same file handle.
221 */
222 TEST_F(AsyncRead, async_read)
223{
224 const char FULLPATH[] = "mountpoint/some_file.txt";
225 const char RELPATH[] = "some_file.txt";
226 uint64_t ino = 42;
227 int fd;
228 ssize_t bufsize = 50;
229 char buf0[bufsize], buf1[bufsize];
230 off_t off0 = 0;
231 off_t off1 = m_maxbcachebuf;
232 off_t fsize = 2 * m_maxbcachebuf;
233 struct aiocb iocb0, iocb1;
234 sem_t sem;
235
236 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
237
238 expect_lookup(RELPATH, ino, fsize);
239 expect_open(ino, 0, 1);
240 EXPECT_CALL(*m_mock, process(
241 ResultOf([=](auto in) {
242 return (in.header.opcode == FUSE_READ &&
243 in.header.nodeid == ino &&
244 in.body.read.fh == FH &&
245 in.body.read.offset == (uint64_t)off0);
246 }, Eq(true)),
247 _)
248 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
249 sem_post(&sem);
250 /* Filesystem is slow to respond */
251 }));
252 EXPECT_CALL(*m_mock, process(
253 ResultOf([=](auto in) {
254 return (in.header.opcode == FUSE_READ &&
255 in.header.nodeid == ino &&
256 in.body.read.fh == FH &&
257 in.body.read.offset == (uint64_t)off1);
258 }, Eq(true)),
259 _)
260 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
261 sem_post(&sem);
262 /* Filesystem is slow to respond */
263 }));
264
265 fd = open(FULLPATH, O_RDONLY);
266 ASSERT_LE(0, fd) << strerror(errno);
267
268 /*
269 * Submit two AIO read requests, but respond to neither. Ensure that
270 * we received both.
271 */
272 iocb0.aio_nbytes = bufsize;
273 iocb0.aio_fildes = fd;
274 iocb0.aio_buf = buf0;
275 iocb0.aio_offset = off0;
276 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
277 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
278
279 iocb1.aio_nbytes = bufsize;
280 iocb1.aio_fildes = fd;
281 iocb1.aio_buf = buf1;
282 iocb1.aio_offset = off1;
283 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
284 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
285
286 /* Wait until both reads have reached the daemon */
287 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
288 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
289
290 m_mock->kill_daemon();
291 /* Wait for AIO activity to complete, but ignore errors */
292 (void)aio_waitcomplete(NULL, NULL);
293
294 leak(fd);
295}
296
297 /* 0-length reads shouldn't cause any confusion */
298 TEST_F(Read, direct_io_read_nothing)
299{
300 const char FULLPATH[] = "mountpoint/some_file.txt";
301 const char RELPATH[] = "some_file.txt";
302 uint64_t ino = 42;
303 int fd;
304 uint64_t offset = 100;
305 char buf[80];
306
307 expect_lookup(RELPATH, ino, offset + 1000);
308 expect_open(ino, FOPEN_DIRECT_IO, 1);
309
310 fd = open(FULLPATH, O_RDONLY);
311 ASSERT_LE(0, fd) << strerror(errno);
312
313 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
314 leak(fd);
315}
316
317 /*
318 * With direct_io, reads should not fill the cache. They should go straight to
319 * the daemon
320 */
321 TEST_F(Read, direct_io_pread)
322{
323 const char FULLPATH[] = "mountpoint/some_file.txt";
324 const char RELPATH[] = "some_file.txt";
325 const char *CONTENTS = "abcdefgh";
326 uint64_t ino = 42;
327 int fd;
328 uint64_t offset = 100;
329 ssize_t bufsize = strlen(CONTENTS);
330 uint8_t buf[bufsize];
331
332 expect_lookup(RELPATH, ino, offset + bufsize);
333 expect_open(ino, FOPEN_DIRECT_IO, 1);
334 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
335
336 fd = open(FULLPATH, O_RDONLY);
337 ASSERT_LE(0, fd) << strerror(errno);
338
339 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
340 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
341
342 // With FOPEN_DIRECT_IO, the cache should be bypassed. The server will
343 // get a 2nd read request.
344 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
345 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
346 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
347 leak(fd);
348}
349
350 /*
351 * With direct_io, filesystems are allowed to return less data than is
352 * requested. fuse(4) should return a short read to userland.
353 */
354 TEST_F(Read, direct_io_short_read)
355{
356 const char FULLPATH[] = "mountpoint/some_file.txt";
357 const char RELPATH[] = "some_file.txt";
358 const char *CONTENTS = "abcdefghijklmnop";
359 uint64_t ino = 42;
360 int fd;
361 uint64_t offset = 100;
362 ssize_t bufsize = strlen(CONTENTS);
363 ssize_t halfbufsize = bufsize / 2;
364 uint8_t buf[bufsize];
365
366 expect_lookup(RELPATH, ino, offset + bufsize);
367 expect_open(ino, FOPEN_DIRECT_IO, 1);
368 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
369
370 fd = open(FULLPATH, O_RDONLY);
371 ASSERT_LE(0, fd) << strerror(errno);
372
373 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
374 << strerror(errno);
375 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
376 leak(fd);
377}
378
379 TEST_F(Read, eio)
380{
381 const char FULLPATH[] = "mountpoint/some_file.txt";
382 const char RELPATH[] = "some_file.txt";
383 const char *CONTENTS = "abcdefgh";
384 uint64_t ino = 42;
385 int fd;
386 ssize_t bufsize = strlen(CONTENTS);
387 uint8_t buf[bufsize];
388
389 expect_lookup(RELPATH, ino, bufsize);
390 expect_open(ino, 0, 1);
391 EXPECT_CALL(*m_mock, process(
392 ResultOf([=](auto in) {
393 return (in.header.opcode == FUSE_READ);
394 }, Eq(true)),
395 _)
396 ).WillOnce(Invoke(ReturnErrno(EIO)));
397
398 fd = open(FULLPATH, O_RDONLY);
399 ASSERT_LE(0, fd) << strerror(errno);
400
401 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
402 ASSERT_EQ(EIO, errno);
403 leak(fd);
404}
405
406 /*
407 * If the server returns a short read when direct io is not in use, that
408 * indicates EOF, because of a server-side truncation. We should invalidate
409 * all cached attributes. We may update the file size,
410 */
411 TEST_F(Read, eof)
412{
413 const char FULLPATH[] = "mountpoint/some_file.txt";
414 const char RELPATH[] = "some_file.txt";
415 const char *CONTENTS = "abcdefghijklmnop";
416 uint64_t ino = 42;
417 int fd;
418 uint64_t offset = 100;
419 ssize_t bufsize = strlen(CONTENTS);
420 ssize_t partbufsize = 3 * bufsize / 4;
421 ssize_t r;
422 uint8_t buf[bufsize];
423 struct stat sb;
424
425 expect_lookup(RELPATH, ino, offset + bufsize);
426 expect_open(ino, 0, 1);
427 expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS);
428 expect_getattr(ino, offset + partbufsize);
429
430 fd = open(FULLPATH, O_RDONLY);
431 ASSERT_LE(0, fd) << strerror(errno);
432
433 r = pread(fd, buf, bufsize, offset);
434 ASSERT_LE(0, r) << strerror(errno);
435 EXPECT_EQ(partbufsize, r) << strerror(errno);
436 ASSERT_EQ(0, fstat(fd, &sb));
437 EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size);
438 leak(fd);
439}
440
441 /* Like Read.eof, but causes an entire buffer to be invalidated */
442 TEST_F(Read, eof_of_whole_buffer)
443{
444 const char FULLPATH[] = "mountpoint/some_file.txt";
445 const char RELPATH[] = "some_file.txt";
446 const char *CONTENTS = "abcdefghijklmnop";
447 uint64_t ino = 42;
448 int fd;
449 ssize_t bufsize = strlen(CONTENTS);
450 off_t old_filesize = m_maxbcachebuf * 2 + bufsize;
451 uint8_t buf[bufsize];
452 struct stat sb;
453
454 expect_lookup(RELPATH, ino, old_filesize);
455 expect_open(ino, 0, 1);
456 expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS);
457 expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS);
458 expect_getattr(ino, m_maxbcachebuf);
459
460 fd = open(FULLPATH, O_RDONLY);
461 ASSERT_LE(0, fd) << strerror(errno);
462
463 /* Cache the third block */
464 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2))
465 << strerror(errno);
466 /* Try to read the 2nd block, but it's past EOF */
467 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf))
468 << strerror(errno);
469 ASSERT_EQ(0, fstat(fd, &sb));
470 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size);
471 leak(fd);
472}
473
474 /*
475 * With the keep_cache option, the kernel may keep its read cache across
476 * multiple open(2)s.
477 */
478 TEST_F(Read, keep_cache)
479{
480 const char FULLPATH[] = "mountpoint/some_file.txt";
481 const char RELPATH[] = "some_file.txt";
482 const char *CONTENTS = "abcdefgh";
483 uint64_t ino = 42;
484 int fd0, fd1;
485 ssize_t bufsize = strlen(CONTENTS);
486 uint8_t buf[bufsize];
487
488 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
489 expect_open(ino, FOPEN_KEEP_CACHE, 2);
490 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
491
492 fd0 = open(FULLPATH, O_RDONLY);
493 ASSERT_LE(0, fd0) << strerror(errno);
494 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
495
496 fd1 = open(FULLPATH, O_RDWR);
497 ASSERT_LE(0, fd1) << strerror(errno);
498
499 /*
500 * This read should be serviced by cache, even though it's on the other
501 * file descriptor
502 */
503 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
504
505 leak(fd0);
506 leak(fd1);
507}
508
509 /*
510 * Without the keep_cache option, the kernel should drop its read caches on
511 * every open
512 */
513 TEST_F(Read, keep_cache_disabled)
514{
515 const char FULLPATH[] = "mountpoint/some_file.txt";
516 const char RELPATH[] = "some_file.txt";
517 const char *CONTENTS = "abcdefgh";
518 uint64_t ino = 42;
519 int fd0, fd1;
520 ssize_t bufsize = strlen(CONTENTS);
521 uint8_t buf[bufsize];
522
523 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
524 expect_open(ino, 0, 2);
525 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
526
527 fd0 = open(FULLPATH, O_RDONLY);
528 ASSERT_LE(0, fd0) << strerror(errno);
529 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
530
531 fd1 = open(FULLPATH, O_RDWR);
532 ASSERT_LE(0, fd1) << strerror(errno);
533
534 /*
535 * This read should not be serviced by cache, even though it's on the
536 * original file descriptor
537 */
538 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
539 ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno);
540 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
541
542 leak(fd0);
543 leak(fd1);
544}
545
546 TEST_F(Read, mmap)
547{
548 const char FULLPATH[] = "mountpoint/some_file.txt";
549 const char RELPATH[] = "some_file.txt";
550 const char *CONTENTS = "abcdefgh";
551 uint64_t ino = 42;
552 int fd;
553 ssize_t len;
554 size_t bufsize = strlen(CONTENTS);
555 void *p;
556
557 len = getpagesize();
558
559 expect_lookup(RELPATH, ino, bufsize);
560 expect_open(ino, 0, 1);
561 EXPECT_CALL(*m_mock, process(
562 ResultOf([=](auto in) {
563 return (in.header.opcode == FUSE_READ &&
564 in.header.nodeid == ino &&
565 in.body.read.fh == Read::FH &&
566 in.body.read.offset == 0 &&
567 in.body.read.size == bufsize);
568 }, Eq(true)),
569 _)
570 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
571 out.header.len = sizeof(struct fuse_out_header) + bufsize;
572 memmove(out.body.bytes, CONTENTS, bufsize);
573 })));
574
575 fd = open(FULLPATH, O_RDONLY);
576 ASSERT_LE(0, fd) << strerror(errno);
577
578 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
579 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
580
581 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
582
583 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
584 leak(fd);
585}
586
587 /*
588 * A read via mmap comes up short, indicating that the file was truncated
589 * server-side.
590 */
591 TEST_F(Read, mmap_eof)
592{
593 const char FULLPATH[] = "mountpoint/some_file.txt";
594 const char RELPATH[] = "some_file.txt";
595 const char *CONTENTS = "abcdefgh";
596 uint64_t ino = 42;
597 int fd;
598 ssize_t len;
599 size_t bufsize = strlen(CONTENTS);
600 struct stat sb;
601 void *p;
602
603 len = getpagesize();
604
605 expect_lookup(RELPATH, ino, m_maxbcachebuf);
606 expect_open(ino, 0, 1);
607 EXPECT_CALL(*m_mock, process(
608 ResultOf([=](auto in) {
609 return (in.header.opcode == FUSE_READ &&
610 in.header.nodeid == ino &&
611 in.body.read.fh == Read::FH &&
612 in.body.read.offset == 0 &&
613 in.body.read.size == (uint32_t)m_maxbcachebuf);
614 }, Eq(true)),
615 _)
616 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
617 out.header.len = sizeof(struct fuse_out_header) + bufsize;
618 memmove(out.body.bytes, CONTENTS, bufsize);
619 })));
620 expect_getattr(ino, bufsize);
621
622 fd = open(FULLPATH, O_RDONLY);
623 ASSERT_LE(0, fd) << strerror(errno);
624
625 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
626 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
627
628 /* The file size should be automatically truncated */
629 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
630 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
631 EXPECT_EQ((off_t)bufsize, sb.st_size);
632
633 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
634 leak(fd);
635}
636
637 /*
638 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
639 * cache and to straight to the daemon
640 */
641 TEST_F(Read, o_direct)
642{
643 const char FULLPATH[] = "mountpoint/some_file.txt";
644 const char RELPATH[] = "some_file.txt";
645 const char *CONTENTS = "abcdefgh";
646 uint64_t ino = 42;
647 int fd;
648 ssize_t bufsize = strlen(CONTENTS);
649 uint8_t buf[bufsize];
650
651 expect_lookup(RELPATH, ino, bufsize);
652 expect_open(ino, 0, 1);
653 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
654
655 fd = open(FULLPATH, O_RDONLY);
656 ASSERT_LE(0, fd) << strerror(errno);
657
658 // Fill the cache
659 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
660 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
661
662 // Reads with o_direct should bypass the cache
663 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
664 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
665 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
666 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
667 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
668
669 leak(fd);
670}
671
672 TEST_F(Read, pread)
673{
674 const char FULLPATH[] = "mountpoint/some_file.txt";
675 const char RELPATH[] = "some_file.txt";
676 const char *CONTENTS = "abcdefgh";
677 uint64_t ino = 42;
678 int fd;
679 /*
680 * Set offset to a maxbcachebuf boundary so we'll be sure what offset
681 * to read from. Without this, the read might start at a lower offset.
682 */
683 uint64_t offset = m_maxbcachebuf;
684 ssize_t bufsize = strlen(CONTENTS);
685 uint8_t buf[bufsize];
686
687 expect_lookup(RELPATH, ino, offset + bufsize);
688 expect_open(ino, 0, 1);
689 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
690
691 fd = open(FULLPATH, O_RDONLY);
692 ASSERT_LE(0, fd) << strerror(errno);
693
694 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
695 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
696 leak(fd);
697}
698
699 TEST_F(Read, read)
700{
701 const char FULLPATH[] = "mountpoint/some_file.txt";
702 const char RELPATH[] = "some_file.txt";
703 const char *CONTENTS = "abcdefgh";
704 uint64_t ino = 42;
705 int fd;
706 ssize_t bufsize = strlen(CONTENTS);
707 uint8_t buf[bufsize];
708
709 expect_lookup(RELPATH, ino, bufsize);
710 expect_open(ino, 0, 1);
711 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
712
713 fd = open(FULLPATH, O_RDONLY);
714 ASSERT_LE(0, fd) << strerror(errno);
715
716 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
717 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
718
719 leak(fd);
720}
721
722 TEST_F(Read_7_8, read)
723{
724 const char FULLPATH[] = "mountpoint/some_file.txt";
725 const char RELPATH[] = "some_file.txt";
726 const char *CONTENTS = "abcdefgh";
727 uint64_t ino = 42;
728 int fd;
729 ssize_t bufsize = strlen(CONTENTS);
730 uint8_t buf[bufsize];
731
732 expect_lookup(RELPATH, ino, bufsize);
733 expect_open(ino, 0, 1);
734 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
735
736 fd = open(FULLPATH, O_RDONLY);
737 ASSERT_LE(0, fd) << strerror(errno);
738
739 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
740 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
741
742 leak(fd);
743}
744
745 /*
746 * If cacheing is enabled, the kernel should try to read an entire cache block
747 * at a time.
748 */
749 TEST_F(Read, cache_block)
750{
751 const char FULLPATH[] = "mountpoint/some_file.txt";
752 const char RELPATH[] = "some_file.txt";
753 const char *CONTENTS0 = "abcdefghijklmnop";
754 uint64_t ino = 42;
755 int fd;
756 ssize_t bufsize = 8;
757 ssize_t filesize = m_maxbcachebuf * 2;
758 char *contents;
759 char buf[bufsize];
760 const char *contents1 = CONTENTS0 + bufsize;
761
762 contents = (char*)calloc(1, filesize);
763 ASSERT_NE(nullptr, contents);
764 memmove(contents, CONTENTS0, strlen(CONTENTS0));
765
766 expect_lookup(RELPATH, ino, filesize);
767 expect_open(ino, 0, 1);
768 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf,
769 contents);
770
771 fd = open(FULLPATH, O_RDONLY);
772 ASSERT_LE(0, fd) << strerror(errno);
773
774 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
775 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
776
777 /* A subsequent read should be serviced by cache */
778 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
779 ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
780 leak(fd);
781 free(contents);
782}
783
784 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
785 TEST_F(Read, sendfile)
786{
787 const char FULLPATH[] = "mountpoint/some_file.txt";
788 const char RELPATH[] = "some_file.txt";
789 const char *CONTENTS = "abcdefgh";
790 uint64_t ino = 42;
791 int fd;
792 size_t bufsize = strlen(CONTENTS);
793 uint8_t buf[bufsize];
794 int sp[2];
795 off_t sbytes;
796
797 expect_lookup(RELPATH, ino, bufsize);
798 expect_open(ino, 0, 1);
799 EXPECT_CALL(*m_mock, process(
800 ResultOf([=](auto in) {
801 return (in.header.opcode == FUSE_READ &&
802 in.header.nodeid == ino &&
803 in.body.read.fh == Read::FH &&
804 in.body.read.offset == 0 &&
805 in.body.read.size == bufsize);
806 }, Eq(true)),
807 _)
808 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
809 out.header.len = sizeof(struct fuse_out_header) + bufsize;
810 memmove(out.body.bytes, CONTENTS, bufsize);
811 })));
812
813 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
814 << strerror(errno);
815 fd = open(FULLPATH, O_RDONLY);
816 ASSERT_LE(0, fd) << strerror(errno);
817
818 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
819 << strerror(errno);
820 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
821 << strerror(errno);
822 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
823
824 close(sp[1]);
825 close(sp[0]);
826 leak(fd);
827}
828
829 /* sendfile should fail gracefully if fuse declines the read */
830 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
831 TEST_F(Read, sendfile_eio)
832{
833 const char FULLPATH[] = "mountpoint/some_file.txt";
834 const char RELPATH[] = "some_file.txt";
835 const char *CONTENTS = "abcdefgh";
836 uint64_t ino = 42;
837 int fd;
838 ssize_t bufsize = strlen(CONTENTS);
839 int sp[2];
840 off_t sbytes;
841
842 expect_lookup(RELPATH, ino, bufsize);
843 expect_open(ino, 0, 1);
844 EXPECT_CALL(*m_mock, process(
845 ResultOf([=](auto in) {
846 return (in.header.opcode == FUSE_READ);
847 }, Eq(true)),
848 _)
849 ).WillOnce(Invoke(ReturnErrno(EIO)));
850
851 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
852 << strerror(errno);
853 fd = open(FULLPATH, O_RDONLY);
854 ASSERT_LE(0, fd) << strerror(errno);
855
856 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
857
858 close(sp[1]);
859 close(sp[0]);
860 leak(fd);
861}
862
863 /*
864 * Sequential reads should use readahead. And if allowed, large reads should
865 * be clustered.
866 */
867 TEST_P(ReadAhead, readahead) {
868 const char FULLPATH[] = "mountpoint/some_file.txt";
869 const char RELPATH[] = "some_file.txt";
870 uint64_t ino = 42;
871 int fd, maxcontig, clustersize;
872 ssize_t bufsize = 4 * m_maxbcachebuf;
873 ssize_t filesize = bufsize;
874 uint64_t len;
875 char *rbuf, *contents;
876 off_t offs;
877
878 contents = (char*)malloc(filesize);
879 ASSERT_NE(nullptr, contents);
880 memset(contents, 'X', filesize);
881 rbuf = (char*)calloc(1, bufsize);
882
883 expect_lookup(RELPATH, ino, filesize);
884 expect_open(ino, 0, 1);
885 maxcontig = m_noclusterr ? m_maxbcachebuf :
886 m_maxbcachebuf + m_maxreadahead;
887 clustersize = MIN(maxcontig, m_maxphys);
888 for (offs = 0; offs < bufsize; offs += clustersize) {
889 len = std::min((size_t)clustersize, (size_t)(filesize - offs));
890 expect_read(ino, offs, len, len, contents + offs);
891 }
892
893 fd = open(FULLPATH, O_RDONLY);
894 ASSERT_LE(0, fd) << strerror(errno);
895
896 /* Set the internal readahead counter to a "large" value */
897 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
898
899 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
900 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
901
902 leak(fd);
903 free(rbuf);
904 free(contents);
905}
906
907INSTANTIATE_TEST_CASE_P(RA, ReadAhead,
908 Values(tuple<bool, int>(false, 0),
909 tuple<bool, int>(false, 1),
910 tuple<bool, int>(false, 2),
911 tuple<bool, int>(false, 3),
912 tuple<bool, int>(true, 0),
913 tuple<bool, int>(true, 1),
914 tuple<bool, int>(true, 2)));
915 

AltStyle によって変換されたページ (->オリジナル) /