]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/write.cc
fusefs: multiple fixes related to the write 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/param.h>
33 #include <sys/mman.h>
34 #include <sys/resource.h>
35 #include <sys/stat.h>
36 #include <sys/sysctl.h>
37 #include <sys/time.h>
38 #include <sys/uio.h>
39
40 #include <aio.h>
41 #include <fcntl.h>
42 #include <signal.h>
43 #include <unistd.h>
44 }
45
46 #include "mockfs.hh"
47 #include "utils.hh"
48
49 using namespace testing;
50
51 class Write: public FuseTest {
52
53 public:
54 static sig_atomic_t s_sigxfsz;
55
56 void SetUp() {
57         s_sigxfsz = 0;
58         FuseTest::SetUp();
59 }
60
61 void TearDown() {
62         struct sigaction sa;
63
64         bzero(&sa, sizeof(sa));
65         sa.sa_handler = SIG_DFL;
66         sigaction(SIGXFSZ, &sa, NULL);
67
68         FuseTest::TearDown();
69 }
70
71 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
72 {
73         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
74 }
75
76 void expect_release(uint64_t ino, ProcessMockerT r)
77 {
78         EXPECT_CALL(*m_mock, process(
79                 ResultOf([=](auto in) {
80                         return (in.header.opcode == FUSE_RELEASE &&
81                                 in.header.nodeid == ino);
82                 }, Eq(true)),
83                 _)
84         ).WillRepeatedly(Invoke(r));
85 }
86
87 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
88         uint64_t osize, const void *contents)
89 {
90         FuseTest::expect_write(ino, offset, isize, osize, 0, 0, contents);
91 }
92
93 /* Expect a write that may or may not come, depending on the cache mode */
94 void maybe_expect_write(uint64_t ino, uint64_t offset, uint64_t size,
95         const void *contents)
96 {
97         EXPECT_CALL(*m_mock, process(
98                 ResultOf([=](auto in) {
99                         const char *buf = (const char*)in.body.bytes +
100                                 sizeof(struct fuse_write_in);
101
102                         return (in.header.opcode == FUSE_WRITE &&
103                                 in.header.nodeid == ino &&
104                                 in.body.write.offset == offset  &&
105                                 in.body.write.size == size &&
106                                 0 == bcmp(buf, contents, size));
107                 }, Eq(true)),
108                 _)
109         ).Times(AtMost(1))
110         .WillRepeatedly(Invoke(
111                 ReturnImmediate([=](auto in __unused, auto& out) {
112                         SET_OUT_HEADER_LEN(out, write);
113                         out.body.write.size = size;
114                 })
115         ));
116 }
117
118 };
119
120 class WriteCacheable: public Write {
121 public:
122 virtual void SetUp() {
123         const char *node = "vfs.fusefs.data_cache_mode";
124         int val = 0;
125         size_t size = sizeof(val);
126
127         FuseTest::SetUp();
128
129         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
130                 << strerror(errno);
131         if (val == 0)
132                 GTEST_SKIP() <<
133                         "fusefs data caching must be enabled for this test";
134 }
135 };
136
137 sig_atomic_t Write::s_sigxfsz = 0;
138
139 class Write_7_8: public FuseTest {
140
141 public:
142 virtual void SetUp() {
143         m_kernel_minor_version = 8;
144         FuseTest::SetUp();
145 }
146
147 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
148 {
149         FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
150 }
151
152 };
153
154 class AioWrite: public Write {
155 virtual void SetUp() {
156         const char *node = "vfs.aio.enable_unsafe";
157         int val = 0;
158         size_t size = sizeof(val);
159
160         FuseTest::SetUp();
161
162         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
163                 << strerror(errno);
164         if (!val)
165                 GTEST_SKIP() <<
166                         "vfs.aio.enable_unsafe must be set for this test";
167 }
168 };
169
170 /* Tests for the write-through cache mode */
171 class WriteThrough: public Write {
172 public:
173 virtual void SetUp() {
174         const char *cache_mode_node = "vfs.fusefs.data_cache_mode";
175         int val = 0;
176         size_t size = sizeof(val);
177
178         FuseTest::SetUp();
179         if (IsSkipped())
180                 return;
181
182         ASSERT_EQ(0, sysctlbyname(cache_mode_node, &val, &size, NULL, 0))
183                 << strerror(errno);
184         if (val != 1)
185                 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 1 "
186                         "(writethrough) for this test";
187 }
188
189 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
190         uint64_t osize, const void *contents)
191 {
192         FuseTest::expect_write(ino, offset, isize, osize, 0, FUSE_WRITE_CACHE,
193                 contents);
194 }
195 };
196
197 /* Tests for the writeback cache mode */
198 class WriteBack: public Write {
199 public:
200 virtual void SetUp() {
201         const char *node = "vfs.fusefs.data_cache_mode";
202         int val = 0;
203         size_t size = sizeof(val);
204
205         FuseTest::SetUp();
206         if (IsSkipped())
207                 return;
208
209         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
210                 << strerror(errno);
211         if (val != 2)
212                 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 2 "
213                         "(writeback) for this test";
214 }
215
216 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
217         uint64_t osize, const void *contents)
218 {
219         FuseTest::expect_write(ino, offset, isize, osize, FUSE_WRITE_CACHE, 0,
220                 contents);
221 }
222 };
223
224 class WriteBackAsync: public WriteBack {
225 public:
226 virtual void SetUp() {
227         m_async = true;
228         WriteBack::SetUp();
229 }
230 };
231
232 /* Tests for clustered writes with WriteBack cacheing */
233 class WriteCluster: public WriteBack {
234 public:
235 virtual void SetUp() {
236         if (MAXPHYS < 2 * DFLTPHYS)
237                 GTEST_SKIP() << "MAXPHYS must be at least twice DFLTPHYS"
238                         << "for this test";
239         m_async = true;
240         m_maxwrite = MAXPHYS;
241         WriteBack::SetUp();
242 }
243 };
244
245 void sigxfsz_handler(int __unused sig) {
246         Write::s_sigxfsz = 1;
247 }
248
249 /* AIO writes need to set the header's pid field correctly */
250 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
251 TEST_F(AioWrite, DISABLED_aio_write)
252 {
253         const char FULLPATH[] = "mountpoint/some_file.txt";
254         const char RELPATH[] = "some_file.txt";
255         const char *CONTENTS = "abcdefgh";
256         uint64_t ino = 42;
257         uint64_t offset = 4096;
258         int fd;
259         ssize_t bufsize = strlen(CONTENTS);
260         struct aiocb iocb, *piocb;
261
262         expect_lookup(RELPATH, ino, 0);
263         expect_open(ino, 0, 1);
264         expect_write(ino, offset, bufsize, bufsize, CONTENTS);
265
266         fd = open(FULLPATH, O_WRONLY);
267         EXPECT_LE(0, fd) << strerror(errno);
268
269         iocb.aio_nbytes = bufsize;
270         iocb.aio_fildes = fd;
271         iocb.aio_buf = (void *)CONTENTS;
272         iocb.aio_offset = offset;
273         iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
274         ASSERT_EQ(0, aio_write(&iocb)) << strerror(errno);
275         ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
276         /* Deliberately leak fd.  close(2) will be tested in release.cc */
277 }
278
279 /* 
280  * When a file is opened with O_APPEND, we should forward that flag to
281  * FUSE_OPEN (tested by Open.o_append) but still attempt to calculate the
282  * offset internally.  That way we'll work both with filesystems that
283  * understand O_APPEND (and ignore the offset) and filesystems that don't (and
284  * simply use the offset).
285  *
286  * Note that verifying the O_APPEND flag in FUSE_OPEN is done in the
287  * Open.o_append test.
288  */
289 TEST_F(Write, append)
290 {
291         const ssize_t BUFSIZE = 9;
292         const char FULLPATH[] = "mountpoint/some_file.txt";
293         const char RELPATH[] = "some_file.txt";
294         const char CONTENTS[BUFSIZE] = "abcdefgh";
295         uint64_t ino = 42;
296         /* 
297          * Set offset to a maxbcachebuf boundary so we don't need to RMW when
298          * using writeback caching
299          */
300         uint64_t initial_offset = m_maxbcachebuf;
301         int fd;
302
303         expect_lookup(RELPATH, ino, initial_offset);
304         expect_open(ino, 0, 1);
305         expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, CONTENTS);
306
307         /* Must open O_RDWR or fuse(4) implicitly sets direct_io */
308         fd = open(FULLPATH, O_RDWR | O_APPEND);
309         EXPECT_LE(0, fd) << strerror(errno);
310
311         ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
312         /* Deliberately leak fd.  close(2) will be tested in release.cc */
313 }
314
315 /* If a file is cached, then appending to the end should not cause a read */
316 TEST_F(Write, append_to_cached)
317 {
318         const ssize_t BUFSIZE = 9;
319         const char FULLPATH[] = "mountpoint/some_file.txt";
320         const char RELPATH[] = "some_file.txt";
321         char *oldcontents, *oldbuf;
322         const char CONTENTS[BUFSIZE] = "abcdefgh";
323         uint64_t ino = 42;
324         /* 
325          * Set offset in between maxbcachebuf boundary to test buffer handling
326          */
327         uint64_t oldsize = m_maxbcachebuf / 2;
328         int fd;
329
330         oldcontents = (char*)calloc(1, oldsize);
331         ASSERT_NE(NULL, oldcontents) << strerror(errno);
332         oldbuf = (char*)malloc(oldsize);
333         ASSERT_NE(NULL, oldbuf) << strerror(errno);
334
335         expect_lookup(RELPATH, ino, oldsize);
336         expect_open(ino, 0, 1);
337         expect_read(ino, 0, oldsize, oldsize, oldcontents);
338         maybe_expect_write(ino, oldsize, BUFSIZE, CONTENTS);
339
340         /* Must open O_RDWR or fuse(4) implicitly sets direct_io */
341         fd = open(FULLPATH, O_RDWR | O_APPEND);
342         EXPECT_LE(0, fd) << strerror(errno);
343
344         /* Read the old data into the cache */
345         ASSERT_EQ((ssize_t)oldsize, read(fd, oldbuf, oldsize))
346                 << strerror(errno);
347
348         /* Write the new data.  There should be no more read operations */
349         ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
350         /* Deliberately leak fd.  close(2) will be tested in release.cc */
351 }
352
353 TEST_F(Write, append_direct_io)
354 {
355         const ssize_t BUFSIZE = 9;
356         const char FULLPATH[] = "mountpoint/some_file.txt";
357         const char RELPATH[] = "some_file.txt";
358         const char CONTENTS[BUFSIZE] = "abcdefgh";
359         uint64_t ino = 42;
360         uint64_t initial_offset = 4096;
361         int fd;
362
363         expect_lookup(RELPATH, ino, initial_offset);
364         expect_open(ino, FOPEN_DIRECT_IO, 1);
365         expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, CONTENTS);
366
367         fd = open(FULLPATH, O_WRONLY | O_APPEND);
368         EXPECT_LE(0, fd) << strerror(errno);
369
370         ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
371         /* Deliberately leak fd.  close(2) will be tested in release.cc */
372 }
373
374 /* A direct write should evict any overlapping cached data */
375 TEST_F(Write, direct_io_evicts_cache)
376 {
377         const char FULLPATH[] = "mountpoint/some_file.txt";
378         const char RELPATH[] = "some_file.txt";
379         const char CONTENTS0[] = "abcdefgh";
380         const char CONTENTS1[] = "ijklmnop";
381         uint64_t ino = 42;
382         int fd;
383         ssize_t bufsize = strlen(CONTENTS0) + 1;
384         char readbuf[bufsize];
385
386         expect_lookup(RELPATH, ino, bufsize);
387         expect_open(ino, 0, 1);
388         expect_read(ino, 0, bufsize, bufsize, CONTENTS0);
389         expect_write(ino, 0, bufsize, bufsize, CONTENTS1);
390
391         fd = open(FULLPATH, O_RDWR);
392         EXPECT_LE(0, fd) << strerror(errno);
393
394         // Prime cache
395         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
396
397         // Write directly, evicting cache
398         ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
399         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
400         ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno);
401
402         // Read again.  Cache should be bypassed
403         expect_read(ino, 0, bufsize, bufsize, CONTENTS1);
404         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
405         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
406         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
407         ASSERT_STREQ(readbuf, CONTENTS1);
408
409         /* Deliberately leak fd.  close(2) will be tested in release.cc */
410 }
411
412 /*
413  * If the server doesn't return FOPEN_DIRECT_IO during FUSE_OPEN, then it's not
414  * allowed to return a short write for that file handle.  However, if it does
415  * then we should still do our darndest to handle it by resending the unwritten
416  * portion.
417  */
418 TEST_F(Write, indirect_io_short_write)
419 {
420         const char FULLPATH[] = "mountpoint/some_file.txt";
421         const char RELPATH[] = "some_file.txt";
422         const char *CONTENTS = "abcdefghijklmnop";
423         uint64_t ino = 42;
424         int fd;
425         ssize_t bufsize = strlen(CONTENTS);
426         ssize_t bufsize0 = 11;
427         ssize_t bufsize1 = strlen(CONTENTS) - bufsize0;
428         const char *contents1 = CONTENTS + bufsize0;
429
430         expect_lookup(RELPATH, ino, 0);
431         expect_open(ino, 0, 1);
432         expect_write(ino, 0, bufsize, bufsize0, CONTENTS);
433         expect_write(ino, bufsize0, bufsize1, bufsize1, contents1);
434
435         fd = open(FULLPATH, O_WRONLY);
436         EXPECT_LE(0, fd) << strerror(errno);
437
438         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
439         /* Deliberately leak fd.  close(2) will be tested in release.cc */
440 }
441
442 /* 
443  * When the direct_io option is used, filesystems are allowed to write less
444  * data than requested.  We should return the short write to userland.
445  */
446 TEST_F(Write, direct_io_short_write)
447 {
448         const char FULLPATH[] = "mountpoint/some_file.txt";
449         const char RELPATH[] = "some_file.txt";
450         const char *CONTENTS = "abcdefghijklmnop";
451         uint64_t ino = 42;
452         int fd;
453         ssize_t bufsize = strlen(CONTENTS);
454         ssize_t halfbufsize = bufsize / 2;
455
456         expect_lookup(RELPATH, ino, 0);
457         expect_open(ino, FOPEN_DIRECT_IO, 1);
458         expect_write(ino, 0, bufsize, halfbufsize, CONTENTS);
459
460         fd = open(FULLPATH, O_WRONLY);
461         EXPECT_LE(0, fd) << strerror(errno);
462
463         ASSERT_EQ(halfbufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
464         /* Deliberately leak fd.  close(2) will be tested in release.cc */
465 }
466
467 /*
468  * An insidious edge case: the filesystem returns a short write, and the
469  * difference between what we requested and what it actually wrote crosses an
470  * iov element boundary
471  */
472 TEST_F(Write, direct_io_short_write_iov)
473 {
474         const char FULLPATH[] = "mountpoint/some_file.txt";
475         const char RELPATH[] = "some_file.txt";
476         const char *CONTENTS0 = "abcdefgh";
477         const char *CONTENTS1 = "ijklmnop";
478         const char *EXPECTED0 = "abcdefghijklmnop";
479         uint64_t ino = 42;
480         int fd;
481         ssize_t size0 = strlen(CONTENTS0) - 1;
482         ssize_t size1 = strlen(CONTENTS1) + 1;
483         ssize_t totalsize = size0 + size1;
484         struct iovec iov[2];
485
486         expect_lookup(RELPATH, ino, 0);
487         expect_open(ino, FOPEN_DIRECT_IO, 1);
488         expect_write(ino, 0, totalsize, size0, EXPECTED0);
489
490         fd = open(FULLPATH, O_WRONLY);
491         EXPECT_LE(0, fd) << strerror(errno);
492
493         iov[0].iov_base = (void*)CONTENTS0;
494         iov[0].iov_len = strlen(CONTENTS0);
495         iov[1].iov_base = (void*)CONTENTS1;
496         iov[1].iov_len = strlen(CONTENTS1);
497         ASSERT_EQ(size0, writev(fd, iov, 2)) << strerror(errno);
498         /* Deliberately leak fd.  close(2) will be tested in release.cc */
499 }
500
501 /* fusefs should respect RLIMIT_FSIZE */
502 TEST_F(Write, rlimit_fsize)
503 {
504         const char FULLPATH[] = "mountpoint/some_file.txt";
505         const char RELPATH[] = "some_file.txt";
506         const char *CONTENTS = "abcdefgh";
507         struct rlimit rl;
508         ssize_t bufsize = strlen(CONTENTS);
509         off_t offset = 1'000'000'000;
510         uint64_t ino = 42;
511         int fd;
512
513         expect_lookup(RELPATH, ino, 0);
514         expect_open(ino, 0, 1);
515
516         rl.rlim_cur = offset;
517         rl.rlim_max = 10 * offset;
518         ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
519         ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
520
521         fd = open(FULLPATH, O_WRONLY);
522
523         EXPECT_LE(0, fd) << strerror(errno);
524
525         ASSERT_EQ(-1, pwrite(fd, CONTENTS, bufsize, offset));
526         EXPECT_EQ(EFBIG, errno);
527         EXPECT_EQ(1, s_sigxfsz);
528         /* Deliberately leak fd.  close(2) will be tested in release.cc */
529 }
530
531 /*
532  * If the kernel cannot be sure which uid, gid, or pid was responsible for a
533  * write, then it must set the FUSE_WRITE_CACHE bit
534  */
535 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */
536 TEST_F(WriteCacheable, mmap)
537 {
538         const char FULLPATH[] = "mountpoint/some_file.txt";
539         const char RELPATH[] = "some_file.txt";
540         const char *CONTENTS = "abcdefgh";
541         uint64_t ino = 42;
542         int fd;
543         ssize_t bufsize = strlen(CONTENTS);
544         void *p;
545         uint64_t offset = 10;
546         size_t len;
547         void *zeros, *expected;
548
549         len = getpagesize();
550
551         zeros = calloc(1, len);
552         ASSERT_NE(NULL, zeros);
553         expected = calloc(1, len);
554         ASSERT_NE(NULL, expected);
555         memmove((uint8_t*)expected + offset, CONTENTS, bufsize);
556
557         expect_lookup(RELPATH, ino, len);
558         expect_open(ino, 0, 1);
559         expect_read(ino, 0, len, len, zeros);
560         /* 
561          * Writes from the pager may or may not be associated with the correct
562          * pid, so they must set FUSE_WRITE_CACHE.
563          */
564         FuseTest::expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, 0, expected);
565         expect_flush(ino, 1, ReturnErrno(0));
566         expect_release(ino, ReturnErrno(0));
567
568         fd = open(FULLPATH, O_RDWR);
569         EXPECT_LE(0, fd) << strerror(errno);
570
571         p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
572         ASSERT_NE(MAP_FAILED, p) << strerror(errno);
573
574         memmove((uint8_t*)p + offset, CONTENTS, bufsize);
575
576         ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
577         close(fd);      // Write mmap'd data on close
578
579         free(expected);
580         free(zeros);
581 }
582
583 TEST_F(WriteThrough, pwrite)
584 {
585         const char FULLPATH[] = "mountpoint/some_file.txt";
586         const char RELPATH[] = "some_file.txt";
587         const char *CONTENTS = "abcdefgh";
588         uint64_t ino = 42;
589         uint64_t offset = 65536;
590         int fd;
591         ssize_t bufsize = strlen(CONTENTS);
592
593         expect_lookup(RELPATH, ino, 0);
594         expect_open(ino, 0, 1);
595         expect_write(ino, offset, bufsize, bufsize, CONTENTS);
596
597         fd = open(FULLPATH, O_WRONLY);
598         EXPECT_LE(0, fd) << strerror(errno);
599
600         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
601                 << strerror(errno);
602         /* Deliberately leak fd.  close(2) will be tested in release.cc */
603 }
604
605 TEST_F(Write, write)
606 {
607         const char FULLPATH[] = "mountpoint/some_file.txt";
608         const char RELPATH[] = "some_file.txt";
609         const char *CONTENTS = "abcdefgh";
610         uint64_t ino = 42;
611         int fd;
612         ssize_t bufsize = strlen(CONTENTS);
613
614         expect_lookup(RELPATH, ino, 0);
615         expect_open(ino, 0, 1);
616         expect_write(ino, 0, bufsize, bufsize, CONTENTS);
617
618         fd = open(FULLPATH, O_WRONLY);
619         EXPECT_LE(0, fd) << strerror(errno);
620
621         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
622         /* Deliberately leak fd.  close(2) will be tested in release.cc */
623 }
624
625 /* fuse(4) should not issue writes of greater size than the daemon requests */
626 TEST_F(Write, write_large)
627 {
628         const char FULLPATH[] = "mountpoint/some_file.txt";
629         const char RELPATH[] = "some_file.txt";
630         int *contents;
631         uint64_t ino = 42;
632         int fd;
633         ssize_t halfbufsize, bufsize;
634
635         halfbufsize = m_mock->m_maxwrite;
636         bufsize = halfbufsize * 2;
637         contents = (int*)malloc(bufsize);
638         ASSERT_NE(NULL, contents);
639         for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) {
640                 contents[i] = i;
641         }
642
643         expect_lookup(RELPATH, ino, 0);
644         expect_open(ino, 0, 1);
645         expect_write(ino, 0, halfbufsize, halfbufsize, contents);
646         maybe_expect_write(ino, halfbufsize, halfbufsize,
647                 &contents[halfbufsize / sizeof(int)]);
648
649         fd = open(FULLPATH, O_WRONLY);
650         EXPECT_LE(0, fd) << strerror(errno);
651
652         ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno);
653         /* Deliberately leak fd.  close(2) will be tested in release.cc */
654
655         free(contents);
656 }
657
658 TEST_F(Write, write_nothing)
659 {
660         const char FULLPATH[] = "mountpoint/some_file.txt";
661         const char RELPATH[] = "some_file.txt";
662         const char *CONTENTS = "";
663         uint64_t ino = 42;
664         int fd;
665         ssize_t bufsize = 0;
666
667         expect_lookup(RELPATH, ino, 0);
668         expect_open(ino, 0, 1);
669
670         fd = open(FULLPATH, O_WRONLY);
671         EXPECT_LE(0, fd) << strerror(errno);
672
673         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
674         /* Deliberately leak fd.  close(2) will be tested in release.cc */
675 }
676
677 TEST_F(Write_7_8, write)
678 {
679         const char FULLPATH[] = "mountpoint/some_file.txt";
680         const char RELPATH[] = "some_file.txt";
681         const char *CONTENTS = "abcdefgh";
682         uint64_t ino = 42;
683         int fd;
684         ssize_t bufsize = strlen(CONTENTS);
685
686         expect_lookup(RELPATH, ino, 0);
687         expect_open(ino, 0, 1);
688         expect_write_7_8(ino, 0, bufsize, bufsize, CONTENTS);
689
690         fd = open(FULLPATH, O_WRONLY);
691         EXPECT_LE(0, fd) << strerror(errno);
692
693         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
694         /* Deliberately leak fd.  close(2) will be tested in release.cc */
695 }
696
697 /* In writeback mode, dirty data should be written on close */
698 TEST_F(WriteBackAsync, close)
699 {
700         const char FULLPATH[] = "mountpoint/some_file.txt";
701         const char RELPATH[] = "some_file.txt";
702         const char *CONTENTS = "abcdefgh";
703         uint64_t ino = 42;
704         int fd;
705         ssize_t bufsize = strlen(CONTENTS);
706
707         expect_lookup(RELPATH, ino, 0);
708         expect_open(ino, 0, 1);
709         expect_write(ino, 0, bufsize, bufsize, CONTENTS);
710         EXPECT_CALL(*m_mock, process(
711                 ResultOf([=](auto in) {
712                         return (in.header.opcode == FUSE_SETATTR);
713                 }, Eq(true)),
714                 _)
715         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
716                 SET_OUT_HEADER_LEN(out, attr);
717                 out.body.attr.attr.ino = ino;   // Must match nodeid
718         })));
719         expect_flush(ino, 1, ReturnErrno(0));
720         expect_release(ino, ReturnErrno(0));
721
722         fd = open(FULLPATH, O_RDWR);
723         ASSERT_LE(0, fd) << strerror(errno);
724
725         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
726         close(fd);
727 }
728
729 /* In writeback mode, adjacent writes will be clustered together */
730 TEST_F(WriteCluster, clustering)
731 {
732         const char FULLPATH[] = "mountpoint/some_file.txt";
733         const char RELPATH[] = "some_file.txt";
734         uint64_t ino = 42;
735         int i, fd;
736         void *wbuf, *wbuf2x;
737         ssize_t bufsize = 65536;
738         off_t filesize = 327680;
739
740         wbuf = malloc(bufsize);
741         ASSERT_NE(NULL, wbuf) << strerror(errno);
742         memset(wbuf, 'X', bufsize);
743         wbuf2x = malloc(2 * bufsize);
744         ASSERT_NE(NULL, wbuf2x) << strerror(errno);
745         memset(wbuf2x, 'X', 2 * bufsize);
746
747         expect_lookup(RELPATH, ino, filesize);
748         expect_open(ino, 0, 1);
749         /*
750          * Writes of bufsize-bytes each should be clustered into greater sizes.
751          * The amount of clustering is adaptive, so the first write actually
752          * issued will be 2x bufsize and subsequent writes may be larger
753          */
754         expect_write(ino, 0, 2 * bufsize, 2 * bufsize, wbuf2x);
755         expect_write(ino, 2 * bufsize, 2 * bufsize, 2 * bufsize, wbuf2x);
756         expect_flush(ino, 1, ReturnErrno(0));
757         expect_release(ino, ReturnErrno(0));
758
759         fd = open(FULLPATH, O_RDWR);
760         ASSERT_LE(0, fd) << strerror(errno);
761
762         for (i = 0; i < 4; i++) {
763                 ASSERT_EQ(bufsize, write(fd, wbuf, bufsize))
764                         << strerror(errno);
765         }
766         close(fd);
767 }
768
769 /* 
770  * When clustering writes, an I/O error to any of the cluster's children should
771  * not panic the system on unmount
772  */
773 /*
774  * Disabled because it panics.
775  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238565
776  */
777 TEST_F(WriteCluster, DISABLED_cluster_write_err)
778 {
779         const char FULLPATH[] = "mountpoint/some_file.txt";
780         const char RELPATH[] = "some_file.txt";
781         uint64_t ino = 42;
782         int i, fd;
783         void *wbuf;
784         ssize_t bufsize = 65536;
785         off_t filesize = 262144;
786
787         wbuf = malloc(bufsize);
788         ASSERT_NE(NULL, wbuf) << strerror(errno);
789         memset(wbuf, 'X', bufsize);
790
791         expect_lookup(RELPATH, ino, filesize);
792         expect_open(ino, 0, 1);
793         EXPECT_CALL(*m_mock, process(
794                 ResultOf([=](auto in) {
795                         return (in.header.opcode == FUSE_WRITE);
796                 }, Eq(true)),
797                 _)
798         ).WillRepeatedly(Invoke(ReturnErrno(EIO)));
799         expect_flush(ino, 1, ReturnErrno(0));
800         expect_release(ino, ReturnErrno(0));
801
802         fd = open(FULLPATH, O_RDWR);
803         ASSERT_LE(0, fd) << strerror(errno);
804
805         for (i = 0; i < 3; i++) {
806                 ASSERT_EQ(bufsize, write(fd, wbuf, bufsize))
807                         << strerror(errno);
808         }
809         close(fd);
810 }
811
812 /*
813  * In writeback mode, writes to an O_WRONLY file could trigger reads from the
814  * server.  The FUSE protocol explicitly allows that.
815  */
816 TEST_F(WriteBack, rmw)
817 {
818         const char FULLPATH[] = "mountpoint/some_file.txt";
819         const char RELPATH[] = "some_file.txt";
820         const char *CONTENTS = "abcdefgh";
821         const char *INITIAL   = "XXXXXXXXXX";
822         uint64_t ino = 42;
823         uint64_t offset = 1;
824         off_t fsize = 10;
825         int fd;
826         ssize_t bufsize = strlen(CONTENTS);
827
828         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
829         expect_open(ino, 0, 1);
830         expect_read(ino, 0, fsize, fsize, INITIAL, O_WRONLY);
831         maybe_expect_write(ino, offset, bufsize, CONTENTS);
832
833         fd = open(FULLPATH, O_WRONLY);
834         EXPECT_LE(0, fd) << strerror(errno);
835
836         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
837                 << strerror(errno);
838         /* Deliberately leak fd.  close(2) will be tested in release.cc */
839 }
840
841 /*
842  * Without direct_io, writes should be committed to cache
843  */
844 TEST_F(WriteBack, cache)
845 {
846         const char FULLPATH[] = "mountpoint/some_file.txt";
847         const char RELPATH[] = "some_file.txt";
848         const char *CONTENTS = "abcdefgh";
849         uint64_t ino = 42;
850         int fd;
851         ssize_t bufsize = strlen(CONTENTS);
852         char readbuf[bufsize];
853
854         expect_lookup(RELPATH, ino, 0);
855         expect_open(ino, 0, 1);
856         expect_write(ino, 0, bufsize, bufsize, CONTENTS);
857
858         fd = open(FULLPATH, O_RDWR);
859         EXPECT_LE(0, fd) << strerror(errno);
860
861         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
862         /* 
863          * A subsequent read should be serviced by cache, without querying the
864          * filesystem daemon
865          */
866         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
867         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
868         /* Deliberately leak fd.  close(2) will be tested in release.cc */
869 }
870
871 /*
872  * With O_DIRECT, writes should be not committed to cache.  Admittedly this is
873  * an odd test, because it would be unusual to use O_DIRECT for writes but not
874  * reads.
875  */
876 TEST_F(WriteBack, o_direct)
877 {
878         const char FULLPATH[] = "mountpoint/some_file.txt";
879         const char RELPATH[] = "some_file.txt";
880         const char *CONTENTS = "abcdefgh";
881         uint64_t ino = 42;
882         int fd;
883         ssize_t bufsize = strlen(CONTENTS);
884         char readbuf[bufsize];
885
886         expect_lookup(RELPATH, ino, 0);
887         expect_open(ino, 0, 1);
888         FuseTest::expect_write(ino, 0, bufsize, bufsize, 0, FUSE_WRITE_CACHE,
889                 CONTENTS);
890         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
891
892         fd = open(FULLPATH, O_RDWR | O_DIRECT);
893         EXPECT_LE(0, fd) << strerror(errno);
894
895         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
896         /* A subsequent read must query the daemon because cache is empty */
897         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
898         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
899         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
900         /* Deliberately leak fd.  close(2) will be tested in release.cc */
901 }
902
903 /*
904  * When mounted with -o async, the writeback cache mode should delay writes
905  */
906 TEST_F(WriteBackAsync, delay)
907 {
908         const char FULLPATH[] = "mountpoint/some_file.txt";
909         const char RELPATH[] = "some_file.txt";
910         const char *CONTENTS = "abcdefgh";
911         uint64_t ino = 42;
912         int fd;
913         ssize_t bufsize = strlen(CONTENTS);
914
915         expect_lookup(RELPATH, ino, 0);
916         expect_open(ino, 0, 1);
917         /* Write should be cached, but FUSE_WRITE shouldn't be sent */
918         EXPECT_CALL(*m_mock, process(
919                 ResultOf([=](auto in) {
920                         return (in.header.opcode == FUSE_WRITE);
921                 }, Eq(true)),
922                 _)
923         ).Times(0);
924
925         fd = open(FULLPATH, O_RDWR);
926         EXPECT_LE(0, fd) << strerror(errno);
927
928         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
929
930         /* Don't close the file because that would flush the cache */
931 }
932
933 /*
934  * Without direct_io, writes should be committed to cache
935  */
936 TEST_F(WriteThrough, writethrough)
937 {
938         const char FULLPATH[] = "mountpoint/some_file.txt";
939         const char RELPATH[] = "some_file.txt";
940         const char *CONTENTS = "abcdefgh";
941         uint64_t ino = 42;
942         int fd;
943         ssize_t bufsize = strlen(CONTENTS);
944         char readbuf[bufsize];
945
946         expect_lookup(RELPATH, ino, 0);
947         expect_open(ino, 0, 1);
948         expect_write(ino, 0, bufsize, bufsize, CONTENTS);
949
950         fd = open(FULLPATH, O_RDWR);
951         EXPECT_LE(0, fd) << strerror(errno);
952
953         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
954         /*
955          * A subsequent read should be serviced by cache, without querying the
956          * filesystem daemon
957          */
958         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
959         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
960         /* Deliberately leak fd.  close(2) will be tested in release.cc */
961 }
962
963 /* With writethrough caching, writes update the cached file size */
964 TEST_F(WriteThrough, update_file_size)
965 {
966         const char FULLPATH[] = "mountpoint/some_file.txt";
967         const char RELPATH[] = "some_file.txt";
968         const char *CONTENTS = "abcdefgh";
969         struct stat sb;
970         uint64_t ino = 42;
971         int fd;
972         ssize_t bufsize = strlen(CONTENTS);
973
974         expect_lookup(RELPATH, ino, 0);
975         expect_open(ino, 0, 1);
976         expect_write(ino, 0, bufsize, bufsize, CONTENTS);
977
978         fd = open(FULLPATH, O_RDWR);
979         EXPECT_LE(0, fd) << strerror(errno);
980
981         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
982         /* Get cached attributes */
983         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
984         ASSERT_EQ(bufsize, sb.st_size);
985         /* Deliberately leak fd.  close(2) will be tested in release.cc */
986 }