1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
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/param.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 <semaphore.h>
41 #include <setjmp.h>
42 #include <signal.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:
expect_lookup(const char * relpath,uint64_t ino,uint64_t size)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 RofsRead: public Read {
61 public:
SetUp()62 virtual void SetUp() {
63 m_ro = true;
64 Read::SetUp();
65 }
66 };
67
68 class Read_7_8: public FuseTest {
69 public:
SetUp()70 virtual void SetUp() {
71 m_kernel_minor_version = 8;
72 FuseTest::SetUp();
73 }
74
expect_lookup(const char * relpath,uint64_t ino,uint64_t size)75 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
76 {
77 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
78 }
79 };
80
81 class AioRead: public Read {
82 public:
SetUp()83 virtual void SetUp() {
84 if (!is_unsafe_aio_enabled())
85 GTEST_SKIP() <<
86 "vfs.aio.enable_unsafe must be set for this test";
87 FuseTest::SetUp();
88 }
89 };
90
91 class AsyncRead: public AioRead {
SetUp()92 virtual void SetUp() {
93 m_init_flags = FUSE_ASYNC_READ;
94 AioRead::SetUp();
95 }
96 };
97
98 class ReadAhead: public Read,
99 public WithParamInterface<tuple<bool, int>>
100 {
SetUp()101 virtual void SetUp() {
102 int val;
103 const char *node = "vfs.maxbcachebuf";
104 size_t size = sizeof(val);
105 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
106 << strerror(errno);
107
108 m_maxreadahead = val * get<1>(GetParam());
109 m_noclusterr = get<0>(GetParam());
110 Read::SetUp();
111 }
112 };
113
114 class ReadNoatime: public Read {
SetUp()115 virtual void SetUp() {
116 m_noatime = true;
117 Read::SetUp();
118 }
119 };
120
121 class ReadSigbus: public Read
122 {
123 public:
124 static jmp_buf s_jmpbuf;
125 static void *s_si_addr;
126
TearDown()127 void TearDown() {
128 struct sigaction sa;
129
130 bzero(&sa, sizeof(sa));
131 sa.sa_handler = SIG_DFL;
132 sigaction(SIGBUS, &sa, NULL);
133
134 FuseTest::TearDown();
135 }
136
137 };
138
139 static void
handle_sigbus(int signo __unused,siginfo_t * info,void * uap __unused)140 handle_sigbus(int signo __unused, siginfo_t *info, void *uap __unused) {
141 ReadSigbus::s_si_addr = info->si_addr;
142 longjmp(ReadSigbus::s_jmpbuf, 1);
143 }
144
145 jmp_buf ReadSigbus::s_jmpbuf;
146 void *ReadSigbus::s_si_addr;
147
148 class TimeGran: public Read, public WithParamInterface<unsigned> {
149 public:
SetUp()150 virtual void SetUp() {
151 m_time_gran = 1 << GetParam();
152 Read::SetUp();
153 }
154 };
155
156 /* AIO reads need to set the header's pid field correctly */
157 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
TEST_F(AioRead,aio_read)158 TEST_F(AioRead, aio_read)
159 {
160 const char FULLPATH[] = "mountpoint/some_file.txt";
161 const char RELPATH[] = "some_file.txt";
162 const char *CONTENTS = "abcdefgh";
163 uint64_t ino = 42;
164 int fd;
165 ssize_t bufsize = strlen(CONTENTS);
166 uint8_t buf[bufsize];
167 struct aiocb iocb, *piocb;
168
169 expect_lookup(RELPATH, ino, bufsize);
170 expect_open(ino, 0, 1);
171 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
172
173 fd = open(FULLPATH, O_RDONLY);
174 ASSERT_LE(0, fd) << strerror(errno);
175
176 iocb.aio_nbytes = bufsize;
177 iocb.aio_fildes = fd;
178 iocb.aio_buf = buf;
179 iocb.aio_offset = 0;
180 iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
181 ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno);
182 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
183 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
184
185 leak(fd);
186 }
187
188 /*
189 * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there
190 * is at most one outstanding read operation per file handle
191 */
TEST_F(AioRead,async_read_disabled)192 TEST_F(AioRead, async_read_disabled)
193 {
194 const char FULLPATH[] = "mountpoint/some_file.txt";
195 const char RELPATH[] = "some_file.txt";
196 uint64_t ino = 42;
197 int fd;
198 ssize_t bufsize = 50;
199 char buf0[bufsize], buf1[bufsize];
200 off_t off0 = 0;
201 off_t off1 = m_maxbcachebuf;
202 struct aiocb iocb0, iocb1;
203 volatile sig_atomic_t read_count = 0;
204
205 expect_lookup(RELPATH, ino, 131072);
206 expect_open(ino, 0, 1);
207 EXPECT_CALL(*m_mock, process(
208 ResultOf([=](auto in) {
209 return (in.header.opcode == FUSE_READ &&
210 in.header.nodeid == ino &&
211 in.body.read.fh == FH &&
212 in.body.read.offset == (uint64_t)off0);
213 }, Eq(true)),
214 _)
215 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
216 read_count++;
217 /* Filesystem is slow to respond */
218 }));
219 EXPECT_CALL(*m_mock, process(
220 ResultOf([=](auto in) {
221 return (in.header.opcode == FUSE_READ &&
222 in.header.nodeid == ino &&
223 in.body.read.fh == FH &&
224 in.body.read.offset == (uint64_t)off1);
225 }, Eq(true)),
226 _)
227 ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) {
228 read_count++;
229 /* Filesystem is slow to respond */
230 }));
231
232 fd = open(FULLPATH, O_RDONLY);
233 ASSERT_LE(0, fd) << strerror(errno);
234
235 /*
236 * Submit two AIO read requests, and respond to neither. If the
237 * filesystem ever gets the second read request, then we failed to
238 * limit outstanding reads.
239 */
240 iocb0.aio_nbytes = bufsize;
241 iocb0.aio_fildes = fd;
242 iocb0.aio_buf = buf0;
243 iocb0.aio_offset = off0;
244 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
245 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
246
247 iocb1.aio_nbytes = bufsize;
248 iocb1.aio_fildes = fd;
249 iocb1.aio_buf = buf1;
250 iocb1.aio_offset = off1;
251 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
252 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
253
254 /*
255 * Sleep for awhile to make sure the kernel has had a chance to issue
256 * the second read, even though the first has not yet returned
257 */
258 nap();
259 EXPECT_EQ(read_count, 1);
260
261 m_mock->kill_daemon();
262 /* Wait for AIO activity to complete, but ignore errors */
263 (void)aio_waitcomplete(NULL, NULL);
264
265 leak(fd);
266 }
267
268 /*
269 * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple
270 * simultaneous read requests on the same file handle.
271 */
TEST_F(AsyncRead,async_read)272 TEST_F(AsyncRead, async_read)
273 {
274 const char FULLPATH[] = "mountpoint/some_file.txt";
275 const char RELPATH[] = "some_file.txt";
276 uint64_t ino = 42;
277 int fd;
278 ssize_t bufsize = 50;
279 char buf0[bufsize], buf1[bufsize];
280 off_t off0 = 0;
281 off_t off1 = m_maxbcachebuf;
282 off_t fsize = 2 * m_maxbcachebuf;
283 struct aiocb iocb0, iocb1;
284 sem_t sem;
285
286 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
287
288 expect_lookup(RELPATH, ino, fsize);
289 expect_open(ino, 0, 1);
290 EXPECT_CALL(*m_mock, process(
291 ResultOf([=](auto in) {
292 return (in.header.opcode == FUSE_READ &&
293 in.header.nodeid == ino &&
294 in.body.read.fh == FH &&
295 in.body.read.offset == (uint64_t)off0);
296 }, Eq(true)),
297 _)
298 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
299 sem_post(&sem);
300 /* Filesystem is slow to respond */
301 }));
302 EXPECT_CALL(*m_mock, process(
303 ResultOf([=](auto in) {
304 return (in.header.opcode == FUSE_READ &&
305 in.header.nodeid == ino &&
306 in.body.read.fh == FH &&
307 in.body.read.offset == (uint64_t)off1);
308 }, Eq(true)),
309 _)
310 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
311 sem_post(&sem);
312 /* Filesystem is slow to respond */
313 }));
314
315 fd = open(FULLPATH, O_RDONLY);
316 ASSERT_LE(0, fd) << strerror(errno);
317
318 /*
319 * Submit two AIO read requests, but respond to neither. Ensure that
320 * we received both.
321 */
322 iocb0.aio_nbytes = bufsize;
323 iocb0.aio_fildes = fd;
324 iocb0.aio_buf = buf0;
325 iocb0.aio_offset = off0;
326 iocb0.aio_sigevent.sigev_notify = SIGEV_NONE;
327 ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno);
328
329 iocb1.aio_nbytes = bufsize;
330 iocb1.aio_fildes = fd;
331 iocb1.aio_buf = buf1;
332 iocb1.aio_offset = off1;
333 iocb1.aio_sigevent.sigev_notify = SIGEV_NONE;
334 ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno);
335
336 /* Wait until both reads have reached the daemon */
337 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
338 ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno);
339
340 m_mock->kill_daemon();
341 /* Wait for AIO activity to complete, but ignore errors */
342 (void)aio_waitcomplete(NULL, NULL);
343
344 leak(fd);
345 }
346
347 /* The kernel should update the cached atime attribute during a read */
TEST_F(Read,atime)348 TEST_F(Read, atime)
349 {
350 const char FULLPATH[] = "mountpoint/some_file.txt";
351 const char RELPATH[] = "some_file.txt";
352 const char *CONTENTS = "abcdefgh";
353 struct stat sb1, sb2;
354 uint64_t ino = 42;
355 int fd;
356 ssize_t bufsize = strlen(CONTENTS);
357 uint8_t buf[bufsize];
358
359 expect_lookup(RELPATH, ino, bufsize);
360 expect_open(ino, 0, 1);
361 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
362
363 fd = open(FULLPATH, O_RDONLY);
364 ASSERT_LE(0, fd) << strerror(errno);
365 ASSERT_EQ(0, fstat(fd, &sb1));
366
367 /* Ensure atime will be different than it was during lookup */
368 nap();
369
370 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
371 ASSERT_EQ(0, fstat(fd, &sb2));
372
373 /* The kernel should automatically update atime during read */
374 EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, <));
375 EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
376 EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
377
378 leak(fd);
379 }
380
381 /* The kernel should update the cached atime attribute during a cached read */
TEST_F(Read,atime_cached)382 TEST_F(Read, atime_cached)
383 {
384 const char FULLPATH[] = "mountpoint/some_file.txt";
385 const char RELPATH[] = "some_file.txt";
386 const char *CONTENTS = "abcdefgh";
387 struct stat sb1, sb2;
388 uint64_t ino = 42;
389 int fd;
390 ssize_t bufsize = strlen(CONTENTS);
391 uint8_t buf[bufsize];
392
393 expect_lookup(RELPATH, ino, bufsize);
394 expect_open(ino, 0, 1);
395 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
396
397 fd = open(FULLPATH, O_RDONLY);
398 ASSERT_LE(0, fd) << strerror(errno);
399
400 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
401 ASSERT_EQ(0, fstat(fd, &sb1));
402
403 /* Ensure atime will be different than it was during the first read */
404 nap();
405
406 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
407 ASSERT_EQ(0, fstat(fd, &sb2));
408
409 /* The kernel should automatically update atime during read */
410 EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, <));
411 EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
412 EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
413
414 leak(fd);
415 }
416
417 /* dirty atime values should be flushed during close */
TEST_F(Read,atime_during_close)418 TEST_F(Read, atime_during_close)
419 {
420 const char FULLPATH[] = "mountpoint/some_file.txt";
421 const char RELPATH[] = "some_file.txt";
422 const char *CONTENTS = "abcdefgh";
423 struct stat sb;
424 uint64_t ino = 42;
425 const mode_t newmode = 0755;
426 int fd;
427 ssize_t bufsize = strlen(CONTENTS);
428 uint8_t buf[bufsize];
429
430 expect_lookup(RELPATH, ino, bufsize);
431 expect_open(ino, 0, 1);
432 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
433 EXPECT_CALL(*m_mock, process(
434 ResultOf([&](auto in) {
435 uint32_t valid = FATTR_ATIME;
436 return (in.header.opcode == FUSE_SETATTR &&
437 in.header.nodeid == ino &&
438 in.body.setattr.valid == valid &&
439 (time_t)in.body.setattr.atime ==
440 sb.st_atim.tv_sec &&
441 (long)in.body.setattr.atimensec ==
442 sb.st_atim.tv_nsec);
443 }, Eq(true)),
444 _)
445 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
446 SET_OUT_HEADER_LEN(out, attr);
447 out.body.attr.attr.ino = ino;
448 out.body.attr.attr.mode = S_IFREG | newmode;
449 })));
450 expect_flush(ino, 1, ReturnErrno(0));
451 expect_release(ino, FuseTest::FH);
452
453 fd = open(FULLPATH, O_RDONLY);
454 ASSERT_LE(0, fd) << strerror(errno);
455
456 /* Ensure atime will be different than during lookup */
457 nap();
458
459 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
460 ASSERT_EQ(0, fstat(fd, &sb));
461
462 close(fd);
463 }
464
465 /*
466 * When not using -o default_permissions, the daemon may make its own decisions
467 * regarding access permissions, and these may be unpredictable. If it rejects
468 * our attempt to set atime, that should not cause close(2) to fail.
469 */
TEST_F(Read,atime_during_close_eacces)470 TEST_F(Read, atime_during_close_eacces)
471 {
472 const char FULLPATH[] = "mountpoint/some_file.txt";
473 const char RELPATH[] = "some_file.txt";
474 const char *CONTENTS = "abcdefgh";
475 uint64_t ino = 42;
476 int fd;
477 ssize_t bufsize = strlen(CONTENTS);
478 uint8_t buf[bufsize];
479
480 expect_lookup(RELPATH, ino, bufsize);
481 expect_open(ino, 0, 1);
482 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
483 EXPECT_CALL(*m_mock, process(
484 ResultOf([&](auto in) {
485 uint32_t valid = FATTR_ATIME;
486 return (in.header.opcode == FUSE_SETATTR &&
487 in.header.nodeid == ino &&
488 in.body.setattr.valid == valid);
489 }, Eq(true)),
490 _)
491 ).WillOnce(Invoke(ReturnErrno(EACCES)));
492 expect_flush(ino, 1, ReturnErrno(0));
493 expect_release(ino, FuseTest::FH);
494
495 fd = open(FULLPATH, O_RDONLY);
496 ASSERT_LE(0, fd) << strerror(errno);
497
498 /* Ensure atime will be different than during lookup */
499 nap();
500
501 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
502
503 ASSERT_EQ(0, close(fd));
504 }
505
506 /* A cached atime should be flushed during FUSE_SETATTR */
TEST_F(Read,atime_during_setattr)507 TEST_F(Read, atime_during_setattr)
508 {
509 const char FULLPATH[] = "mountpoint/some_file.txt";
510 const char RELPATH[] = "some_file.txt";
511 const char *CONTENTS = "abcdefgh";
512 struct stat sb;
513 uint64_t ino = 42;
514 const mode_t newmode = 0755;
515 int fd;
516 ssize_t bufsize = strlen(CONTENTS);
517 uint8_t buf[bufsize];
518
519 expect_lookup(RELPATH, ino, bufsize);
520 expect_open(ino, 0, 1);
521 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
522 EXPECT_CALL(*m_mock, process(
523 ResultOf([&](auto in) {
524 uint32_t valid = FATTR_MODE | FATTR_ATIME;
525 return (in.header.opcode == FUSE_SETATTR &&
526 in.header.nodeid == ino &&
527 in.body.setattr.valid == valid &&
528 (time_t)in.body.setattr.atime ==
529 sb.st_atim.tv_sec &&
530 (long)in.body.setattr.atimensec ==
531 sb.st_atim.tv_nsec);
532 }, Eq(true)),
533 _)
534 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
535 SET_OUT_HEADER_LEN(out, attr);
536 out.body.attr.attr.ino = ino;
537 out.body.attr.attr.mode = S_IFREG | newmode;
538 })));
539
540 fd = open(FULLPATH, O_RDONLY);
541 ASSERT_LE(0, fd) << strerror(errno);
542
543 /* Ensure atime will be different than during lookup */
544 nap();
545
546 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
547 ASSERT_EQ(0, fstat(fd, &sb));
548 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
549
550 leak(fd);
551 }
552
553 /* 0-length reads shouldn't cause any confusion */
TEST_F(Read,direct_io_read_nothing)554 TEST_F(Read, direct_io_read_nothing)
555 {
556 const char FULLPATH[] = "mountpoint/some_file.txt";
557 const char RELPATH[] = "some_file.txt";
558 uint64_t ino = 42;
559 int fd;
560 uint64_t offset = 100;
561 char buf[80];
562
563 expect_lookup(RELPATH, ino, offset + 1000);
564 expect_open(ino, FOPEN_DIRECT_IO, 1);
565
566 fd = open(FULLPATH, O_RDONLY);
567 ASSERT_LE(0, fd) << strerror(errno);
568
569 ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno);
570 leak(fd);
571 }
572
573 /*
574 * With direct_io, reads should not fill the cache. They should go straight to
575 * the daemon
576 */
TEST_F(Read,direct_io_pread)577 TEST_F(Read, direct_io_pread)
578 {
579 const char FULLPATH[] = "mountpoint/some_file.txt";
580 const char RELPATH[] = "some_file.txt";
581 const char *CONTENTS = "abcdefgh";
582 uint64_t ino = 42;
583 int fd;
584 uint64_t offset = 100;
585 ssize_t bufsize = strlen(CONTENTS);
586 uint8_t buf[bufsize];
587
588 expect_lookup(RELPATH, ino, offset + bufsize);
589 expect_open(ino, FOPEN_DIRECT_IO, 1);
590 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
591
592 fd = open(FULLPATH, O_RDONLY);
593 ASSERT_LE(0, fd) << strerror(errno);
594
595 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
596 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
597
598 // With FOPEN_DIRECT_IO, the cache should be bypassed. The server will
599 // get a 2nd read request.
600 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
601 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
602 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
603 leak(fd);
604 }
605
606 /*
607 * With direct_io, filesystems are allowed to return less data than is
608 * requested. fuse(4) should return a short read to userland.
609 */
TEST_F(Read,direct_io_short_read)610 TEST_F(Read, direct_io_short_read)
611 {
612 const char FULLPATH[] = "mountpoint/some_file.txt";
613 const char RELPATH[] = "some_file.txt";
614 const char *CONTENTS = "abcdefghijklmnop";
615 uint64_t ino = 42;
616 int fd;
617 uint64_t offset = 100;
618 ssize_t bufsize = strlen(CONTENTS);
619 ssize_t halfbufsize = bufsize / 2;
620 uint8_t buf[bufsize];
621
622 expect_lookup(RELPATH, ino, offset + bufsize);
623 expect_open(ino, FOPEN_DIRECT_IO, 1);
624 expect_read(ino, offset, bufsize, halfbufsize, CONTENTS);
625
626 fd = open(FULLPATH, O_RDONLY);
627 ASSERT_LE(0, fd) << strerror(errno);
628
629 ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset))
630 << strerror(errno);
631 ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize));
632 leak(fd);
633 }
634
TEST_F(Read,eio)635 TEST_F(Read, eio)
636 {
637 const char FULLPATH[] = "mountpoint/some_file.txt";
638 const char RELPATH[] = "some_file.txt";
639 const char *CONTENTS = "abcdefgh";
640 uint64_t ino = 42;
641 int fd;
642 ssize_t bufsize = strlen(CONTENTS);
643 uint8_t buf[bufsize];
644
645 expect_lookup(RELPATH, ino, bufsize);
646 expect_open(ino, 0, 1);
647 EXPECT_CALL(*m_mock, process(
648 ResultOf([=](auto in) {
649 return (in.header.opcode == FUSE_READ);
650 }, Eq(true)),
651 _)
652 ).WillOnce(Invoke(ReturnErrno(EIO)));
653
654 fd = open(FULLPATH, O_RDONLY);
655 ASSERT_LE(0, fd) << strerror(errno);
656
657 ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno);
658 ASSERT_EQ(EIO, errno);
659 leak(fd);
660 }
661
662 /*
663 * If the server returns a short read when direct io is not in use, that
664 * indicates EOF, because of a server-side truncation. We should invalidate
665 * all cached attributes. We may update the file size,
666 */
TEST_F(Read,eof)667 TEST_F(Read, eof)
668 {
669 const char FULLPATH[] = "mountpoint/some_file.txt";
670 const char RELPATH[] = "some_file.txt";
671 const char *CONTENTS = "abcdefghijklmnop";
672 uint64_t ino = 42;
673 int fd;
674 uint64_t offset = 100;
675 ssize_t bufsize = strlen(CONTENTS);
676 ssize_t partbufsize = 3 * bufsize / 4;
677 ssize_t r;
678 uint8_t buf[bufsize];
679 struct stat sb;
680
681 expect_lookup(RELPATH, ino, offset + bufsize);
682 expect_open(ino, 0, 1);
683 expect_read(ino, 0, offset + bufsize, offset + partbufsize, CONTENTS);
684 expect_getattr(ino, offset + partbufsize);
685
686 fd = open(FULLPATH, O_RDONLY);
687 ASSERT_LE(0, fd) << strerror(errno);
688
689 r = pread(fd, buf, bufsize, offset);
690 ASSERT_LE(0, r) << strerror(errno);
691 EXPECT_EQ(partbufsize, r) << strerror(errno);
692 ASSERT_EQ(0, fstat(fd, &sb));
693 EXPECT_EQ((off_t)(offset + partbufsize), sb.st_size);
694 leak(fd);
695 }
696
697 /* Like Read.eof, but causes an entire buffer to be invalidated */
TEST_F(Read,eof_of_whole_buffer)698 TEST_F(Read, eof_of_whole_buffer)
699 {
700 const char FULLPATH[] = "mountpoint/some_file.txt";
701 const char RELPATH[] = "some_file.txt";
702 const char *CONTENTS = "abcdefghijklmnop";
703 uint64_t ino = 42;
704 int fd;
705 ssize_t bufsize = strlen(CONTENTS);
706 off_t old_filesize = m_maxbcachebuf * 2 + bufsize;
707 uint8_t buf[bufsize];
708 struct stat sb;
709
710 expect_lookup(RELPATH, ino, old_filesize);
711 expect_open(ino, 0, 1);
712 expect_read(ino, 2 * m_maxbcachebuf, bufsize, bufsize, CONTENTS);
713 expect_read(ino, m_maxbcachebuf, m_maxbcachebuf, 0, CONTENTS);
714 expect_getattr(ino, m_maxbcachebuf);
715
716 fd = open(FULLPATH, O_RDONLY);
717 ASSERT_LE(0, fd) << strerror(errno);
718
719 /* Cache the third block */
720 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, m_maxbcachebuf * 2))
721 << strerror(errno);
722 /* Try to read the 2nd block, but it's past EOF */
723 ASSERT_EQ(0, pread(fd, buf, bufsize, m_maxbcachebuf))
724 << strerror(errno);
725 ASSERT_EQ(0, fstat(fd, &sb));
726 EXPECT_EQ((off_t)(m_maxbcachebuf), sb.st_size);
727 leak(fd);
728 }
729
730 /*
731 * With the keep_cache option, the kernel may keep its read cache across
732 * multiple open(2)s.
733 */
TEST_F(Read,keep_cache)734 TEST_F(Read, keep_cache)
735 {
736 const char FULLPATH[] = "mountpoint/some_file.txt";
737 const char RELPATH[] = "some_file.txt";
738 const char *CONTENTS = "abcdefgh";
739 uint64_t ino = 42;
740 int fd0, fd1;
741 ssize_t bufsize = strlen(CONTENTS);
742 uint8_t buf[bufsize];
743
744 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
745 expect_open(ino, FOPEN_KEEP_CACHE, 2);
746 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
747
748 fd0 = open(FULLPATH, O_RDONLY);
749 ASSERT_LE(0, fd0) << strerror(errno);
750 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
751
752 fd1 = open(FULLPATH, O_RDWR);
753 ASSERT_LE(0, fd1) << strerror(errno);
754
755 /*
756 * This read should be serviced by cache, even though it's on the other
757 * file descriptor
758 */
759 ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno);
760
761 leak(fd0);
762 leak(fd1);
763 }
764
765 /*
766 * Without the keep_cache option, the kernel should drop its read caches on
767 * every open
768 */
TEST_F(Read,keep_cache_disabled)769 TEST_F(Read, keep_cache_disabled)
770 {
771 const char FULLPATH[] = "mountpoint/some_file.txt";
772 const char RELPATH[] = "some_file.txt";
773 const char *CONTENTS = "abcdefgh";
774 uint64_t ino = 42;
775 int fd0, fd1;
776 ssize_t bufsize = strlen(CONTENTS);
777 uint8_t buf[bufsize];
778
779 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2);
780 expect_open(ino, 0, 2);
781 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
782
783 fd0 = open(FULLPATH, O_RDONLY);
784 ASSERT_LE(0, fd0) << strerror(errno);
785 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
786
787 fd1 = open(FULLPATH, O_RDWR);
788 ASSERT_LE(0, fd1) << strerror(errno);
789
790 /*
791 * This read should not be serviced by cache, even though it's on the
792 * original file descriptor
793 */
794 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
795 ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno);
796 ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno);
797
798 leak(fd0);
799 leak(fd1);
800 }
801
TEST_F(Read,mmap)802 TEST_F(Read, mmap)
803 {
804 const char FULLPATH[] = "mountpoint/some_file.txt";
805 const char RELPATH[] = "some_file.txt";
806 const char *CONTENTS = "abcdefgh";
807 uint64_t ino = 42;
808 int fd;
809 ssize_t len;
810 size_t bufsize = strlen(CONTENTS);
811 void *p;
812
813 len = getpagesize();
814
815 expect_lookup(RELPATH, ino, bufsize);
816 expect_open(ino, 0, 1);
817 EXPECT_CALL(*m_mock, process(
818 ResultOf([=](auto in) {
819 return (in.header.opcode == FUSE_READ &&
820 in.header.nodeid == ino &&
821 in.body.read.fh == Read::FH &&
822 in.body.read.offset == 0 &&
823 in.body.read.size == bufsize);
824 }, Eq(true)),
825 _)
826 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
827 out.header.len = sizeof(struct fuse_out_header) + bufsize;
828 memmove(out.body.bytes, CONTENTS, bufsize);
829 })));
830
831 fd = open(FULLPATH, O_RDONLY);
832 ASSERT_LE(0, fd) << strerror(errno);
833
834 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
835 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
836
837 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
838
839 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
840 leak(fd);
841 }
842
843 /*
844 * The kernel should not update the cached atime attribute during a read, if
845 * MNT_NOATIME is used.
846 */
TEST_F(ReadNoatime,atime)847 TEST_F(ReadNoatime, atime)
848 {
849 const char FULLPATH[] = "mountpoint/some_file.txt";
850 const char RELPATH[] = "some_file.txt";
851 const char *CONTENTS = "abcdefgh";
852 struct stat sb1, sb2;
853 uint64_t ino = 42;
854 int fd;
855 ssize_t bufsize = strlen(CONTENTS);
856 uint8_t buf[bufsize];
857
858 expect_lookup(RELPATH, ino, bufsize);
859 expect_open(ino, 0, 1);
860 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
861
862 fd = open(FULLPATH, O_RDONLY);
863 ASSERT_LE(0, fd) << strerror(errno);
864 ASSERT_EQ(0, fstat(fd, &sb1));
865
866 nap();
867
868 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
869 ASSERT_EQ(0, fstat(fd, &sb2));
870
871 /* The kernel should not update atime during read */
872 EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, ==));
873 EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
874 EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
875
876 leak(fd);
877 }
878
879 /*
880 * The kernel should not update the cached atime attribute during a cached
881 * read, if MNT_NOATIME is used.
882 */
TEST_F(ReadNoatime,atime_cached)883 TEST_F(ReadNoatime, atime_cached)
884 {
885 const char FULLPATH[] = "mountpoint/some_file.txt";
886 const char RELPATH[] = "some_file.txt";
887 const char *CONTENTS = "abcdefgh";
888 struct stat sb1, sb2;
889 uint64_t ino = 42;
890 int fd;
891 ssize_t bufsize = strlen(CONTENTS);
892 uint8_t buf[bufsize];
893
894 expect_lookup(RELPATH, ino, bufsize);
895 expect_open(ino, 0, 1);
896 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
897
898 fd = open(FULLPATH, O_RDONLY);
899 ASSERT_LE(0, fd) << strerror(errno);
900
901 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
902 ASSERT_EQ(0, fstat(fd, &sb1));
903
904 nap();
905
906 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, 0)) << strerror(errno);
907 ASSERT_EQ(0, fstat(fd, &sb2));
908
909 /* The kernel should automatically update atime during read */
910 EXPECT_TRUE(timespeccmp(&sb1.st_atim, &sb2.st_atim, ==));
911 EXPECT_TRUE(timespeccmp(&sb1.st_ctim, &sb2.st_ctim, ==));
912 EXPECT_TRUE(timespeccmp(&sb1.st_mtim, &sb2.st_mtim, ==));
913
914 leak(fd);
915 }
916
917 /* Read of an mmap()ed file fails */
TEST_F(ReadSigbus,mmap_eio)918 TEST_F(ReadSigbus, mmap_eio)
919 {
920 const char FULLPATH[] = "mountpoint/some_file.txt";
921 const char RELPATH[] = "some_file.txt";
922 const char *CONTENTS = "abcdefgh";
923 struct sigaction sa;
924 uint64_t ino = 42;
925 int fd;
926 ssize_t len;
927 size_t bufsize = strlen(CONTENTS);
928 void *p;
929
930 len = getpagesize();
931
932 expect_lookup(RELPATH, ino, bufsize);
933 expect_open(ino, 0, 1);
934 EXPECT_CALL(*m_mock, process(
935 ResultOf([=](auto in) {
936 return (in.header.opcode == FUSE_READ &&
937 in.header.nodeid == ino &&
938 in.body.read.fh == Read::FH);
939 }, Eq(true)),
940 _)
941 ).WillRepeatedly(Invoke(ReturnErrno(EIO)));
942
943 fd = open(FULLPATH, O_RDONLY);
944 ASSERT_LE(0, fd) << strerror(errno);
945
946 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
947 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
948
949 /* Accessing the mapped page should return SIGBUS. */
950
951 bzero(&sa, sizeof(sa));
952 sa.sa_handler = SIG_DFL;
953 sa.sa_sigaction = handle_sigbus;
954 sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
955 ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
956 if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
957 atomic_signal_fence(std::memory_order::memory_order_seq_cst);
958 volatile char x __unused = *(volatile char*)p;
959 FAIL() << "shouldn't get here";
960 }
961
962 ASSERT_EQ(p, ReadSigbus::s_si_addr);
963 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
964 leak(fd);
965 }
966
967 /*
968 * A read via mmap comes up short, indicating that the file was truncated
969 * server-side.
970 */
TEST_F(Read,mmap_eof)971 TEST_F(Read, mmap_eof)
972 {
973 const char FULLPATH[] = "mountpoint/some_file.txt";
974 const char RELPATH[] = "some_file.txt";
975 const char *CONTENTS = "abcdefgh";
976 uint64_t ino = 42;
977 int fd;
978 ssize_t len;
979 size_t bufsize = strlen(CONTENTS);
980 struct stat sb;
981 void *p;
982
983 len = getpagesize();
984
985 expect_lookup(RELPATH, ino, m_maxbcachebuf);
986 expect_open(ino, 0, 1);
987 EXPECT_CALL(*m_mock, process(
988 ResultOf([=](auto in) {
989 return (in.header.opcode == FUSE_READ &&
990 in.header.nodeid == ino &&
991 in.body.read.fh == Read::FH &&
992 in.body.read.offset == 0 &&
993 in.body.read.size == (uint32_t)m_maxbcachebuf);
994 }, Eq(true)),
995 _)
996 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
997 out.header.len = sizeof(struct fuse_out_header) + bufsize;
998 memmove(out.body.bytes, CONTENTS, bufsize);
999 })));
1000 expect_getattr(ino, bufsize);
1001
1002 fd = open(FULLPATH, O_RDONLY);
1003 ASSERT_LE(0, fd) << strerror(errno);
1004
1005 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
1006 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
1007
1008 /* The file size should be automatically truncated */
1009 ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize));
1010 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1011 EXPECT_EQ((off_t)bufsize, sb.st_size);
1012
1013 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
1014 leak(fd);
1015 }
1016
1017 /*
1018 * During VOP_GETPAGES, the FUSE server fails a FUSE_GETATTR operation. This
1019 * almost certainly indicates a buggy FUSE server, and our goal should be not
1020 * to panic. Instead, generate SIGBUS.
1021 */
TEST_F(ReadSigbus,mmap_getblksz_fail)1022 TEST_F(ReadSigbus, mmap_getblksz_fail)
1023 {
1024 const char FULLPATH[] = "mountpoint/some_file.txt";
1025 const char RELPATH[] = "some_file.txt";
1026 const char *CONTENTS = "abcdefgh";
1027 struct sigaction sa;
1028 Sequence seq;
1029 uint64_t ino = 42;
1030 int fd;
1031 ssize_t len;
1032 size_t bufsize = strlen(CONTENTS);
1033 mode_t mode = S_IFREG | 0644;
1034 void *p;
1035
1036 len = getpagesize();
1037
1038 FuseTest::expect_lookup(RELPATH, ino, mode, bufsize, 1, 0);
1039 /* Expect two GETATTR calls that succeed, followed by one that fail. */
1040 EXPECT_CALL(*m_mock, process(
1041 ResultOf([=](auto in) {
1042 return (in.header.opcode == FUSE_GETATTR &&
1043 in.header.nodeid == ino);
1044 }, Eq(true)),
1045 _)
1046 ).Times(2)
1047 .InSequence(seq)
1048 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
1049 SET_OUT_HEADER_LEN(out, attr);
1050 out.body.attr.attr.ino = ino;
1051 out.body.attr.attr.mode = mode;
1052 out.body.attr.attr.size = bufsize;
1053 out.body.attr.attr_valid = 0;
1054 })));
1055 EXPECT_CALL(*m_mock, process(
1056 ResultOf([=](auto in) {
1057 return (in.header.opcode == FUSE_GETATTR &&
1058 in.header.nodeid == ino);
1059 }, Eq(true)),
1060 _)
1061 ).InSequence(seq)
1062 .WillRepeatedly(Invoke(ReturnErrno(EIO)));
1063 expect_open(ino, 0, 1);
1064 EXPECT_CALL(*m_mock, process(
1065 ResultOf([=](auto in) {
1066 return (in.header.opcode == FUSE_READ);
1067 }, Eq(true)),
1068 _)
1069 ).Times(0);
1070
1071 fd = open(FULLPATH, O_RDONLY);
1072 ASSERT_LE(0, fd) << strerror(errno);
1073
1074 p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
1075 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
1076
1077 /* Accessing the mapped page should return SIGBUS. */
1078 bzero(&sa, sizeof(sa));
1079 sa.sa_handler = SIG_DFL;
1080 sa.sa_sigaction = handle_sigbus;
1081 sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
1082 ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
1083 if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
1084 atomic_signal_fence(std::memory_order::memory_order_seq_cst);
1085 volatile char x __unused = *(volatile char*)p;
1086 FAIL() << "shouldn't get here";
1087 }
1088
1089 ASSERT_EQ(p, ReadSigbus::s_si_addr);
1090 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
1091 leak(fd);
1092 }
1093
1094 /*
1095 * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
1096 * cache and to straight to the daemon
1097 */
TEST_F(Read,o_direct)1098 TEST_F(Read, o_direct)
1099 {
1100 const char FULLPATH[] = "mountpoint/some_file.txt";
1101 const char RELPATH[] = "some_file.txt";
1102 const char *CONTENTS = "abcdefgh";
1103 uint64_t ino = 42;
1104 int fd;
1105 ssize_t bufsize = strlen(CONTENTS);
1106 uint8_t buf[bufsize];
1107
1108 expect_lookup(RELPATH, ino, bufsize);
1109 expect_open(ino, 0, 1);
1110 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1111
1112 fd = open(FULLPATH, O_RDONLY);
1113 ASSERT_LE(0, fd) << strerror(errno);
1114
1115 // Fill the cache
1116 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1117 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1118
1119 // Reads with o_direct should bypass the cache
1120 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1121 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
1122 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
1123 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1124 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1125
1126 leak(fd);
1127 }
1128
TEST_F(Read,pread)1129 TEST_F(Read, pread)
1130 {
1131 const char FULLPATH[] = "mountpoint/some_file.txt";
1132 const char RELPATH[] = "some_file.txt";
1133 const char *CONTENTS = "abcdefgh";
1134 uint64_t ino = 42;
1135 int fd;
1136 /*
1137 * Set offset to a maxbcachebuf boundary so we'll be sure what offset
1138 * to read from. Without this, the read might start at a lower offset.
1139 */
1140 uint64_t offset = m_maxbcachebuf;
1141 ssize_t bufsize = strlen(CONTENTS);
1142 uint8_t buf[bufsize];
1143
1144 expect_lookup(RELPATH, ino, offset + bufsize);
1145 expect_open(ino, 0, 1);
1146 expect_read(ino, offset, bufsize, bufsize, CONTENTS);
1147
1148 fd = open(FULLPATH, O_RDONLY);
1149 ASSERT_LE(0, fd) << strerror(errno);
1150
1151 ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno);
1152 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1153 leak(fd);
1154 }
1155
TEST_F(Read,read)1156 TEST_F(Read, read)
1157 {
1158 const char FULLPATH[] = "mountpoint/some_file.txt";
1159 const char RELPATH[] = "some_file.txt";
1160 const char *CONTENTS = "abcdefgh";
1161 uint64_t ino = 42;
1162 int fd;
1163 ssize_t bufsize = strlen(CONTENTS);
1164 uint8_t buf[bufsize];
1165
1166 expect_lookup(RELPATH, ino, bufsize);
1167 expect_open(ino, 0, 1);
1168 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1169
1170 fd = open(FULLPATH, O_RDONLY);
1171 ASSERT_LE(0, fd) << strerror(errno);
1172
1173 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1174 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1175
1176 leak(fd);
1177 }
1178
TEST_F(Read_7_8,read)1179 TEST_F(Read_7_8, read)
1180 {
1181 const char FULLPATH[] = "mountpoint/some_file.txt";
1182 const char RELPATH[] = "some_file.txt";
1183 const char *CONTENTS = "abcdefgh";
1184 uint64_t ino = 42;
1185 int fd;
1186 ssize_t bufsize = strlen(CONTENTS);
1187 uint8_t buf[bufsize];
1188
1189 expect_lookup(RELPATH, ino, bufsize);
1190 expect_open(ino, 0, 1);
1191 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1192
1193 fd = open(FULLPATH, O_RDONLY);
1194 ASSERT_LE(0, fd) << strerror(errno);
1195
1196 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1197 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1198
1199 leak(fd);
1200 }
1201
1202 /*
1203 * If cacheing is enabled, the kernel should try to read an entire cache block
1204 * at a time.
1205 */
TEST_F(Read,cache_block)1206 TEST_F(Read, cache_block)
1207 {
1208 const char FULLPATH[] = "mountpoint/some_file.txt";
1209 const char RELPATH[] = "some_file.txt";
1210 const char *CONTENTS0 = "abcdefghijklmnop";
1211 uint64_t ino = 42;
1212 int fd;
1213 ssize_t bufsize = 8;
1214 ssize_t filesize = m_maxbcachebuf * 2;
1215 char *contents;
1216 char buf[bufsize];
1217 const char *contents1 = CONTENTS0 + bufsize;
1218
1219 contents = new char[filesize]();
1220 memmove(contents, CONTENTS0, strlen(CONTENTS0));
1221
1222 expect_lookup(RELPATH, ino, filesize);
1223 expect_open(ino, 0, 1);
1224 expect_read(ino, 0, m_maxbcachebuf, m_maxbcachebuf,
1225 contents);
1226
1227 fd = open(FULLPATH, O_RDONLY);
1228 ASSERT_LE(0, fd) << strerror(errno);
1229
1230 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1231 ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize));
1232
1233 /* A subsequent read should be serviced by cache */
1234 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1235 ASSERT_EQ(0, memcmp(buf, contents1, bufsize));
1236 leak(fd);
1237 delete[] contents;
1238 }
1239
1240 /* Reading with sendfile should work (though it obviously won't be 0-copy) */
TEST_F(Read,sendfile)1241 TEST_F(Read, sendfile)
1242 {
1243 const char FULLPATH[] = "mountpoint/some_file.txt";
1244 const char RELPATH[] = "some_file.txt";
1245 const char *CONTENTS = "abcdefgh";
1246 uint64_t ino = 42;
1247 int fd;
1248 size_t bufsize = strlen(CONTENTS);
1249 uint8_t buf[bufsize];
1250 int sp[2];
1251 off_t sbytes;
1252
1253 expect_lookup(RELPATH, ino, bufsize);
1254 expect_open(ino, 0, 1);
1255 EXPECT_CALL(*m_mock, process(
1256 ResultOf([=](auto in) {
1257 return (in.header.opcode == FUSE_READ &&
1258 in.header.nodeid == ino &&
1259 in.body.read.fh == Read::FH &&
1260 in.body.read.offset == 0 &&
1261 in.body.read.size == bufsize);
1262 }, Eq(true)),
1263 _)
1264 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1265 out.header.len = sizeof(struct fuse_out_header) + bufsize;
1266 memmove(out.body.bytes, CONTENTS, bufsize);
1267 })));
1268
1269 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
1270 << strerror(errno);
1271 fd = open(FULLPATH, O_RDONLY);
1272 ASSERT_LE(0, fd) << strerror(errno);
1273
1274 ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0))
1275 << strerror(errno);
1276 ASSERT_EQ(static_cast<ssize_t>(bufsize), read(sp[0], buf, bufsize))
1277 << strerror(errno);
1278 ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
1279
1280 close(sp[1]);
1281 close(sp[0]);
1282 leak(fd);
1283 }
1284
1285 /* sendfile should fail gracefully if fuse declines the read */
1286 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */
TEST_F(Read,sendfile_eio)1287 TEST_F(Read, sendfile_eio)
1288 {
1289 const char FULLPATH[] = "mountpoint/some_file.txt";
1290 const char RELPATH[] = "some_file.txt";
1291 const char *CONTENTS = "abcdefgh";
1292 uint64_t ino = 42;
1293 int fd;
1294 ssize_t bufsize = strlen(CONTENTS);
1295 int sp[2];
1296 off_t sbytes;
1297
1298 expect_lookup(RELPATH, ino, bufsize);
1299 expect_open(ino, 0, 1);
1300 EXPECT_CALL(*m_mock, process(
1301 ResultOf([=](auto in) {
1302 return (in.header.opcode == FUSE_READ);
1303 }, Eq(true)),
1304 _)
1305 ).WillOnce(Invoke(ReturnErrno(EIO)));
1306
1307 ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp))
1308 << strerror(errno);
1309 fd = open(FULLPATH, O_RDONLY);
1310 ASSERT_LE(0, fd) << strerror(errno);
1311
1312 ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0));
1313
1314 close(sp[1]);
1315 close(sp[0]);
1316 leak(fd);
1317 }
1318
1319 /*
1320 * Sequential reads should use readahead. And if allowed, large reads should
1321 * be clustered.
1322 */
TEST_P(ReadAhead,readahead)1323 TEST_P(ReadAhead, readahead) {
1324 const char FULLPATH[] = "mountpoint/some_file.txt";
1325 const char RELPATH[] = "some_file.txt";
1326 uint64_t ino = 42;
1327 int fd, maxcontig, clustersize;
1328 ssize_t bufsize = 4 * m_maxbcachebuf;
1329 ssize_t filesize = bufsize;
1330 uint64_t len;
1331 char *rbuf, *contents;
1332 off_t offs;
1333
1334 contents = new char[filesize];
1335 memset(contents, 'X', filesize);
1336 rbuf = new char[bufsize]();
1337
1338 expect_lookup(RELPATH, ino, filesize);
1339 expect_open(ino, 0, 1);
1340 maxcontig = m_noclusterr ? m_maxbcachebuf :
1341 m_maxbcachebuf + m_maxreadahead;
1342 clustersize = MIN(maxcontig, m_maxphys);
1343 for (offs = 0; offs < bufsize; offs += clustersize) {
1344 len = std::min((size_t)clustersize, (size_t)(filesize - offs));
1345 expect_read(ino, offs, len, len, contents + offs);
1346 }
1347
1348 fd = open(FULLPATH, O_RDONLY);
1349 ASSERT_LE(0, fd) << strerror(errno);
1350
1351 /* Set the internal readahead counter to a "large" value */
1352 ASSERT_EQ(0, fcntl(fd, F_READAHEAD, 1'000'000'000)) << strerror(errno);
1353
1354 ASSERT_EQ(bufsize, read(fd, rbuf, bufsize)) << strerror(errno);
1355 ASSERT_EQ(0, memcmp(rbuf, contents, bufsize));
1356
1357 leak(fd);
1358 delete[] rbuf;
1359 delete[] contents;
1360 }
1361
1362 INSTANTIATE_TEST_SUITE_P(RA, ReadAhead,
1363 Values(tuple<bool, int>(false, 0),
1364 tuple<bool, int>(false, 1),
1365 tuple<bool, int>(false, 2),
1366 tuple<bool, int>(false, 3),
1367 tuple<bool, int>(true, 0),
1368 tuple<bool, int>(true, 1),
1369 tuple<bool, int>(true, 2)));
1370
1371 /* With read-only mounts, fuse should never update atime during close */
TEST_F(RofsRead,atime_during_close)1372 TEST_F(RofsRead, atime_during_close)
1373 {
1374 const char FULLPATH[] = "mountpoint/some_file.txt";
1375 const char RELPATH[] = "some_file.txt";
1376 const char *CONTENTS = "abcdefgh";
1377 uint64_t ino = 42;
1378 int fd;
1379 ssize_t bufsize = strlen(CONTENTS);
1380 uint8_t buf[bufsize];
1381
1382 expect_lookup(RELPATH, ino, bufsize);
1383 expect_open(ino, 0, 1);
1384 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1385 EXPECT_CALL(*m_mock, process(
1386 ResultOf([&](auto in) {
1387 return (in.header.opcode == FUSE_SETATTR);
1388 }, Eq(true)),
1389 _)
1390 ).Times(0);
1391 expect_flush(ino, 1, ReturnErrno(0));
1392 expect_release(ino, FuseTest::FH);
1393
1394 fd = open(FULLPATH, O_RDONLY);
1395 ASSERT_LE(0, fd) << strerror(errno);
1396
1397 /* Ensure atime will be different than during lookup */
1398 nap();
1399
1400 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1401
1402 close(fd);
1403 }
1404
1405 /* fuse_init_out.time_gran controls the granularity of timestamps */
TEST_P(TimeGran,atime_during_setattr)1406 TEST_P(TimeGran, atime_during_setattr)
1407 {
1408 const char FULLPATH[] = "mountpoint/some_file.txt";
1409 const char RELPATH[] = "some_file.txt";
1410 const char *CONTENTS = "abcdefgh";
1411 ssize_t bufsize = strlen(CONTENTS);
1412 uint8_t buf[bufsize];
1413 uint64_t ino = 42;
1414 const mode_t newmode = 0755;
1415 int fd;
1416
1417 expect_lookup(RELPATH, ino, bufsize);
1418 expect_open(ino, 0, 1);
1419 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
1420 EXPECT_CALL(*m_mock, process(
1421 ResultOf([=](auto in) {
1422 uint32_t valid = FATTR_MODE | FATTR_ATIME;
1423 return (in.header.opcode == FUSE_SETATTR &&
1424 in.header.nodeid == ino &&
1425 in.body.setattr.valid == valid &&
1426 in.body.setattr.atimensec % m_time_gran == 0);
1427 }, Eq(true)),
1428 _)
1429 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1430 SET_OUT_HEADER_LEN(out, attr);
1431 out.body.attr.attr.ino = ino;
1432 out.body.attr.attr.mode = S_IFREG | newmode;
1433 })));
1434
1435 fd = open(FULLPATH, O_RDWR);
1436 ASSERT_LE(0, fd) << strerror(errno);
1437
1438 ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
1439 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
1440
1441 leak(fd);
1442 }
1443
1444 INSTANTIATE_TEST_SUITE_P(TG, TimeGran, Range(0u, 10u));
1445