xref: /freebsd/tests/sys/fs/fusefs/fsync.cc (revision bbce101753b9f68edd34180cb617fff9327a9e0b)
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 <aio.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 }
36 
37 #include "mockfs.hh"
38 #include "utils.hh"
39 
40 using namespace testing;
41 
42 /*
43  * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28.
44  * This bit was actually part of kernel protocol version 5.2, but never
45  * documented until after 7.28
46  */
47 #ifndef FUSE_FSYNC_FDATASYNC
48 #define FUSE_FSYNC_FDATASYNC 1
49 #endif
50 
51 class Fsync: public FuseTest {
52 public:
53 void expect_fsync(uint64_t ino, uint32_t flags, int error)
54 {
55 	EXPECT_CALL(*m_mock, process(
56 		ResultOf([=](auto in) {
57 			return (in.header.opcode == FUSE_FSYNC &&
58 				in.header.nodeid == ino &&
59 				/*
60 				 * TODO: reenable pid check after fixing
61 				 * bug 236379
62 				 */
63 				//(pid_t)in.header.pid == getpid() &&
64 				in.body.fsync.fh == FH &&
65 				in.body.fsync.fsync_flags == flags);
66 		}, Eq(true)),
67 		_)
68 	).WillOnce(Invoke(ReturnErrno(error)));
69 }
70 
71 void expect_lookup(const char *relpath, uint64_t ino)
72 {
73 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
74 }
75 
76 void expect_write(uint64_t ino, uint64_t size, const void *contents)
77 {
78 	FuseTest::expect_write(ino, 0, size, size, 0, 0, contents);
79 }
80 
81 };
82 
83 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
84 TEST_F(Fsync, aio_fsync)
85 {
86 	const char FULLPATH[] = "mountpoint/some_file.txt";
87 	const char RELPATH[] = "some_file.txt";
88 	const char *CONTENTS = "abcdefgh";
89 	ssize_t bufsize = strlen(CONTENTS);
90 	uint64_t ino = 42;
91 	struct aiocb iocb, *piocb;
92 	int fd;
93 
94 	expect_lookup(RELPATH, ino);
95 	expect_open(ino, 0, 1);
96 	expect_write(ino, bufsize, CONTENTS);
97 	expect_fsync(ino, 0, 0);
98 
99 	fd = open(FULLPATH, O_RDWR);
100 	ASSERT_LE(0, fd) << strerror(errno);
101 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
102 
103 	bzero(&iocb, sizeof(iocb));
104 	iocb.aio_fildes = fd;
105 
106 	ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno);
107 	ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
108 
109 	leak(fd);
110 }
111 
112 /*
113  * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE
114  *
115  * This test only really make sense in writeback caching mode, but it should
116  * still pass in any cache mode.
117  */
118 TEST_F(Fsync, close)
119 {
120 	const char FULLPATH[] = "mountpoint/some_file.txt";
121 	const char RELPATH[] = "some_file.txt";
122 	const char *CONTENTS = "abcdefgh";
123 	ssize_t bufsize = strlen(CONTENTS);
124 	uint64_t ino = 42;
125 	int fd;
126 
127 	expect_lookup(RELPATH, ino);
128 	expect_open(ino, 0, 1);
129 	expect_write(ino, bufsize, CONTENTS);
130 	EXPECT_CALL(*m_mock, process(
131 		ResultOf([=](auto in) {
132 			return (in.header.opcode == FUSE_SETATTR);
133 		}, Eq(true)),
134 		_)
135 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
136 		SET_OUT_HEADER_LEN(out, attr);
137 		out.body.attr.attr.ino = ino;	// Must match nodeid
138 	})));
139 	EXPECT_CALL(*m_mock, process(
140 		ResultOf([=](auto in) {
141 			return (in.header.opcode == FUSE_FSYNC);
142 		}, Eq(true)),
143 		_)
144 	).Times(0);
145 	expect_flush(ino, 1, ReturnErrno(0));
146 	expect_release(ino, FH);
147 
148 	fd = open(FULLPATH, O_RDWR);
149 	ASSERT_LE(0, fd) << strerror(errno);
150 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
151 	close(fd);
152 }
153 
154 TEST_F(Fsync, eio)
155 {
156 	const char FULLPATH[] = "mountpoint/some_file.txt";
157 	const char RELPATH[] = "some_file.txt";
158 	const char *CONTENTS = "abcdefgh";
159 	ssize_t bufsize = strlen(CONTENTS);
160 	uint64_t ino = 42;
161 	int fd;
162 
163 	expect_lookup(RELPATH, ino);
164 	expect_open(ino, 0, 1);
165 	expect_write(ino, bufsize, CONTENTS);
166 	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO);
167 
168 	fd = open(FULLPATH, O_RDWR);
169 	ASSERT_LE(0, fd) << strerror(errno);
170 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
171 	ASSERT_NE(0, fdatasync(fd));
172 	ASSERT_EQ(EIO, errno);
173 
174 	leak(fd);
175 }
176 
177 /*
178  * If the filesystem returns ENOSYS, it will be treated as success and
179  * subsequent calls to VOP_FSYNC will succeed automatically without being sent
180  * to the filesystem daemon
181  */
182 TEST_F(Fsync, enosys)
183 {
184 	const char FULLPATH[] = "mountpoint/some_file.txt";
185 	const char RELPATH[] = "some_file.txt";
186 	const char *CONTENTS = "abcdefgh";
187 	ssize_t bufsize = strlen(CONTENTS);
188 	uint64_t ino = 42;
189 	int fd;
190 
191 	expect_lookup(RELPATH, ino);
192 	expect_open(ino, 0, 1);
193 	expect_write(ino, bufsize, CONTENTS);
194 	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, ENOSYS);
195 
196 	fd = open(FULLPATH, O_RDWR);
197 	ASSERT_LE(0, fd) << strerror(errno);
198 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
199 	EXPECT_EQ(0, fdatasync(fd));
200 
201 	/* Subsequent calls shouldn't query the daemon*/
202 	EXPECT_EQ(0, fdatasync(fd));
203 	leak(fd);
204 }
205 
206 
207 TEST_F(Fsync, fdatasync)
208 {
209 	const char FULLPATH[] = "mountpoint/some_file.txt";
210 	const char RELPATH[] = "some_file.txt";
211 	const char *CONTENTS = "abcdefgh";
212 	ssize_t bufsize = strlen(CONTENTS);
213 	uint64_t ino = 42;
214 	int fd;
215 
216 	expect_lookup(RELPATH, ino);
217 	expect_open(ino, 0, 1);
218 	expect_write(ino, bufsize, CONTENTS);
219 	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0);
220 
221 	fd = open(FULLPATH, O_RDWR);
222 	ASSERT_LE(0, fd) << strerror(errno);
223 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
224 	ASSERT_EQ(0, fdatasync(fd)) << strerror(errno);
225 
226 	leak(fd);
227 }
228 
229 TEST_F(Fsync, fsync)
230 {
231 	const char FULLPATH[] = "mountpoint/some_file.txt";
232 	const char RELPATH[] = "some_file.txt";
233 	const char *CONTENTS = "abcdefgh";
234 	ssize_t bufsize = strlen(CONTENTS);
235 	uint64_t ino = 42;
236 	int fd;
237 
238 	expect_lookup(RELPATH, ino);
239 	expect_open(ino, 0, 1);
240 	expect_write(ino, bufsize, CONTENTS);
241 	expect_fsync(ino, 0, 0);
242 
243 	fd = open(FULLPATH, O_RDWR);
244 	ASSERT_LE(0, fd) << strerror(errno);
245 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
246 	ASSERT_EQ(0, fsync(fd)) << strerror(errno);
247 
248 	leak(fd);
249 }
250