]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/write.cc
fusefs: cache file attributes
[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 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235774 */
232 TEST_F(Write, DISABLED_direct_io_evicts_cache)
233 {
234         const char FULLPATH[] = "mountpoint/some_file.txt";
235         const char RELPATH[] = "some_file.txt";
236         const char CONTENTS0[] = "abcdefgh";
237         const char CONTENTS1[] = "ijklmnop";
238         uint64_t ino = 42;
239         int fd;
240         ssize_t bufsize = strlen(CONTENTS0) + 1;
241         char readbuf[bufsize];
242
243         expect_lookup(RELPATH, ino, bufsize);
244         expect_open(ino, 0, 1);
245         expect_read(ino, 0, bufsize, bufsize, CONTENTS0);
246         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS1);
247
248         fd = open(FULLPATH, O_RDWR);
249         EXPECT_LE(0, fd) << strerror(errno);
250
251         // Prime cache
252         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
253
254         // Write directly, evicting cache
255         ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
256         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
257         ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno);
258
259         // Read again.  Cache should be bypassed
260         expect_read(ino, 0, bufsize, bufsize, CONTENTS1);
261         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
262         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
263         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
264         ASSERT_STREQ(readbuf, CONTENTS1);
265
266         /* Deliberately leak fd.  close(2) will be tested in release.cc */
267 }
268
269 /*
270  * If the server doesn't return FOPEN_DIRECT_IO during FUSE_OPEN, then it's not
271  * allowed to return a short write for that file handle.  However, if it does
272  * then we should still do our darndest to handle it by resending the unwritten
273  * portion.
274  */
275 TEST_F(Write, indirect_io_short_write)
276 {
277         const char FULLPATH[] = "mountpoint/some_file.txt";
278         const char RELPATH[] = "some_file.txt";
279         const char *CONTENTS = "abcdefghijklmnop";
280         uint64_t ino = 42;
281         int fd;
282         ssize_t bufsize = strlen(CONTENTS);
283         ssize_t bufsize0 = 11;
284         ssize_t bufsize1 = strlen(CONTENTS) - bufsize0;
285         const char *contents1 = CONTENTS + bufsize0;
286
287         expect_lookup(RELPATH, ino, 0);
288         expect_open(ino, 0, 1);
289         expect_write(ino, 0, bufsize, bufsize0, 0, CONTENTS);
290         expect_write(ino, bufsize0, bufsize1, bufsize1, 0,
291                 contents1);
292
293         fd = open(FULLPATH, O_WRONLY);
294         EXPECT_LE(0, fd) << strerror(errno);
295
296         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
297         /* Deliberately leak fd.  close(2) will be tested in release.cc */
298 }
299
300 /* 
301  * When the direct_io option is used, filesystems are allowed to write less
302  * data than requested.  We should return the short write to userland.
303  */
304 TEST_F(Write, direct_io_short_write)
305 {
306         const char FULLPATH[] = "mountpoint/some_file.txt";
307         const char RELPATH[] = "some_file.txt";
308         const char *CONTENTS = "abcdefghijklmnop";
309         uint64_t ino = 42;
310         int fd;
311         ssize_t bufsize = strlen(CONTENTS);
312         ssize_t halfbufsize = bufsize / 2;
313
314         expect_lookup(RELPATH, ino, 0);
315         expect_open(ino, FOPEN_DIRECT_IO, 1);
316         expect_write(ino, 0, bufsize, halfbufsize, 0, CONTENTS);
317
318         fd = open(FULLPATH, O_WRONLY);
319         EXPECT_LE(0, fd) << strerror(errno);
320
321         ASSERT_EQ(halfbufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
322         /* Deliberately leak fd.  close(2) will be tested in release.cc */
323 }
324
325 /*
326  * An insidious edge case: the filesystem returns a short write, and the
327  * difference between what we requested and what it actually wrote crosses an
328  * iov element boundary
329  */
330 TEST_F(Write, direct_io_short_write_iov)
331 {
332         const char FULLPATH[] = "mountpoint/some_file.txt";
333         const char RELPATH[] = "some_file.txt";
334         const char *CONTENTS0 = "abcdefgh";
335         const char *CONTENTS1 = "ijklmnop";
336         const char *EXPECTED0 = "abcdefghijklmnop";
337         uint64_t ino = 42;
338         int fd;
339         ssize_t size0 = strlen(CONTENTS0) - 1;
340         ssize_t size1 = strlen(CONTENTS1) + 1;
341         ssize_t totalsize = size0 + size1;
342         struct iovec iov[2];
343
344         expect_lookup(RELPATH, ino, 0);
345         expect_open(ino, FOPEN_DIRECT_IO, 1);
346         expect_write(ino, 0, totalsize, size0, 0, EXPECTED0);
347
348         fd = open(FULLPATH, O_WRONLY);
349         EXPECT_LE(0, fd) << strerror(errno);
350
351         iov[0].iov_base = (void*)CONTENTS0;
352         iov[0].iov_len = strlen(CONTENTS0);
353         iov[1].iov_base = (void*)CONTENTS1;
354         iov[1].iov_len = strlen(CONTENTS1);
355         ASSERT_EQ(size0, writev(fd, iov, 2)) << strerror(errno);
356         /* Deliberately leak fd.  close(2) will be tested in release.cc */
357 }
358
359 /*
360  * If the kernel cannot be sure which uid, gid, or pid was responsible for a
361  * write, then it must set the FUSE_WRITE_CACHE bit
362  */
363 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */
364 // TODO: check vfs.fusefs.mmap_enable
365 TEST_F(Write, DISABLED_mmap)
366 {
367         const char FULLPATH[] = "mountpoint/some_file.txt";
368         const char RELPATH[] = "some_file.txt";
369         const char *CONTENTS = "abcdefgh";
370         uint64_t ino = 42;
371         int fd;
372         ssize_t bufsize = strlen(CONTENTS);
373         void *p;
374         uint64_t offset = 10;
375         size_t len;
376         void *zeros, *expected;
377
378         len = getpagesize();
379
380         zeros = calloc(1, len);
381         ASSERT_NE(NULL, zeros);
382         expected = calloc(1, len);
383         ASSERT_NE(NULL, expected);
384         memmove((uint8_t*)expected + offset, CONTENTS, bufsize);
385
386         expect_lookup(RELPATH, ino, len);
387         expect_open(ino, 0, 1);
388         expect_read(ino, 0, len, len, zeros);
389         /* 
390          * Writes from the pager may or may not be associated with the correct
391          * pid, so they must set FUSE_WRITE_CACHE
392          */
393         expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, 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 TEST_F(WriteThrough, pwrite)
413 {
414         const char FULLPATH[] = "mountpoint/some_file.txt";
415         const char RELPATH[] = "some_file.txt";
416         const char *CONTENTS = "abcdefgh";
417         uint64_t ino = 42;
418         uint64_t offset = 4096;
419         int fd;
420         ssize_t bufsize = strlen(CONTENTS);
421
422         expect_lookup(RELPATH, ino, 0);
423         expect_open(ino, 0, 1);
424         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
425
426         fd = open(FULLPATH, O_WRONLY);
427         EXPECT_LE(0, fd) << strerror(errno);
428
429         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
430                 << strerror(errno);
431         /* Deliberately leak fd.  close(2) will be tested in release.cc */
432 }
433
434 TEST_F(Write, write)
435 {
436         const char FULLPATH[] = "mountpoint/some_file.txt";
437         const char RELPATH[] = "some_file.txt";
438         const char *CONTENTS = "abcdefgh";
439         uint64_t ino = 42;
440         int fd;
441         ssize_t bufsize = strlen(CONTENTS);
442
443         expect_lookup(RELPATH, ino, 0);
444         expect_open(ino, 0, 1);
445         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
446
447         fd = open(FULLPATH, O_WRONLY);
448         EXPECT_LE(0, fd) << strerror(errno);
449
450         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
451         /* Deliberately leak fd.  close(2) will be tested in release.cc */
452 }
453
454 /* fuse(4) should not issue writes of greater size than the daemon requests */
455 TEST_F(Write, write_large)
456 {
457         const char FULLPATH[] = "mountpoint/some_file.txt";
458         const char RELPATH[] = "some_file.txt";
459         int *contents;
460         uint64_t ino = 42;
461         int fd;
462         ssize_t halfbufsize, bufsize;
463
464         halfbufsize = m_mock->m_max_write;
465         bufsize = halfbufsize * 2;
466         contents = (int*)malloc(bufsize);
467         ASSERT_NE(NULL, contents);
468         for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) {
469                 contents[i] = i;
470         }
471
472         expect_lookup(RELPATH, ino, 0);
473         expect_open(ino, 0, 1);
474         expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents);
475         expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0,
476                 &contents[halfbufsize / sizeof(int)]);
477
478         fd = open(FULLPATH, O_WRONLY);
479         EXPECT_LE(0, fd) << strerror(errno);
480
481         ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno);
482         /* Deliberately leak fd.  close(2) will be tested in release.cc */
483
484         free(contents);
485 }
486
487 TEST_F(Write, write_nothing)
488 {
489         const char FULLPATH[] = "mountpoint/some_file.txt";
490         const char RELPATH[] = "some_file.txt";
491         const char *CONTENTS = "";
492         uint64_t ino = 42;
493         int fd;
494         ssize_t bufsize = 0;
495
496         expect_lookup(RELPATH, ino, 0);
497         expect_open(ino, 0, 1);
498
499         fd = open(FULLPATH, O_WRONLY);
500         EXPECT_LE(0, fd) << strerror(errno);
501
502         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
503         /* Deliberately leak fd.  close(2) will be tested in release.cc */
504 }
505
506 /* In writeback mode, dirty data should be written on close */
507 TEST_F(WriteBack, close)
508 {
509         const char FULLPATH[] = "mountpoint/some_file.txt";
510         const char RELPATH[] = "some_file.txt";
511         const char *CONTENTS = "abcdefgh";
512         uint64_t ino = 42;
513         int fd;
514         ssize_t bufsize = strlen(CONTENTS);
515
516         expect_lookup(RELPATH, ino, 0);
517         expect_open(ino, 0, 1);
518         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
519         EXPECT_CALL(*m_mock, process(
520                 ResultOf([=](auto in) {
521                         return (in->header.opcode == FUSE_SETATTR);
522                 }, Eq(true)),
523                 _)
524         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
525                 SET_OUT_HEADER_LEN(out, attr);
526                 out->body.attr.attr.ino = ino;  // Must match nodeid
527         })));
528         expect_flush(ino, 1, ReturnErrno(0));
529         expect_release(ino, ReturnErrno(0));
530
531         fd = open(FULLPATH, O_RDWR);
532         ASSERT_LE(0, fd) << strerror(errno);
533
534         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
535         close(fd);
536 }
537
538 /*
539  * In writeback mode, writes to an O_WRONLY file could trigger reads from the
540  * server.  The FUSE protocol explicitly allows that.
541  */
542 TEST_F(WriteBack, rmw)
543 {
544         const char FULLPATH[] = "mountpoint/some_file.txt";
545         const char RELPATH[] = "some_file.txt";
546         const char *CONTENTS = "abcdefgh";
547         const char *INITIAL   = "XXXXXXXXXX";
548         uint64_t ino = 42;
549         uint64_t offset = 1;
550         off_t fsize = 10;
551         int fd;
552         ssize_t bufsize = strlen(CONTENTS);
553
554         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
555         expect_open(ino, 0, 1);
556         expect_read(ino, 0, fsize, fsize, INITIAL);
557         expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS);
558
559         fd = open(FULLPATH, O_WRONLY);
560         EXPECT_LE(0, fd) << strerror(errno);
561
562         ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
563                 << strerror(errno);
564         /* Deliberately leak fd.  close(2) will be tested in release.cc */
565 }
566
567 /*
568  * Without direct_io, writes should be committed to cache
569  */
570 TEST_F(WriteBack, writeback)
571 {
572         const char FULLPATH[] = "mountpoint/some_file.txt";
573         const char RELPATH[] = "some_file.txt";
574         const char *CONTENTS = "abcdefgh";
575         uint64_t ino = 42;
576         int fd;
577         ssize_t bufsize = strlen(CONTENTS);
578         char readbuf[bufsize];
579
580         expect_lookup(RELPATH, ino, 0);
581         expect_open(ino, 0, 1);
582         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
583
584         fd = open(FULLPATH, O_RDWR);
585         EXPECT_LE(0, fd) << strerror(errno);
586
587         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
588         /* 
589          * A subsequent read should be serviced by cache, without querying the
590          * filesystem daemon
591          */
592         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
593         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
594         /* Deliberately leak fd.  close(2) will be tested in release.cc */
595 }
596
597 /*
598  * With O_DIRECT, writes should be not committed to cache.  Admittedly this is
599  * an odd test, because it would be unusual to use O_DIRECT for writes but not
600  * reads.
601  */
602 TEST_F(WriteBack, o_direct)
603 {
604         const char FULLPATH[] = "mountpoint/some_file.txt";
605         const char RELPATH[] = "some_file.txt";
606         const char *CONTENTS = "abcdefgh";
607         uint64_t ino = 42;
608         int fd;
609         ssize_t bufsize = strlen(CONTENTS);
610         char readbuf[bufsize];
611
612         expect_lookup(RELPATH, ino, 0);
613         expect_open(ino, 0, 1);
614         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
615         expect_read(ino, 0, bufsize, bufsize, CONTENTS);
616
617         fd = open(FULLPATH, O_RDWR | O_DIRECT);
618         EXPECT_LE(0, fd) << strerror(errno);
619
620         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
621         /* A subsequent read must query the daemon because cache is empty */
622         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
623         ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
624         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
625         /* Deliberately leak fd.  close(2) will be tested in release.cc */
626 }
627
628 /*
629  * Without direct_io, writes should be committed to cache
630  */
631 /* 
632  * Disabled because we don't yet implement write-through caching.  No bugzilla
633  * entry, because that's a feature request, not a bug.
634  */
635 TEST_F(WriteThrough, DISABLED_writethrough)
636 {
637         const char FULLPATH[] = "mountpoint/some_file.txt";
638         const char RELPATH[] = "some_file.txt";
639         const char *CONTENTS = "abcdefgh";
640         uint64_t ino = 42;
641         int fd;
642         ssize_t bufsize = strlen(CONTENTS);
643         char readbuf[bufsize];
644
645         expect_lookup(RELPATH, ino, 0);
646         expect_open(ino, 0, 1);
647         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
648
649         fd = open(FULLPATH, O_RDWR);
650         EXPECT_LE(0, fd) << strerror(errno);
651
652         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
653         /* 
654          * A subsequent read should be serviced by cache, without querying the
655          * filesystem daemon
656          */
657         ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
658         /* Deliberately leak fd.  close(2) will be tested in release.cc */
659 }
660
661 /* With writethrough caching, writes update the cached file size */
662 TEST_F(WriteThrough, update_file_size)
663 {
664         const char FULLPATH[] = "mountpoint/some_file.txt";
665         const char RELPATH[] = "some_file.txt";
666         const char *CONTENTS = "abcdefgh";
667         struct stat sb;
668         uint64_t ino = 42;
669         int fd;
670         ssize_t bufsize = strlen(CONTENTS);
671
672         expect_lookup(RELPATH, ino, 0);
673         expect_open(ino, 0, 1);
674         expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS);
675
676         fd = open(FULLPATH, O_RDWR);
677         EXPECT_LE(0, fd) << strerror(errno);
678
679         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
680         /* Get cached attributes */
681         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
682         ASSERT_EQ(bufsize, sb.st_size);
683         /* Deliberately leak fd.  close(2) will be tested in release.cc */
684 }