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