2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/param.h>
34 #include <sys/resource.h>
36 #include <sys/sysctl.h>
49 using namespace testing;
51 class Write: public FuseTest {
54 static sig_atomic_t s_sigxfsz;
64 bzero(&sa, sizeof(sa));
65 sa.sa_handler = SIG_DFL;
66 sigaction(SIGXFSZ, &sa, NULL);
71 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
73 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1);
76 void expect_release(uint64_t ino, ProcessMockerT r)
78 EXPECT_CALL(*m_mock, process(
79 ResultOf([=](auto in) {
80 return (in.header.opcode == FUSE_RELEASE &&
81 in.header.nodeid == ino);
84 ).WillRepeatedly(Invoke(r));
87 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
88 uint64_t osize, const void *contents)
90 FuseTest::expect_write(ino, offset, isize, osize, 0, 0, contents);
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,
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);
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));
110 .WillRepeatedly(Invoke(
111 ReturnImmediate([=](auto in __unused, auto& out) {
112 SET_OUT_HEADER_LEN(out, write);
113 out.body.write.size = size;
120 sig_atomic_t Write::s_sigxfsz = 0;
122 class Write_7_8: public FuseTest {
125 virtual void SetUp() {
126 m_kernel_minor_version = 8;
130 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
132 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
137 class AioWrite: public Write {
138 virtual void SetUp() {
139 const char *node = "vfs.aio.enable_unsafe";
141 size_t size = sizeof(val);
145 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
149 "vfs.aio.enable_unsafe must be set for this test";
153 /* Tests for the writeback cache mode */
154 class WriteBack: public Write {
156 virtual void SetUp() {
157 m_init_flags |= FUSE_WRITEBACK_CACHE;
163 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
164 uint64_t osize, const void *contents)
166 FuseTest::expect_write(ino, offset, isize, osize, FUSE_WRITE_CACHE, 0,
171 class WriteBackAsync: public WriteBack {
173 virtual void SetUp() {
179 class TimeGran: public WriteBackAsync, public WithParamInterface<unsigned> {
181 virtual void SetUp() {
182 m_time_gran = 1 << GetParam();
183 WriteBackAsync::SetUp();
187 /* Tests for clustered writes with WriteBack cacheing */
188 class WriteCluster: public WriteBack {
190 virtual void SetUp() {
192 m_maxwrite = m_maxphys;
194 if (m_maxphys < 2 * DFLTPHYS)
195 GTEST_SKIP() << "MAXPHYS must be at least twice DFLTPHYS"
197 if (m_maxphys < 2 * m_maxbcachebuf)
198 GTEST_SKIP() << "MAXPHYS must be at least twice maxbcachebuf"
203 void sigxfsz_handler(int __unused sig) {
204 Write::s_sigxfsz = 1;
207 /* AIO writes need to set the header's pid field correctly */
208 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
209 TEST_F(AioWrite, DISABLED_aio_write)
211 const char FULLPATH[] = "mountpoint/some_file.txt";
212 const char RELPATH[] = "some_file.txt";
213 const char *CONTENTS = "abcdefgh";
215 uint64_t offset = 4096;
217 ssize_t bufsize = strlen(CONTENTS);
218 struct aiocb iocb, *piocb;
220 expect_lookup(RELPATH, ino, 0);
221 expect_open(ino, 0, 1);
222 expect_write(ino, offset, bufsize, bufsize, CONTENTS);
224 fd = open(FULLPATH, O_WRONLY);
225 EXPECT_LE(0, fd) << strerror(errno);
227 iocb.aio_nbytes = bufsize;
228 iocb.aio_fildes = fd;
229 iocb.aio_buf = __DECONST(void *, CONTENTS);
230 iocb.aio_offset = offset;
231 iocb.aio_sigevent.sigev_notify = SIGEV_NONE;
232 ASSERT_EQ(0, aio_write(&iocb)) << strerror(errno);
233 ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
238 * When a file is opened with O_APPEND, we should forward that flag to
239 * FUSE_OPEN (tested by Open.o_append) but still attempt to calculate the
240 * offset internally. That way we'll work both with filesystems that
241 * understand O_APPEND (and ignore the offset) and filesystems that don't (and
242 * simply use the offset).
244 * Note that verifying the O_APPEND flag in FUSE_OPEN is done in the
245 * Open.o_append test.
247 TEST_F(Write, append)
249 const ssize_t BUFSIZE = 9;
250 const char FULLPATH[] = "mountpoint/some_file.txt";
251 const char RELPATH[] = "some_file.txt";
252 const char CONTENTS[BUFSIZE] = "abcdefgh";
255 * Set offset to a maxbcachebuf boundary so we don't need to RMW when
256 * using writeback caching
258 uint64_t initial_offset = m_maxbcachebuf;
261 expect_lookup(RELPATH, ino, initial_offset);
262 expect_open(ino, 0, 1);
263 expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, CONTENTS);
265 /* Must open O_RDWR or fuse(4) implicitly sets direct_io */
266 fd = open(FULLPATH, O_RDWR | O_APPEND);
267 EXPECT_LE(0, fd) << strerror(errno);
269 ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
273 /* If a file is cached, then appending to the end should not cause a read */
274 TEST_F(Write, append_to_cached)
276 const ssize_t BUFSIZE = 9;
277 const char FULLPATH[] = "mountpoint/some_file.txt";
278 const char RELPATH[] = "some_file.txt";
279 char *oldcontents, *oldbuf;
280 const char CONTENTS[BUFSIZE] = "abcdefgh";
283 * Set offset in between maxbcachebuf boundary to test buffer handling
285 uint64_t oldsize = m_maxbcachebuf / 2;
288 oldcontents = (char*)calloc(1, oldsize);
289 ASSERT_NE(nullptr, oldcontents) << strerror(errno);
290 oldbuf = (char*)malloc(oldsize);
291 ASSERT_NE(nullptr, oldbuf) << strerror(errno);
293 expect_lookup(RELPATH, ino, oldsize);
294 expect_open(ino, 0, 1);
295 expect_read(ino, 0, oldsize, oldsize, oldcontents);
296 maybe_expect_write(ino, oldsize, BUFSIZE, CONTENTS);
298 /* Must open O_RDWR or fuse(4) implicitly sets direct_io */
299 fd = open(FULLPATH, O_RDWR | O_APPEND);
300 EXPECT_LE(0, fd) << strerror(errno);
302 /* Read the old data into the cache */
303 ASSERT_EQ((ssize_t)oldsize, read(fd, oldbuf, oldsize))
306 /* Write the new data. There should be no more read operations */
307 ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
311 TEST_F(Write, append_direct_io)
313 const ssize_t BUFSIZE = 9;
314 const char FULLPATH[] = "mountpoint/some_file.txt";
315 const char RELPATH[] = "some_file.txt";
316 const char CONTENTS[BUFSIZE] = "abcdefgh";
318 uint64_t initial_offset = 4096;
321 expect_lookup(RELPATH, ino, initial_offset);
322 expect_open(ino, FOPEN_DIRECT_IO, 1);
323 expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, CONTENTS);
325 fd = open(FULLPATH, O_WRONLY | O_APPEND);
326 EXPECT_LE(0, fd) << strerror(errno);
328 ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno);
332 /* A direct write should evict any overlapping cached data */
333 TEST_F(Write, direct_io_evicts_cache)
335 const char FULLPATH[] = "mountpoint/some_file.txt";
336 const char RELPATH[] = "some_file.txt";
337 const char CONTENTS0[] = "abcdefgh";
338 const char CONTENTS1[] = "ijklmnop";
341 ssize_t bufsize = strlen(CONTENTS0) + 1;
342 char readbuf[bufsize];
344 expect_lookup(RELPATH, ino, bufsize);
345 expect_open(ino, 0, 1);
346 expect_read(ino, 0, bufsize, bufsize, CONTENTS0);
347 expect_write(ino, 0, bufsize, bufsize, CONTENTS1);
349 fd = open(FULLPATH, O_RDWR);
350 EXPECT_LE(0, fd) << strerror(errno);
353 ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
355 // Write directly, evicting cache
356 ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno);
357 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
358 ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno);
360 // Read again. Cache should be bypassed
361 expect_read(ino, 0, bufsize, bufsize, CONTENTS1);
362 ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
363 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
364 ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
365 ASSERT_STREQ(readbuf, CONTENTS1);
371 * If the server doesn't return FOPEN_DIRECT_IO during FUSE_OPEN, then it's not
372 * allowed to return a short write for that file handle. However, if it does
373 * then we should still do our darndest to handle it by resending the unwritten
376 TEST_F(Write, indirect_io_short_write)
378 const char FULLPATH[] = "mountpoint/some_file.txt";
379 const char RELPATH[] = "some_file.txt";
380 const char *CONTENTS = "abcdefghijklmnop";
383 ssize_t bufsize = strlen(CONTENTS);
384 ssize_t bufsize0 = 11;
385 ssize_t bufsize1 = strlen(CONTENTS) - bufsize0;
386 const char *contents1 = CONTENTS + bufsize0;
388 expect_lookup(RELPATH, ino, 0);
389 expect_open(ino, 0, 1);
390 expect_write(ino, 0, bufsize, bufsize0, CONTENTS);
391 expect_write(ino, bufsize0, bufsize1, bufsize1, contents1);
393 fd = open(FULLPATH, O_WRONLY);
394 EXPECT_LE(0, fd) << strerror(errno);
396 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
401 * When the direct_io option is used, filesystems are allowed to write less
402 * data than requested. We should return the short write to userland.
404 TEST_F(Write, direct_io_short_write)
406 const char FULLPATH[] = "mountpoint/some_file.txt";
407 const char RELPATH[] = "some_file.txt";
408 const char *CONTENTS = "abcdefghijklmnop";
411 ssize_t bufsize = strlen(CONTENTS);
412 ssize_t halfbufsize = bufsize / 2;
414 expect_lookup(RELPATH, ino, 0);
415 expect_open(ino, FOPEN_DIRECT_IO, 1);
416 expect_write(ino, 0, bufsize, halfbufsize, CONTENTS);
418 fd = open(FULLPATH, O_WRONLY);
419 EXPECT_LE(0, fd) << strerror(errno);
421 ASSERT_EQ(halfbufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
426 * An insidious edge case: the filesystem returns a short write, and the
427 * difference between what we requested and what it actually wrote crosses an
428 * iov element boundary
430 TEST_F(Write, direct_io_short_write_iov)
432 const char FULLPATH[] = "mountpoint/some_file.txt";
433 const char RELPATH[] = "some_file.txt";
434 const char *CONTENTS0 = "abcdefgh";
435 const char *CONTENTS1 = "ijklmnop";
436 const char *EXPECTED0 = "abcdefghijklmnop";
439 ssize_t size0 = strlen(CONTENTS0) - 1;
440 ssize_t size1 = strlen(CONTENTS1) + 1;
441 ssize_t totalsize = size0 + size1;
444 expect_lookup(RELPATH, ino, 0);
445 expect_open(ino, FOPEN_DIRECT_IO, 1);
446 expect_write(ino, 0, totalsize, size0, EXPECTED0);
448 fd = open(FULLPATH, O_WRONLY);
449 EXPECT_LE(0, fd) << strerror(errno);
451 iov[0].iov_base = __DECONST(void*, CONTENTS0);
452 iov[0].iov_len = strlen(CONTENTS0);
453 iov[1].iov_base = __DECONST(void*, CONTENTS1);
454 iov[1].iov_len = strlen(CONTENTS1);
455 ASSERT_EQ(size0, writev(fd, iov, 2)) << strerror(errno);
459 /* fusefs should respect RLIMIT_FSIZE */
460 TEST_F(Write, rlimit_fsize)
462 const char FULLPATH[] = "mountpoint/some_file.txt";
463 const char RELPATH[] = "some_file.txt";
464 const char *CONTENTS = "abcdefgh";
466 ssize_t bufsize = strlen(CONTENTS);
467 off_t offset = 1'000'000'000;
471 expect_lookup(RELPATH, ino, 0);
472 expect_open(ino, 0, 1);
474 rl.rlim_cur = offset;
475 rl.rlim_max = 10 * offset;
476 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
477 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
479 fd = open(FULLPATH, O_WRONLY);
481 EXPECT_LE(0, fd) << strerror(errno);
483 ASSERT_EQ(-1, pwrite(fd, CONTENTS, bufsize, offset));
484 EXPECT_EQ(EFBIG, errno);
485 EXPECT_EQ(1, s_sigxfsz);
490 * A short read indicates EOF. Test that nothing bad happens if we get EOF
491 * during the R of a RMW operation.
493 TEST_F(Write, eof_during_rmw)
495 const char FULLPATH[] = "mountpoint/some_file.txt";
496 const char RELPATH[] = "some_file.txt";
497 const char *CONTENTS = "abcdefgh";
498 const char *INITIAL = "XXXXXXXXXX";
501 ssize_t bufsize = strlen(CONTENTS);
502 off_t orig_fsize = 10;
503 off_t truncated_fsize = 5;
504 off_t final_fsize = bufsize;
507 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, orig_fsize, 1);
508 expect_open(ino, 0, 1);
509 expect_read(ino, 0, orig_fsize, truncated_fsize, INITIAL, O_RDWR);
510 expect_getattr(ino, truncated_fsize);
511 expect_read(ino, 0, final_fsize, final_fsize, INITIAL, O_RDWR);
512 maybe_expect_write(ino, offset, bufsize, CONTENTS);
514 fd = open(FULLPATH, O_RDWR);
515 EXPECT_LE(0, fd) << strerror(errno);
517 ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
523 * If the kernel cannot be sure which uid, gid, or pid was responsible for a
524 * write, then it must set the FUSE_WRITE_CACHE bit
526 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */
529 const char FULLPATH[] = "mountpoint/some_file.txt";
530 const char RELPATH[] = "some_file.txt";
531 const char *CONTENTS = "abcdefgh";
534 ssize_t bufsize = strlen(CONTENTS);
536 uint64_t offset = 10;
538 void *zeros, *expected;
542 zeros = calloc(1, len);
543 ASSERT_NE(nullptr, zeros);
544 expected = calloc(1, len);
545 ASSERT_NE(nullptr, expected);
546 memmove((uint8_t*)expected + offset, CONTENTS, bufsize);
548 expect_lookup(RELPATH, ino, len);
549 expect_open(ino, 0, 1);
550 expect_read(ino, 0, len, len, zeros);
552 * Writes from the pager may or may not be associated with the correct
553 * pid, so they must set FUSE_WRITE_CACHE.
555 FuseTest::expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, 0, expected);
556 expect_flush(ino, 1, ReturnErrno(0));
557 expect_release(ino, ReturnErrno(0));
559 fd = open(FULLPATH, O_RDWR);
560 EXPECT_LE(0, fd) << strerror(errno);
562 p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
563 ASSERT_NE(MAP_FAILED, p) << strerror(errno);
565 memmove((uint8_t*)p + offset, CONTENTS, bufsize);
567 ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
568 close(fd); // Write mmap'd data on close
574 TEST_F(Write, pwrite)
576 const char FULLPATH[] = "mountpoint/some_file.txt";
577 const char RELPATH[] = "some_file.txt";
578 const char *CONTENTS = "abcdefgh";
580 uint64_t offset = m_maxbcachebuf;
582 ssize_t bufsize = strlen(CONTENTS);
584 expect_lookup(RELPATH, ino, 0);
585 expect_open(ino, 0, 1);
586 expect_write(ino, offset, bufsize, bufsize, CONTENTS);
588 fd = open(FULLPATH, O_WRONLY);
589 EXPECT_LE(0, fd) << strerror(errno);
591 ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
596 /* Writing a file should update its cached mtime and ctime */
597 TEST_F(Write, timestamps)
599 const char FULLPATH[] = "mountpoint/some_file.txt";
600 const char RELPATH[] = "some_file.txt";
601 const char *CONTENTS = "abcdefgh";
602 ssize_t bufsize = strlen(CONTENTS);
604 struct stat sb0, sb1;
607 expect_lookup(RELPATH, ino, 0);
608 expect_open(ino, 0, 1);
609 maybe_expect_write(ino, 0, bufsize, CONTENTS);
611 fd = open(FULLPATH, O_RDWR);
612 EXPECT_LE(0, fd) << strerror(errno);
613 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
614 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
618 ASSERT_EQ(0, fstat(fd, &sb1)) << strerror(errno);
620 EXPECT_EQ(sb0.st_atime, sb1.st_atime);
621 EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
622 EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
627 const char FULLPATH[] = "mountpoint/some_file.txt";
628 const char RELPATH[] = "some_file.txt";
629 const char *CONTENTS = "abcdefgh";
632 ssize_t bufsize = strlen(CONTENTS);
634 expect_lookup(RELPATH, ino, 0);
635 expect_open(ino, 0, 1);
636 expect_write(ino, 0, bufsize, bufsize, CONTENTS);
638 fd = open(FULLPATH, O_WRONLY);
639 EXPECT_LE(0, fd) << strerror(errno);
641 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
645 /* fuse(4) should not issue writes of greater size than the daemon requests */
646 TEST_F(Write, write_large)
648 const char FULLPATH[] = "mountpoint/some_file.txt";
649 const char RELPATH[] = "some_file.txt";
653 ssize_t halfbufsize, bufsize;
655 halfbufsize = m_mock->m_maxwrite;
656 bufsize = halfbufsize * 2;
657 contents = (int*)malloc(bufsize);
658 ASSERT_NE(nullptr, contents);
659 for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) {
663 expect_lookup(RELPATH, ino, 0);
664 expect_open(ino, 0, 1);
665 maybe_expect_write(ino, 0, halfbufsize, contents);
666 maybe_expect_write(ino, halfbufsize, halfbufsize,
667 &contents[halfbufsize / sizeof(int)]);
669 fd = open(FULLPATH, O_WRONLY);
670 EXPECT_LE(0, fd) << strerror(errno);
672 ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno);
678 TEST_F(Write, write_nothing)
680 const char FULLPATH[] = "mountpoint/some_file.txt";
681 const char RELPATH[] = "some_file.txt";
682 const char *CONTENTS = "";
687 expect_lookup(RELPATH, ino, 0);
688 expect_open(ino, 0, 1);
690 fd = open(FULLPATH, O_WRONLY);
691 EXPECT_LE(0, fd) << strerror(errno);
693 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
697 TEST_F(Write_7_8, write)
699 const char FULLPATH[] = "mountpoint/some_file.txt";
700 const char RELPATH[] = "some_file.txt";
701 const char *CONTENTS = "abcdefgh";
704 ssize_t bufsize = strlen(CONTENTS);
706 expect_lookup(RELPATH, ino, 0);
707 expect_open(ino, 0, 1);
708 expect_write_7_8(ino, 0, bufsize, bufsize, CONTENTS);
710 fd = open(FULLPATH, O_WRONLY);
711 EXPECT_LE(0, fd) << strerror(errno);
713 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
717 /* In writeback mode, dirty data should be written on close */
718 TEST_F(WriteBackAsync, close)
720 const char FULLPATH[] = "mountpoint/some_file.txt";
721 const char RELPATH[] = "some_file.txt";
722 const char *CONTENTS = "abcdefgh";
725 ssize_t bufsize = strlen(CONTENTS);
727 expect_lookup(RELPATH, ino, 0);
728 expect_open(ino, 0, 1);
729 expect_write(ino, 0, bufsize, bufsize, CONTENTS);
730 EXPECT_CALL(*m_mock, process(
731 ResultOf([=](auto in) {
732 return (in.header.opcode == FUSE_SETATTR);
735 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
736 SET_OUT_HEADER_LEN(out, attr);
737 out.body.attr.attr.ino = ino; // Must match nodeid
739 expect_flush(ino, 1, ReturnErrno(0));
740 expect_release(ino, ReturnErrno(0));
742 fd = open(FULLPATH, O_RDWR);
743 ASSERT_LE(0, fd) << strerror(errno);
745 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
749 /* In writeback mode, adjacent writes will be clustered together */
750 TEST_F(WriteCluster, clustering)
752 const char FULLPATH[] = "mountpoint/some_file.txt";
753 const char RELPATH[] = "some_file.txt";
757 ssize_t bufsize = m_maxbcachebuf;
758 off_t filesize = 5 * bufsize;
760 wbuf = malloc(bufsize);
761 ASSERT_NE(nullptr, wbuf) << strerror(errno);
762 memset(wbuf, 'X', bufsize);
763 wbuf2x = malloc(2 * bufsize);
764 ASSERT_NE(nullptr, wbuf2x) << strerror(errno);
765 memset(wbuf2x, 'X', 2 * bufsize);
767 expect_lookup(RELPATH, ino, filesize);
768 expect_open(ino, 0, 1);
770 * Writes of bufsize-bytes each should be clustered into greater sizes.
771 * The amount of clustering is adaptive, so the first write actually
772 * issued will be 2x bufsize and subsequent writes may be larger
774 expect_write(ino, 0, 2 * bufsize, 2 * bufsize, wbuf2x);
775 expect_write(ino, 2 * bufsize, 2 * bufsize, 2 * bufsize, wbuf2x);
776 expect_flush(ino, 1, ReturnErrno(0));
777 expect_release(ino, ReturnErrno(0));
779 fd = open(FULLPATH, O_RDWR);
780 ASSERT_LE(0, fd) << strerror(errno);
782 for (i = 0; i < 4; i++) {
783 ASSERT_EQ(bufsize, write(fd, wbuf, bufsize))
790 * When clustering writes, an I/O error to any of the cluster's children should
791 * not panic the system on unmount
794 * Disabled because it panics.
795 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238565
797 TEST_F(WriteCluster, DISABLED_cluster_write_err)
799 const char FULLPATH[] = "mountpoint/some_file.txt";
800 const char RELPATH[] = "some_file.txt";
804 ssize_t bufsize = m_maxbcachebuf;
805 off_t filesize = 4 * bufsize;
807 wbuf = malloc(bufsize);
808 ASSERT_NE(nullptr, wbuf) << strerror(errno);
809 memset(wbuf, 'X', bufsize);
811 expect_lookup(RELPATH, ino, filesize);
812 expect_open(ino, 0, 1);
813 EXPECT_CALL(*m_mock, process(
814 ResultOf([=](auto in) {
815 return (in.header.opcode == FUSE_WRITE);
818 ).WillRepeatedly(Invoke(ReturnErrno(EIO)));
819 expect_flush(ino, 1, ReturnErrno(0));
820 expect_release(ino, ReturnErrno(0));
822 fd = open(FULLPATH, O_RDWR);
823 ASSERT_LE(0, fd) << strerror(errno);
825 for (i = 0; i < 3; i++) {
826 ASSERT_EQ(bufsize, write(fd, wbuf, bufsize))
833 * In writeback mode, writes to an O_WRONLY file could trigger reads from the
834 * server. The FUSE protocol explicitly allows that.
836 TEST_F(WriteBack, rmw)
838 const char FULLPATH[] = "mountpoint/some_file.txt";
839 const char RELPATH[] = "some_file.txt";
840 const char *CONTENTS = "abcdefgh";
841 const char *INITIAL = "XXXXXXXXXX";
846 ssize_t bufsize = strlen(CONTENTS);
848 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
849 expect_open(ino, 0, 1);
850 expect_read(ino, 0, fsize, fsize, INITIAL, O_WRONLY);
851 maybe_expect_write(ino, offset, bufsize, CONTENTS);
853 fd = open(FULLPATH, O_WRONLY);
854 EXPECT_LE(0, fd) << strerror(errno);
856 ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset))
862 * Without direct_io, writes should be committed to cache
864 TEST_F(WriteBack, cache)
866 const char FULLPATH[] = "mountpoint/some_file.txt";
867 const char RELPATH[] = "some_file.txt";
868 const char *CONTENTS = "abcdefgh";
871 ssize_t bufsize = strlen(CONTENTS);
872 char readbuf[bufsize];
874 expect_lookup(RELPATH, ino, 0);
875 expect_open(ino, 0, 1);
876 expect_write(ino, 0, bufsize, bufsize, CONTENTS);
878 fd = open(FULLPATH, O_RDWR);
879 EXPECT_LE(0, fd) << strerror(errno);
881 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
883 * A subsequent read should be serviced by cache, without querying the
886 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
887 ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
892 * With O_DIRECT, writes should be not committed to cache. Admittedly this is
893 * an odd test, because it would be unusual to use O_DIRECT for writes but not
896 TEST_F(WriteBack, o_direct)
898 const char FULLPATH[] = "mountpoint/some_file.txt";
899 const char RELPATH[] = "some_file.txt";
900 const char *CONTENTS = "abcdefgh";
903 ssize_t bufsize = strlen(CONTENTS);
904 char readbuf[bufsize];
906 expect_lookup(RELPATH, ino, 0);
907 expect_open(ino, 0, 1);
908 FuseTest::expect_write(ino, 0, bufsize, bufsize, 0, FUSE_WRITE_CACHE,
910 expect_read(ino, 0, bufsize, bufsize, CONTENTS);
912 fd = open(FULLPATH, O_RDWR | O_DIRECT);
913 EXPECT_LE(0, fd) << strerror(errno);
915 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
916 /* A subsequent read must query the daemon because cache is empty */
917 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
918 ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno);
919 ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
924 * When mounted with -o async, the writeback cache mode should delay writes
926 TEST_F(WriteBackAsync, delay)
928 const char FULLPATH[] = "mountpoint/some_file.txt";
929 const char RELPATH[] = "some_file.txt";
930 const char *CONTENTS = "abcdefgh";
933 ssize_t bufsize = strlen(CONTENTS);
935 expect_lookup(RELPATH, ino, 0);
936 expect_open(ino, 0, 1);
937 /* Write should be cached, but FUSE_WRITE shouldn't be sent */
938 EXPECT_CALL(*m_mock, process(
939 ResultOf([=](auto in) {
940 return (in.header.opcode == FUSE_WRITE);
945 fd = open(FULLPATH, O_RDWR);
946 EXPECT_LE(0, fd) << strerror(errno);
948 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
950 /* Don't close the file because that would flush the cache */
954 * In WriteBack mode, writes may be cached beyond what the server thinks is the
955 * EOF. In this case, a short read at EOF should _not_ cause fusefs to update
958 TEST_F(WriteBackAsync, eof)
960 const char FULLPATH[] = "mountpoint/some_file.txt";
961 const char RELPATH[] = "some_file.txt";
962 const char *CONTENTS0 = "abcdefgh";
963 const char *CONTENTS1 = "ijklmnop";
966 off_t offset = m_maxbcachebuf;
967 ssize_t wbufsize = strlen(CONTENTS1);
968 off_t old_filesize = (off_t)strlen(CONTENTS0);
969 ssize_t rbufsize = 2 * old_filesize;
970 char readbuf[rbufsize];
971 size_t holesize = rbufsize - old_filesize;
976 expect_lookup(RELPATH, ino, 0);
977 expect_open(ino, 0, 1);
978 expect_read(ino, 0, m_maxbcachebuf, old_filesize, CONTENTS0);
980 fd = open(FULLPATH, O_RDWR);
981 EXPECT_LE(0, fd) << strerror(errno);
983 /* Write and cache data beyond EOF */
984 ASSERT_EQ(wbufsize, pwrite(fd, CONTENTS1, wbufsize, offset))
987 /* Read from the old EOF */
988 r = pread(fd, readbuf, rbufsize, 0);
989 ASSERT_LE(0, r) << strerror(errno);
990 EXPECT_EQ(rbufsize, r) << "read should've synthesized a hole";
991 EXPECT_EQ(0, memcmp(CONTENTS0, readbuf, old_filesize));
992 bzero(hole, holesize);
993 EXPECT_EQ(0, memcmp(hole, readbuf + old_filesize, holesize));
995 /* The file's size should still be what was established by pwrite */
996 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
997 EXPECT_EQ(offset + wbufsize, sb.st_size);
1002 * When a file has dirty writes that haven't been flushed, the server's notion
1003 * of its mtime and ctime will be wrong. The kernel should ignore those if it
1004 * gets them from a FUSE_GETATTR before flushing.
1006 TEST_F(WriteBackAsync, timestamps)
1008 const char FULLPATH[] = "mountpoint/some_file.txt";
1009 const char RELPATH[] = "some_file.txt";
1010 const char *CONTENTS = "abcdefgh";
1011 ssize_t bufsize = strlen(CONTENTS);
1013 uint64_t attr_valid = 0;
1014 uint64_t attr_valid_nsec = 0;
1015 uint64_t server_time = 12345;
1016 mode_t mode = S_IFREG | 0644;
1021 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1022 .WillRepeatedly(Invoke(
1023 ReturnImmediate([=](auto in __unused, auto& out) {
1024 SET_OUT_HEADER_LEN(out, entry);
1025 out.body.entry.attr.mode = mode;
1026 out.body.entry.nodeid = ino;
1027 out.body.entry.attr.nlink = 1;
1028 out.body.entry.attr_valid = attr_valid;
1029 out.body.entry.attr_valid_nsec = attr_valid_nsec;
1031 expect_open(ino, 0, 1);
1032 EXPECT_CALL(*m_mock, process(
1033 ResultOf([=](auto in) {
1034 return (in.header.opcode == FUSE_GETATTR &&
1035 in.header.nodeid == ino);
1038 ).WillRepeatedly(Invoke(
1039 ReturnImmediate([=](auto i __unused, auto& out) {
1040 SET_OUT_HEADER_LEN(out, attr);
1041 out.body.attr.attr.ino = ino;
1042 out.body.attr.attr.mode = mode;
1043 out.body.attr.attr_valid = attr_valid;
1044 out.body.attr.attr_valid_nsec = attr_valid_nsec;
1045 out.body.attr.attr.atime = server_time;
1046 out.body.attr.attr.mtime = server_time;
1047 out.body.attr.attr.ctime = server_time;
1050 fd = open(FULLPATH, O_RDWR);
1051 EXPECT_LE(0, fd) << strerror(errno);
1052 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1054 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1055 EXPECT_EQ((time_t)server_time, sb.st_atime);
1056 EXPECT_NE((time_t)server_time, sb.st_mtime);
1057 EXPECT_NE((time_t)server_time, sb.st_ctime);
1060 /* Any dirty timestamp fields should be flushed during a SETATTR */
1061 TEST_F(WriteBackAsync, timestamps_during_setattr)
1063 const char FULLPATH[] = "mountpoint/some_file.txt";
1064 const char RELPATH[] = "some_file.txt";
1065 const char *CONTENTS = "abcdefgh";
1066 ssize_t bufsize = strlen(CONTENTS);
1068 const mode_t newmode = 0755;
1071 expect_lookup(RELPATH, ino, 0);
1072 expect_open(ino, 0, 1);
1073 EXPECT_CALL(*m_mock, process(
1074 ResultOf([=](auto in) {
1075 uint32_t valid = FATTR_MODE | FATTR_MTIME | FATTR_CTIME;
1076 return (in.header.opcode == FUSE_SETATTR &&
1077 in.header.nodeid == ino &&
1078 in.body.setattr.valid == valid);
1081 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1082 SET_OUT_HEADER_LEN(out, attr);
1083 out.body.attr.attr.ino = ino;
1084 out.body.attr.attr.mode = S_IFREG | newmode;
1087 fd = open(FULLPATH, O_RDWR);
1088 EXPECT_LE(0, fd) << strerror(errno);
1089 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1090 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
1093 /* fuse_init_out.time_gran controls the granularity of timestamps */
1094 TEST_P(TimeGran, timestamps_during_setattr)
1096 const char FULLPATH[] = "mountpoint/some_file.txt";
1097 const char RELPATH[] = "some_file.txt";
1098 const char *CONTENTS = "abcdefgh";
1099 ssize_t bufsize = strlen(CONTENTS);
1101 const mode_t newmode = 0755;
1104 expect_lookup(RELPATH, ino, 0);
1105 expect_open(ino, 0, 1);
1106 EXPECT_CALL(*m_mock, process(
1107 ResultOf([=](auto in) {
1108 uint32_t valid = FATTR_MODE | FATTR_MTIME | FATTR_CTIME;
1109 return (in.header.opcode == FUSE_SETATTR &&
1110 in.header.nodeid == ino &&
1111 in.body.setattr.valid == valid &&
1112 in.body.setattr.mtimensec % m_time_gran == 0 &&
1113 in.body.setattr.ctimensec % m_time_gran == 0);
1116 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1117 SET_OUT_HEADER_LEN(out, attr);
1118 out.body.attr.attr.ino = ino;
1119 out.body.attr.attr.mode = S_IFREG | newmode;
1122 fd = open(FULLPATH, O_RDWR);
1123 EXPECT_LE(0, fd) << strerror(errno);
1124 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1125 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
1128 INSTANTIATE_TEST_CASE_P(RA, TimeGran, Range(0u, 10u));
1131 * Without direct_io, writes should be committed to cache
1133 TEST_F(Write, writethrough)
1135 const char FULLPATH[] = "mountpoint/some_file.txt";
1136 const char RELPATH[] = "some_file.txt";
1137 const char *CONTENTS = "abcdefgh";
1140 ssize_t bufsize = strlen(CONTENTS);
1141 char readbuf[bufsize];
1143 expect_lookup(RELPATH, ino, 0);
1144 expect_open(ino, 0, 1);
1145 expect_write(ino, 0, bufsize, bufsize, CONTENTS);
1147 fd = open(FULLPATH, O_RDWR);
1148 EXPECT_LE(0, fd) << strerror(errno);
1150 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1152 * A subsequent read should be serviced by cache, without querying the
1155 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
1156 ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno);
1160 /* Writes that extend a file should update the cached file size */
1161 TEST_F(Write, update_file_size)
1163 const char FULLPATH[] = "mountpoint/some_file.txt";
1164 const char RELPATH[] = "some_file.txt";
1165 const char *CONTENTS = "abcdefgh";
1169 ssize_t bufsize = strlen(CONTENTS);
1171 expect_lookup(RELPATH, ino, 0);
1172 expect_open(ino, 0, 1);
1173 expect_write(ino, 0, bufsize, bufsize, CONTENTS);
1175 fd = open(FULLPATH, O_RDWR);
1176 EXPECT_LE(0, fd) << strerror(errno);
1178 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1179 /* Get cached attributes */
1180 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1181 ASSERT_EQ(bufsize, sb.st_size);