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