]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/write.cc
fusefs: send FUSE_FLUSH during VOP_CLOSE
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / write.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/stat.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 Write: public FuseTest {
49
50 public:
51
52 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
53 {
54         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
55 }
56
57 void expect_release(uint64_t ino, ProcessMockerT r)
58 {
59         EXPECT_CALL(*m_mock, process(
60                 ResultOf([=](auto in) {
61                         return (in->header.opcode == FUSE_RELEASE &&
62                                 in->header.nodeid == ino);
63                 }, Eq(true)),
64                 _)
65         ).WillRepeatedly(Invoke(r));
66 }
67
68 void require_sync_resize_0() {
69         const char *sync_resize_node = "vfs.fusefs.sync_resize";
70         int val = 0;
71         size_t size = sizeof(val);
72
73         ASSERT_EQ(0, sysctlbyname(sync_resize_node, &val, &size, NULL, 0))
74                 << strerror(errno);
75         if (val != 0)
76                 GTEST_SKIP() <<
77                         "vfs.fusefs.sync_resize must be set to 0 for this test."
78                         "  That sysctl will probably be removed soon.";
79 }
80
81 };
82
83 class AioWrite: public Write {
84 virtual void SetUp() {
85         const char *node = "vfs.aio.enable_unsafe";
86         int val = 0;
87         size_t size = sizeof(val);
88
89         FuseTest::SetUp();
90
91         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
92                 << strerror(errno);
93         if (!val)
94                 GTEST_SKIP() <<
95                         "vfs.aio.enable_unsafe must be set for this test";
96 }
97 };
98
99 /* Tests for the write-through cache mode */
100 class WriteThrough: public Write {
101
102 virtual void SetUp() {
103         const char *cache_mode_node = "vfs.fusefs.data_cache_mode";
104         int val = 0;
105         size_t size = sizeof(val);
106
107         FuseTest::SetUp();
108         if (IsSkipped())
109                 return;
110
111         ASSERT_EQ(0, sysctlbyname(cache_mode_node, &val, &size, NULL, 0))
112                 << strerror(errno);
113         if (val != 1)
114                 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 1 "
115                         "(writethrough) for this test";
116 }
117
118 };
119
120 /* Tests for the writeback cache mode */
121 class WriteBack: public Write {
122
123 virtual void SetUp() {
124         const char *node = "vfs.fusefs.data_cache_mode";
125         int val = 0;
126         size_t size = sizeof(val);
127
128         FuseTest::SetUp();
129         if (IsSkipped())
130                 return;
131
132         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
133                 << strerror(errno);
134         if (val != 2)
135                 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 2 "
136                         "(writeback) for this test";
137 }
138
139 };
140
141 /* AIO writes need to set the header's pid field correctly */
142 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
143 TEST_F(AioWrite, DISABLED_aio_write)
144 {
145         const char FULLPATH[] = "mountpoint/some_file.txt";
146         const char RELPATH[] = "some_file.txt";
147         const char *CONTENTS = "abcdefgh";
148         uint64_t ino = 42;
149         uint64_t offset = 4096;
150         int fd;
151         ssize_t bufsize = strlen(CONTENTS);
152         struct aiocb iocb, *piocb;
153
154         expect_lookup(RELPATH, ino, 0);
155         expect_open(ino, 0, 1);
156         expect_getattr(ino, 0);
157         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
158
159         fd = open(FULLPATH, O_WRONLY);
160         EXPECT_LE(0, fd) << strerror(errno);
161
162         iocb.aio_nbytes = bufsize;
163         iocb.aio_fildes = fd;
164         iocb.aio_buf = (void *)CONTENTS;
165         iocb.aio_offset = offset;
166         iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
167         ASSERT_EQ(0, aio_write(&iocb)) << strerror(errno);
168         ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
169         /* Deliberately leak fd.  close(2) will be tested in release.cc */
170 }
171
172 /* 
173  * When a file is opened with O_APPEND, we should forward that flag to
174  * FUSE_OPEN (tested by Open.o_append) but still attempt to calculate the
175  * offset internally.  That way we'll work both with filesystems that
176  * understand O_APPEND (and ignore the offset) and filesystems that don't (and
177  * simply use the offset).
178  *
179  * Note that verifying the O_APPEND flag in FUSE_OPEN is done in the
180  * Open.o_append test.
181  */
182 TEST_F(Write, append)
183 {
184         const ssize_t BUFSIZE = 9;
185         const char FULLPATH[] = "mountpoint/some_file.txt";
186         const char RELPATH[] = "some_file.txt";
187         const char CONTENTS[BUFSIZE] = "abcdefgh";
188         uint64_t ino = 42;
189         /* 
190          * Set offset to a maxbcachebuf boundary so we don't need to RMW when
191          * using writeback caching
192          */
193         uint64_t initial_offset = m_maxbcachebuf;
194         int fd;
195
196         require_sync_resize_0();
197
198         expect_lookup(RELPATH, ino, initial_offset);
199         expect_open(ino, 0, 1);
200         expect_getattr(ino, initial_offset);
201         expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS);
202
203         /* Must open O_RDWR or fuse(4) implicitly sets direct_io */
204         fd = open(FULLPATH, O_RDWR | O_APPEND);
205         EXPECT_LE(0, fd) << strerror(errno);
206
207         ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
208         /* Deliberately leak fd.  close(2) will be tested in release.cc */
209 }
210
211 TEST_F(Write, append_direct_io)
212 {
213         const ssize_t BUFSIZE = 9;
214         const char FULLPATH[] = "mountpoint/some_file.txt";
215         const char RELPATH[] = "some_file.txt";
216         const char CONTENTS[BUFSIZE] = "abcdefgh";
217         uint64_t ino = 42;
218         uint64_t initial_offset = 4096;
219         int fd;
220
221         expect_lookup(RELPATH, ino, initial_offset);
222         expect_open(ino, FOPEN_DIRECT_IO, 1);
223         expect_getattr(ino, initial_offset);
224         expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS);
225
226         fd = open(FULLPATH, O_WRONLY | O_APPEND);
227         EXPECT_LE(0, fd) << strerror(errno);
228
229         ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
230         /* Deliberately leak fd.  close(2) will be tested in release.cc */
231 }
232
233 /* A direct write should evict any overlapping cached data */
234 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235774 */
235 TEST_F(Write, DISABLED_direct_io_evicts_cache)
236 {
237         const char FULLPATH[] = "mountpoint/some_file.txt";
238         const char RELPATH[] = "some_file.txt";
239         const char CONTENTS0[] = "abcdefgh";
240         const char CONTENTS1[] = "ijklmnop";
241         uint64_t ino = 42;
242         int fd;
243         ssize_t bufsize = strlen(CONTENTS0) + 1;
244         char readbuf[bufsize];
245
246         expect_lookup(RELPATH, ino, bufsize);
247         expect_open(ino, 0, 1);
248         expect_getattr(ino, bufsize);
249         expect_read(ino, 0, bufsize, bufsize, CONTENTS0);
250         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS1);
251
252         fd = open(FULLPATH, O_RDWR);
253         EXPECT_LE(0, fd) << strerror(errno);
254
255         // Prime cache
256         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
257
258         // Write directly, evicting cache
259         ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
260         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
261         ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno);
262
263         // Read again.  Cache should be bypassed
264         expect_read(ino, 0, bufsize, bufsize, CONTENTS1);
265         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
266         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
267         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
268         ASSERT_STREQ(readbuf, CONTENTS1);
269
270         /* Deliberately leak fd.  close(2) will be tested in release.cc */
271 }
272
273 /* 
274  * When the direct_io option is used, filesystems are allowed to write less
275  * data than requested
276  */
277 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */
278 TEST_F(Write, DISABLED_direct_io_short_write)
279 {
280         const char FULLPATH[] = "mountpoint/some_file.txt";
281         const char RELPATH[] = "some_file.txt";
282         const char *CONTENTS = "abcdefghijklmnop";
283         uint64_t ino = 42;
284         int fd;
285         ssize_t bufsize = strlen(CONTENTS);
286         ssize_t halfbufsize = bufsize / 2;
287         const char *halfcontents = CONTENTS + halfbufsize;
288
289         expect_lookup(RELPATH, ino, 0);
290         expect_open(ino, FOPEN_DIRECT_IO, 1);
291         expect_getattr(ino, 0);
292         expect_write(ino, 0, bufsize, halfbufsize, 0, CONTENTS);
293         expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0,
294                 halfcontents);
295
296         fd = open(FULLPATH, O_WRONLY);
297         EXPECT_LE(0, fd) << strerror(errno);
298
299         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
300         /* Deliberately leak fd.  close(2) will be tested in release.cc */
301 }
302
303 /*
304  * An insidious edge case: the filesystem returns a short write, and the
305  * difference between what we requested and what it actually wrote crosses an
306  * iov element boundary
307  */
308 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */
309 TEST_F(Write, DISABLED_direct_io_short_write_iov)
310 {
311         const char FULLPATH[] = "mountpoint/some_file.txt";
312         const char RELPATH[] = "some_file.txt";
313         const char *CONTENTS0 = "abcdefgh";
314         const char *CONTENTS1 = "ijklmnop";
315         const char *EXPECTED0 = "abcdefghijklmnop";
316         const char *EXPECTED1 = "hijklmnop";
317         uint64_t ino = 42;
318         int fd;
319         ssize_t size0 = strlen(CONTENTS0) - 1;
320         ssize_t size1 = strlen(CONTENTS1) + 1;
321         ssize_t totalsize = size0 + size1;
322         struct iovec iov[2];
323
324         expect_lookup(RELPATH, ino, 0);
325         expect_open(ino, FOPEN_DIRECT_IO, 1);
326         expect_getattr(ino, 0);
327         expect_write(ino, 0, totalsize, size0, 0, EXPECTED0);
328         expect_write(ino, size0, size1, size1, 0, EXPECTED1);
329
330         fd = open(FULLPATH, O_WRONLY);
331         EXPECT_LE(0, fd) << strerror(errno);
332
333         iov[0].iov_base = (void*)CONTENTS0;
334         iov[0].iov_len = strlen(CONTENTS0);
335         iov[1].iov_base = (void*)CONTENTS1;
336         iov[1].iov_len = strlen(CONTENTS1);
337         ASSERT_EQ(totalsize, writev(fd, iov, 2)) << strerror(errno);
338         /* Deliberately leak fd.  close(2) will be tested in release.cc */
339 }
340
341 /*
342  * If the kernel cannot be sure which uid, gid, or pid was responsible for a
343  * write, then it must set the FUSE_WRITE_CACHE bit
344  */
345 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */
346 // TODO: check vfs.fusefs.mmap_enable
347 TEST_F(Write, DISABLED_mmap)
348 {
349         const char FULLPATH[] = "mountpoint/some_file.txt";
350         const char RELPATH[] = "some_file.txt";
351         const char *CONTENTS = "abcdefgh";
352         uint64_t ino = 42;
353         int fd;
354         ssize_t bufsize = strlen(CONTENTS);
355         void *p;
356         uint64_t offset = 10;
357         size_t len;
358         void *zeros, *expected;
359
360         len = getpagesize();
361
362         zeros = calloc(1, len);
363         ASSERT_NE(NULL, zeros);
364         expected = calloc(1, len);
365         ASSERT_NE(NULL, expected);
366         memmove((uint8_t*)expected + offset, CONTENTS, bufsize);
367
368         expect_lookup(RELPATH, ino, len);
369         expect_open(ino, 0, 1);
370         expect_getattr(ino, len);
371         expect_read(ino, 0, len, len, zeros);
372         /* 
373          * Writes from the pager may or may not be associated with the correct
374          * pid, so they must set FUSE_WRITE_CACHE
375          */
376         expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, expected);
377         expect_flush(ino, 1, ReturnErrno(0));
378         expect_release(ino, ReturnErrno(0));
379
380         fd = open(FULLPATH, O_RDWR);
381         EXPECT_LE(0, fd) << strerror(errno);
382
383         p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
384         ASSERT_NE(MAP_FAILED, p) << strerror(errno);
385
386         memmove((uint8_t*)p + offset, CONTENTS, bufsize);
387
388         ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
389         close(fd);      // Write mmap'd data on close
390
391         free(expected);
392         free(zeros);
393 }
394
395 TEST_F(WriteThrough, pwrite)
396 {
397         const char FULLPATH[] = "mountpoint/some_file.txt";
398         const char RELPATH[] = "some_file.txt";
399         const char *CONTENTS = "abcdefgh";
400         uint64_t ino = 42;
401         uint64_t offset = 4096;
402         int fd;
403         ssize_t bufsize = strlen(CONTENTS);
404
405         expect_lookup(RELPATH, ino, 0);
406         expect_open(ino, 0, 1);
407         expect_getattr(ino, 0);
408         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
409
410         fd = open(FULLPATH, O_WRONLY);
411         EXPECT_LE(0, fd) << strerror(errno);
412
413         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
414                 << strerror(errno);
415         /* Deliberately leak fd.  close(2) will be tested in release.cc */
416 }
417
418 TEST_F(Write, write)
419 {
420         const char FULLPATH[] = "mountpoint/some_file.txt";
421         const char RELPATH[] = "some_file.txt";
422         const char *CONTENTS = "abcdefgh";
423         uint64_t ino = 42;
424         int fd;
425         ssize_t bufsize = strlen(CONTENTS);
426
427         expect_lookup(RELPATH, ino, 0);
428         expect_open(ino, 0, 1);
429         expect_getattr(ino, 0);
430         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
431
432         fd = open(FULLPATH, O_WRONLY);
433         EXPECT_LE(0, fd) << strerror(errno);
434
435         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
436         /* Deliberately leak fd.  close(2) will be tested in release.cc */
437 }
438
439 /* fuse(4) should not issue writes of greater size than the daemon requests */
440 TEST_F(Write, write_large)
441 {
442         const char FULLPATH[] = "mountpoint/some_file.txt";
443         const char RELPATH[] = "some_file.txt";
444         int *contents;
445         uint64_t ino = 42;
446         int fd;
447         ssize_t halfbufsize, bufsize;
448
449         halfbufsize = m_mock->m_max_write;
450         bufsize = halfbufsize * 2;
451         contents = (int*)malloc(bufsize);
452         ASSERT_NE(NULL, contents);
453         for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) {
454                 contents[i] = i;
455         }
456
457         expect_lookup(RELPATH, ino, 0);
458         expect_open(ino, 0, 1);
459         expect_getattr(ino, 0);
460         expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents);
461         expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0,
462                 &contents[halfbufsize / sizeof(int)]);
463
464         fd = open(FULLPATH, O_WRONLY);
465         EXPECT_LE(0, fd) << strerror(errno);
466
467         ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno);
468         /* Deliberately leak fd.  close(2) will be tested in release.cc */
469
470         free(contents);
471 }
472
473 TEST_F(Write, write_nothing)
474 {
475         const char FULLPATH[] = "mountpoint/some_file.txt";
476         const char RELPATH[] = "some_file.txt";
477         const char *CONTENTS = "";
478         uint64_t ino = 42;
479         int fd;
480         ssize_t bufsize = 0;
481
482         expect_lookup(RELPATH, ino, 0);
483         expect_open(ino, 0, 1);
484         expect_getattr(ino, 0);
485
486         fd = open(FULLPATH, O_WRONLY);
487         EXPECT_LE(0, fd) << strerror(errno);
488
489         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
490         /* Deliberately leak fd.  close(2) will be tested in release.cc */
491 }
492
493 /* In writeback mode, dirty data should be written on close */
494 TEST_F(WriteBack, close)
495 {
496         const char FULLPATH[] = "mountpoint/some_file.txt";
497         const char RELPATH[] = "some_file.txt";
498         const char *CONTENTS = "abcdefgh";
499         uint64_t ino = 42;
500         int fd;
501         ssize_t bufsize = strlen(CONTENTS);
502
503         expect_lookup(RELPATH, ino, 0);
504         expect_open(ino, 0, 1);
505         expect_getattr(ino, 0);
506         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
507         EXPECT_CALL(*m_mock, process(
508                 ResultOf([=](auto in) {
509                         return (in->header.opcode == FUSE_SETATTR);
510                 }, Eq(true)),
511                 _)
512         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
513                 SET_OUT_HEADER_LEN(out, attr);
514                 out->body.attr.attr.ino = ino;  // Must match nodeid
515         })));
516         expect_flush(ino, 1, ReturnErrno(0));
517         expect_release(ino, ReturnErrno(0));
518
519         fd = open(FULLPATH, O_RDWR);
520         ASSERT_LE(0, fd) << strerror(errno);
521
522         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
523         close(fd);
524 }
525
526 /*
527  * In writeback mode, writes to an O_WRONLY file could trigger reads from the
528  * server.  The FUSE protocol explicitly allows that.
529  */
530 TEST_F(WriteBack, rmw)
531 {
532         const char FULLPATH[] = "mountpoint/some_file.txt";
533         const char RELPATH[] = "some_file.txt";
534         const char *CONTENTS = "abcdefgh";
535         const char *INITIAL   = "XXXXXXXXXX";
536         uint64_t ino = 42;
537         uint64_t offset = 1;
538         off_t fsize = 10;
539         int fd;
540         ssize_t bufsize = strlen(CONTENTS);
541
542         expect_lookup(RELPATH, ino, 0);
543         expect_open(ino, 0, 1);
544         expect_getattr(ino, fsize);
545         expect_read(ino, 0, fsize, fsize, INITIAL);
546         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
547
548         fd = open(FULLPATH, O_WRONLY);
549         EXPECT_LE(0, fd) << strerror(errno);
550
551         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
552                 << strerror(errno);
553         /* Deliberately leak fd.  close(2) will be tested in release.cc */
554 }
555
556 /*
557  * Without direct_io, writes should be committed to cache
558  */
559 TEST_F(WriteBack, writeback)
560 {
561         const char FULLPATH[] = "mountpoint/some_file.txt";
562         const char RELPATH[] = "some_file.txt";
563         const char *CONTENTS = "abcdefgh";
564         uint64_t ino = 42;
565         int fd;
566         ssize_t bufsize = strlen(CONTENTS);
567         char readbuf[bufsize];
568
569         expect_lookup(RELPATH, ino, 0);
570         expect_open(ino, 0, 1);
571         expect_getattr(ino, 0);
572         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
573
574         fd = open(FULLPATH, O_RDWR);
575         EXPECT_LE(0, fd) << strerror(errno);
576
577         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
578         /* 
579          * A subsequent read should be serviced by cache, without querying the
580          * filesystem daemon
581          */
582         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
583         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
584         /* Deliberately leak fd.  close(2) will be tested in release.cc */
585 }
586
587 /*
588  * With O_DIRECT, writes should be not committed to cache.  Admittedly this is
589  * an odd test, because it would be unusual to use O_DIRECT for writes but not
590  * reads.
591  */
592 TEST_F(WriteBack, o_direct)
593 {
594         const char FULLPATH[] = "mountpoint/some_file.txt";
595         const char RELPATH[] = "some_file.txt";
596         const char *CONTENTS = "abcdefgh";
597         uint64_t ino = 42;
598         int fd;
599         ssize_t bufsize = strlen(CONTENTS);
600         char readbuf[bufsize];
601
602         expect_lookup(RELPATH, ino, 0);
603         expect_open(ino, 0, 1);
604         expect_getattr(ino, 0);
605         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
606         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
607
608         fd = open(FULLPATH, O_RDWR | O_DIRECT);
609         EXPECT_LE(0, fd) << strerror(errno);
610
611         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
612         /* A subsequent read must query the daemon because cache is empty */
613         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
614         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
615         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
616         /* Deliberately leak fd.  close(2) will be tested in release.cc */
617 }
618
619 /*
620  * Without direct_io, writes should be committed to cache
621  */
622 /* 
623  * Disabled because we don't yet implement write-through caching.  No bugzilla
624  * entry, because that's a feature request, not a bug.
625  */
626 TEST_F(WriteThrough, DISABLED_writethrough)
627 {
628         const char FULLPATH[] = "mountpoint/some_file.txt";
629         const char RELPATH[] = "some_file.txt";
630         const char *CONTENTS = "abcdefgh";
631         uint64_t ino = 42;
632         int fd;
633         ssize_t bufsize = strlen(CONTENTS);
634         char readbuf[bufsize];
635
636         expect_lookup(RELPATH, ino, 0);
637         expect_open(ino, 0, 1);
638         expect_getattr(ino, 0);
639         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
640
641         fd = open(FULLPATH, O_RDWR);
642         EXPECT_LE(0, fd) << strerror(errno);
643
644         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
645         /* 
646          * A subsequent read should be serviced by cache, without querying the
647          * filesystem daemon
648          */
649         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
650         /* Deliberately leak fd.  close(2) will be tested in release.cc */
651 }
652
653 /* With writethrough caching, writes update the cached file size */
654 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
655 TEST_F(WriteThrough, DISABLED_update_file_size)
656 {
657         const char FULLPATH[] = "mountpoint/some_file.txt";
658         const char RELPATH[] = "some_file.txt";
659         const char *CONTENTS = "abcdefgh";
660         struct stat sb;
661         uint64_t ino = 42;
662         int fd;
663         ssize_t bufsize = strlen(CONTENTS);
664
665         expect_lookup(RELPATH, ino, 0);
666         expect_open(ino, 0, 1);
667         EXPECT_CALL(*m_mock, process(
668                 ResultOf([=](auto in) {
669                         return (in->header.opcode == FUSE_GETATTR &&
670                                 in->header.nodeid == ino);
671                 }, Eq(true)),
672                 _)
673         ).Times(2)
674         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
675                 SET_OUT_HEADER_LEN(out, attr);
676                 out->body.attr.attr.ino = ino;  // Must match nodeid
677                 out->body.attr.attr.mode = S_IFREG | 0644;
678                 out->body.attr.attr.size = 0;
679                 out->body.attr.attr_valid = UINT64_MAX;
680         })));
681         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
682
683         fd = open(FULLPATH, O_RDWR);
684         EXPECT_LE(0, fd) << strerror(errno);
685
686         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
687         /* Get cached attributes */
688         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
689         ASSERT_EQ(bufsize, sb.st_size);
690         /* Deliberately leak fd.  close(2) will be tested in release.cc */
691 }