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