xref: /freebsd/tests/sys/fs/fusefs/open.cc (revision 3332f1b444d4a73238e9f59cca27bfc95fe936bd)
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  * $FreeBSD$
31  */
32 
33 extern "C" {
34 #include <sys/wait.h>
35 
36 #include <fcntl.h>
37 #include <semaphore.h>
38 }
39 
40 #include "mockfs.hh"
41 #include "utils.hh"
42 
43 using namespace testing;
44 
45 class Open: public FuseTest {
46 
47 public:
48 
49 /* Test an OK open of a file with the given flags */
50 void test_ok(int os_flags, int fuse_flags) {
51 	const char FULLPATH[] = "mountpoint/some_file.txt";
52 	const char RELPATH[] = "some_file.txt";
53 	uint64_t ino = 42;
54 	int fd;
55 
56 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
57 	EXPECT_CALL(*m_mock, process(
58 		ResultOf([=](auto in) {
59 			return (in.header.opcode == FUSE_OPEN &&
60 				in.body.open.flags == (uint32_t)fuse_flags &&
61 				in.header.nodeid == ino);
62 		}, Eq(true)),
63 		_)
64 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
65 		out.header.len = sizeof(out.header);
66 		SET_OUT_HEADER_LEN(out, open);
67 	})));
68 
69 	fd = open(FULLPATH, os_flags);
70 	ASSERT_LE(0, fd) << strerror(errno);
71 	leak(fd);
72 }
73 };
74 
75 
76 class OpenNoOpenSupport: public FuseTest {
77 	virtual void SetUp() {
78 		m_init_flags = FUSE_NO_OPEN_SUPPORT;
79 		FuseTest::SetUp();
80 	}
81 };
82 
83 /*
84  * fusefs(5) does not support I/O on device nodes (neither does UFS).  But it
85  * shouldn't crash
86  */
87 TEST_F(Open, chr)
88 {
89 	const char FULLPATH[] = "mountpoint/zero";
90 	const char RELPATH[] = "zero";
91 	uint64_t ino = 42;
92 
93 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
94 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
95 		SET_OUT_HEADER_LEN(out, entry);
96 		out.body.entry.attr.mode = S_IFCHR | 0644;
97 		out.body.entry.nodeid = ino;
98 		out.body.entry.attr.nlink = 1;
99 		out.body.entry.attr_valid = UINT64_MAX;
100 		out.body.entry.attr.rdev = 44;	/* /dev/zero's rdev */
101 	})));
102 
103 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
104 	EXPECT_EQ(EOPNOTSUPP, errno);
105 }
106 
107 /*
108  * The fuse daemon fails the request with enoent.  This usually indicates a
109  * race condition: some other FUSE client removed the file in between when the
110  * kernel checked for it with lookup and tried to open it
111  */
112 TEST_F(Open, enoent)
113 {
114 	const char FULLPATH[] = "mountpoint/some_file.txt";
115 	const char RELPATH[] = "some_file.txt";
116 	uint64_t ino = 42;
117 	sem_t sem;
118 
119 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
120 
121 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
122 	EXPECT_CALL(*m_mock, process(
123 		ResultOf([=](auto in) {
124 			return (in.header.opcode == FUSE_OPEN &&
125 				in.header.nodeid == ino);
126 		}, Eq(true)),
127 		_)
128 	).WillOnce(Invoke(ReturnErrno(ENOENT)));
129 	// Since FUSE_OPEN returns ENOENT, the kernel will reclaim the vnode
130 	// and send a FUSE_FORGET
131 	expect_forget(ino, 1, &sem);
132 
133 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
134 	EXPECT_EQ(ENOENT, errno);
135 
136 	sem_wait(&sem);
137 	sem_destroy(&sem);
138 }
139 
140 /*
141  * The daemon is responsible for checking file permissions (unless the
142  * default_permissions mount option was used)
143  */
144 TEST_F(Open, eperm)
145 {
146 	const char FULLPATH[] = "mountpoint/some_file.txt";
147 	const char RELPATH[] = "some_file.txt";
148 	uint64_t ino = 42;
149 
150 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
151 	EXPECT_CALL(*m_mock, process(
152 		ResultOf([=](auto in) {
153 			return (in.header.opcode == FUSE_OPEN &&
154 				in.header.nodeid == ino);
155 		}, Eq(true)),
156 		_)
157 	).WillOnce(Invoke(ReturnErrno(EPERM)));
158 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
159 	EXPECT_EQ(EPERM, errno);
160 }
161 
162 /*
163  * fusefs must issue multiple FUSE_OPEN operations if clients with different
164  * credentials open the same file, even if they use the same mode.  This is
165  * necessary so that the daemon can validate each set of credentials.
166  */
167 TEST_F(Open, multiple_creds)
168 {
169 	const static char FULLPATH[] = "mountpoint/some_file.txt";
170 	const static char RELPATH[] = "some_file.txt";
171 	int fd1, status;
172 	const static uint64_t ino = 42;
173 	const static uint64_t fh0 = 100, fh1 = 200;
174 
175 	/* Fork a child to open the file with different credentials */
176 	fork(false, &status, [&] {
177 
178 		expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
179 		EXPECT_CALL(*m_mock, process(
180 			ResultOf([=](auto in) {
181 				return (in.header.opcode == FUSE_OPEN &&
182 					in.header.pid == (uint32_t)getpid() &&
183 					in.header.nodeid == ino);
184 			}, Eq(true)),
185 			_)
186 		).WillOnce(Invoke(
187 			ReturnImmediate([](auto in __unused, auto& out) {
188 			out.body.open.fh = fh0;
189 			out.header.len = sizeof(out.header);
190 			SET_OUT_HEADER_LEN(out, open);
191 		})));
192 
193 		EXPECT_CALL(*m_mock, process(
194 			ResultOf([=](auto in) {
195 				return (in.header.opcode == FUSE_OPEN &&
196 					in.header.pid != (uint32_t)getpid() &&
197 					in.header.nodeid == ino);
198 			}, Eq(true)),
199 			_)
200 		).WillOnce(Invoke(
201 			ReturnImmediate([](auto in __unused, auto& out) {
202 			out.body.open.fh = fh1;
203 			out.header.len = sizeof(out.header);
204 			SET_OUT_HEADER_LEN(out, open);
205 		})));
206 		expect_flush(ino, 2, ReturnErrno(0));
207 		expect_release(ino, fh0);
208 		expect_release(ino, fh1);
209 
210 		fd1 = open(FULLPATH, O_RDONLY);
211 		ASSERT_LE(0, fd1) << strerror(errno);
212 	}, [] {
213 		int fd0;
214 
215 		fd0 = open(FULLPATH, O_RDONLY);
216 		if (fd0 < 0) {
217 			perror("open");
218 			return(1);
219 		}
220 		return 0;
221 	}
222 	);
223 	ASSERT_EQ(0, WEXITSTATUS(status));
224 
225 	close(fd1);
226 }
227 
228 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
229 TEST_F(Open, DISABLED_o_append)
230 {
231 	test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
232 }
233 
234 /* The kernel is supposed to filter out this flag */
235 TEST_F(Open, o_creat)
236 {
237 	test_ok(O_WRONLY | O_CREAT, O_WRONLY);
238 }
239 
240 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
241 TEST_F(Open, DISABLED_o_direct)
242 {
243 	test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
244 }
245 
246 /* The kernel is supposed to filter out this flag */
247 TEST_F(Open, o_excl)
248 {
249 	test_ok(O_WRONLY | O_EXCL, O_WRONLY);
250 }
251 
252 TEST_F(Open, o_exec)
253 {
254 	test_ok(O_EXEC, O_EXEC);
255 }
256 
257 /* The kernel is supposed to filter out this flag */
258 TEST_F(Open, o_noctty)
259 {
260 	test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
261 }
262 
263 TEST_F(Open, o_rdonly)
264 {
265 	test_ok(O_RDONLY, O_RDONLY);
266 }
267 
268 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
269 TEST_F(Open, DISABLED_o_trunc)
270 {
271 	test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
272 }
273 
274 TEST_F(Open, o_wronly)
275 {
276 	test_ok(O_WRONLY, O_WRONLY);
277 }
278 
279 TEST_F(Open, o_rdwr)
280 {
281 	test_ok(O_RDWR, O_RDWR);
282 }
283 
284 /*
285  * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error
286  */
287 TEST_F(Open, enosys)
288 {
289 	const char FULLPATH[] = "mountpoint/some_file.txt";
290 	const char RELPATH[] = "some_file.txt";
291 	uint64_t ino = 42;
292 	int fd;
293 
294 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
295 	EXPECT_CALL(*m_mock, process(
296 		ResultOf([=](auto in) {
297 			return (in.header.opcode == FUSE_OPEN &&
298 				in.body.open.flags == (uint32_t)O_RDONLY &&
299 				in.header.nodeid == ino);
300 		}, Eq(true)),
301 		_)
302 	).Times(1)
303 	.WillOnce(Invoke(ReturnErrno(ENOSYS)));
304 
305 	fd = open(FULLPATH, O_RDONLY);
306 	ASSERT_EQ(-1, fd) << strerror(errno);
307 	EXPECT_EQ(ENOSYS, errno);
308 }
309 
310 /*
311  * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a
312  * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will
313  * also succeed automatically without being sent to the server.
314  */
315 TEST_F(OpenNoOpenSupport, enosys)
316 {
317 	const char FULLPATH[] = "mountpoint/some_file.txt";
318 	const char RELPATH[] = "some_file.txt";
319 	uint64_t ino = 42;
320 	int fd;
321 
322 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
323 	EXPECT_CALL(*m_mock, process(
324 		ResultOf([=](auto in) {
325 			return (in.header.opcode == FUSE_OPEN &&
326 				in.body.open.flags == (uint32_t)O_RDONLY &&
327 				in.header.nodeid == ino);
328 		}, Eq(true)),
329 		_)
330 	).Times(1)
331 	.WillOnce(Invoke(ReturnErrno(ENOSYS)));
332 	expect_flush(ino, 1, ReturnErrno(ENOSYS));
333 
334 	fd = open(FULLPATH, O_RDONLY);
335 	ASSERT_LE(0, fd) << strerror(errno);
336 	close(fd);
337 
338 	fd = open(FULLPATH, O_RDONLY);
339 	ASSERT_LE(0, fd) << strerror(errno);
340 
341 	leak(fd);
342 }
343