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