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