]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/write.cc
MFHead @345353
[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_release(ino, ReturnErrno(0));
378
379         fd = open(FULLPATH, O_RDWR);
380         EXPECT_LE(0, fd) << strerror(errno);
381
382         p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
383         ASSERT_NE(MAP_FAILED, p) << strerror(errno);
384
385         memmove((uint8_t*)p + offset, CONTENTS, bufsize);
386
387         ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
388         close(fd);      // Write mmap'd data on close
389
390         free(expected);
391         free(zeros);
392 }
393
394 TEST_F(Write, pwrite)
395 {
396         const char FULLPATH[] = "mountpoint/some_file.txt";
397         const char RELPATH[] = "some_file.txt";
398         const char *CONTENTS = "abcdefgh";
399         uint64_t ino = 42;
400         uint64_t offset = 4096;
401         int fd;
402         ssize_t bufsize = strlen(CONTENTS);
403
404         expect_lookup(RELPATH, ino, 0);
405         expect_open(ino, 0, 1);
406         expect_getattr(ino, 0);
407         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
408
409         fd = open(FULLPATH, O_WRONLY);
410         EXPECT_LE(0, fd) << strerror(errno);
411
412         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
413                 << strerror(errno);
414         /* Deliberately leak fd.  close(2) will be tested in release.cc */
415 }
416
417 TEST_F(Write, write)
418 {
419         const char FULLPATH[] = "mountpoint/some_file.txt";
420         const char RELPATH[] = "some_file.txt";
421         const char *CONTENTS = "abcdefgh";
422         uint64_t ino = 42;
423         int fd;
424         ssize_t bufsize = strlen(CONTENTS);
425
426         expect_lookup(RELPATH, ino, 0);
427         expect_open(ino, 0, 1);
428         expect_getattr(ino, 0);
429         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
430
431         fd = open(FULLPATH, O_WRONLY);
432         EXPECT_LE(0, fd) << strerror(errno);
433
434         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
435         /* Deliberately leak fd.  close(2) will be tested in release.cc */
436 }
437
438 /* fuse(4) should not issue writes of greater size than the daemon requests */
439 TEST_F(Write, write_large)
440 {
441         const char FULLPATH[] = "mountpoint/some_file.txt";
442         const char RELPATH[] = "some_file.txt";
443         int *contents;
444         uint64_t ino = 42;
445         int fd;
446         ssize_t halfbufsize, bufsize;
447
448         halfbufsize = m_mock->m_max_write;
449         bufsize = halfbufsize * 2;
450         contents = (int*)malloc(bufsize);
451         ASSERT_NE(NULL, contents);
452         for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) {
453                 contents[i] = i;
454         }
455
456         expect_lookup(RELPATH, ino, 0);
457         expect_open(ino, 0, 1);
458         expect_getattr(ino, 0);
459         expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents);
460         expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0,
461                 &contents[halfbufsize / sizeof(int)]);
462
463         fd = open(FULLPATH, O_WRONLY);
464         EXPECT_LE(0, fd) << strerror(errno);
465
466         ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno);
467         /* Deliberately leak fd.  close(2) will be tested in release.cc */
468
469         free(contents);
470 }
471
472 TEST_F(Write, write_nothing)
473 {
474         const char FULLPATH[] = "mountpoint/some_file.txt";
475         const char RELPATH[] = "some_file.txt";
476         const char *CONTENTS = "";
477         uint64_t ino = 42;
478         int fd;
479         ssize_t bufsize = 0;
480
481         expect_lookup(RELPATH, ino, 0);
482         expect_open(ino, 0, 1);
483         expect_getattr(ino, 0);
484
485         fd = open(FULLPATH, O_WRONLY);
486         EXPECT_LE(0, fd) << strerror(errno);
487
488         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
489         /* Deliberately leak fd.  close(2) will be tested in release.cc */
490 }
491
492 /* In writeback mode, dirty data should be written on close */
493 TEST_F(WriteBack, close)
494 {
495         const char FULLPATH[] = "mountpoint/some_file.txt";
496         const char RELPATH[] = "some_file.txt";
497         const char *CONTENTS = "abcdefgh";
498         uint64_t ino = 42;
499         int fd;
500         ssize_t bufsize = strlen(CONTENTS);
501
502         expect_lookup(RELPATH, ino, 0);
503         expect_open(ino, 0, 1);
504         expect_getattr(ino, 0);
505         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
506         EXPECT_CALL(*m_mock, process(
507                 ResultOf([=](auto in) {
508                         return (in->header.opcode == FUSE_SETATTR);
509                 }, Eq(true)),
510                 _)
511         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
512                 SET_OUT_HEADER_LEN(out, attr);
513                 out->body.attr.attr.ino = ino;  // Must match nodeid
514         })));
515         expect_release(ino, ReturnErrno(0));
516
517         fd = open(FULLPATH, O_RDWR);
518         ASSERT_LE(0, fd) << strerror(errno);
519
520         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
521         close(fd);
522 }
523
524 /*
525  * Without direct_io, writes should be committed to cache
526  */
527 TEST_F(WriteBack, writeback)
528 {
529         const char FULLPATH[] = "mountpoint/some_file.txt";
530         const char RELPATH[] = "some_file.txt";
531         const char *CONTENTS = "abcdefgh";
532         uint64_t ino = 42;
533         int fd;
534         ssize_t bufsize = strlen(CONTENTS);
535         char readbuf[bufsize];
536
537         expect_lookup(RELPATH, ino, 0);
538         expect_open(ino, 0, 1);
539         expect_getattr(ino, 0);
540         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
541
542         fd = open(FULLPATH, O_RDWR);
543         EXPECT_LE(0, fd) << strerror(errno);
544
545         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
546         /* 
547          * A subsequent read should be serviced by cache, without querying the
548          * filesystem daemon
549          */
550         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
551         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
552         /* Deliberately leak fd.  close(2) will be tested in release.cc */
553 }
554
555 /*
556  * With O_DIRECT, writes should be not committed to cache.  Admittedly this is
557  * an odd test, because it would be unusual to use O_DIRECT for writes but not
558  * reads.
559  */
560 TEST_F(WriteBack, o_direct)
561 {
562         const char FULLPATH[] = "mountpoint/some_file.txt";
563         const char RELPATH[] = "some_file.txt";
564         const char *CONTENTS = "abcdefgh";
565         uint64_t ino = 42;
566         int fd;
567         ssize_t bufsize = strlen(CONTENTS);
568         char readbuf[bufsize];
569
570         expect_lookup(RELPATH, ino, 0);
571         expect_open(ino, 0, 1);
572         expect_getattr(ino, 0);
573         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
574         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
575
576         fd = open(FULLPATH, O_RDWR | O_DIRECT);
577         EXPECT_LE(0, fd) << strerror(errno);
578
579         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
580         /* A subsequent read must query the daemon because cache is empty */
581         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
582         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << 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  * Without direct_io, writes should be committed to cache
589  */
590 /* 
591  * Disabled because we don't yet implement write-through caching.  No bugzilla
592  * entry, because that's a feature request, not a bug.
593  */
594 TEST_F(WriteThrough, DISABLED_writethrough)
595 {
596         const char FULLPATH[] = "mountpoint/some_file.txt";
597         const char RELPATH[] = "some_file.txt";
598         const char *CONTENTS = "abcdefgh";
599         uint64_t ino = 42;
600         int fd;
601         ssize_t bufsize = strlen(CONTENTS);
602         char readbuf[bufsize];
603
604         expect_lookup(RELPATH, ino, 0);
605         expect_open(ino, 0, 1);
606         expect_getattr(ino, 0);
607         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
608
609         fd = open(FULLPATH, O_RDWR);
610         EXPECT_LE(0, fd) << strerror(errno);
611
612         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
613         /* 
614          * A subsequent read should be serviced by cache, without querying the
615          * filesystem daemon
616          */
617         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
618         /* Deliberately leak fd.  close(2) will be tested in release.cc */
619 }
620
621 /* With writethrough caching, writes update the cached file size */
622 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
623 TEST_F(WriteThrough, DISABLED_update_file_size)
624 {
625         const char FULLPATH[] = "mountpoint/some_file.txt";
626         const char RELPATH[] = "some_file.txt";
627         const char *CONTENTS = "abcdefgh";
628         struct stat sb;
629         uint64_t ino = 42;
630         int fd;
631         ssize_t bufsize = strlen(CONTENTS);
632
633         expect_lookup(RELPATH, ino, 0);
634         expect_open(ino, 0, 1);
635         EXPECT_CALL(*m_mock, process(
636                 ResultOf([=](auto in) {
637                         return (in->header.opcode == FUSE_GETATTR &&
638                                 in->header.nodeid == ino);
639                 }, Eq(true)),
640                 _)
641         ).Times(2)
642         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
643                 SET_OUT_HEADER_LEN(out, attr);
644                 out->body.attr.attr.ino = ino;  // Must match nodeid
645                 out->body.attr.attr.mode = S_IFREG | 0644;
646                 out->body.attr.attr.size = 0;
647                 out->body.attr.attr_valid = UINT64_MAX;
648         })));
649         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
650
651         fd = open(FULLPATH, O_RDWR);
652         EXPECT_LE(0, fd) << strerror(errno);
653
654         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
655         /* Get cached attributes */
656         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
657         ASSERT_EQ(bufsize, sb.st_size);
658         /* Deliberately leak fd.  close(2) will be tested in release.cc */
659 }