xref: /freebsd/tests/sys/fs/fusefs/open.cc (revision a573243370d3f4ffad81c740591004851f2beb45)
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 /*
77  * fusefs(5) does not support I/O on device nodes (neither does UFS).  But it
78  * shouldn't crash
79  */
80 TEST_F(Open, chr)
81 {
82 	const char FULLPATH[] = "mountpoint/zero";
83 	const char RELPATH[] = "zero";
84 	uint64_t ino = 42;
85 
86 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
87 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
88 		SET_OUT_HEADER_LEN(out, entry);
89 		out.body.entry.attr.mode = S_IFCHR | 0644;
90 		out.body.entry.nodeid = ino;
91 		out.body.entry.attr.nlink = 1;
92 		out.body.entry.attr_valid = UINT64_MAX;
93 		out.body.entry.attr.rdev = 44;	/* /dev/zero's rdev */
94 	})));
95 
96 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
97 	EXPECT_EQ(EOPNOTSUPP, errno);
98 }
99 
100 /*
101  * The fuse daemon fails the request with enoent.  This usually indicates a
102  * race condition: some other FUSE client removed the file in between when the
103  * kernel checked for it with lookup and tried to open it
104  */
105 TEST_F(Open, enoent)
106 {
107 	const char FULLPATH[] = "mountpoint/some_file.txt";
108 	const char RELPATH[] = "some_file.txt";
109 	uint64_t ino = 42;
110 	sem_t sem;
111 
112 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
113 
114 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
115 	EXPECT_CALL(*m_mock, process(
116 		ResultOf([=](auto in) {
117 			return (in.header.opcode == FUSE_OPEN &&
118 				in.header.nodeid == ino);
119 		}, Eq(true)),
120 		_)
121 	).WillOnce(Invoke(ReturnErrno(ENOENT)));
122 	// Since FUSE_OPEN returns ENOENT, the kernel will reclaim the vnode
123 	// and send a FUSE_FORGET
124 	expect_forget(ino, 1, &sem);
125 
126 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
127 	EXPECT_EQ(ENOENT, errno);
128 
129 	sem_wait(&sem);
130 	sem_destroy(&sem);
131 }
132 
133 /*
134  * The daemon is responsible for checking file permissions (unless the
135  * default_permissions mount option was used)
136  */
137 TEST_F(Open, eperm)
138 {
139 	const char FULLPATH[] = "mountpoint/some_file.txt";
140 	const char RELPATH[] = "some_file.txt";
141 	uint64_t ino = 42;
142 
143 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
144 	EXPECT_CALL(*m_mock, process(
145 		ResultOf([=](auto in) {
146 			return (in.header.opcode == FUSE_OPEN &&
147 				in.header.nodeid == ino);
148 		}, Eq(true)),
149 		_)
150 	).WillOnce(Invoke(ReturnErrno(EPERM)));
151 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
152 	EXPECT_EQ(EPERM, errno);
153 }
154 
155 /*
156  * fusefs must issue multiple FUSE_OPEN operations if clients with different
157  * credentials open the same file, even if they use the same mode.  This is
158  * necessary so that the daemon can validate each set of credentials.
159  */
160 TEST_F(Open, multiple_creds)
161 {
162 	const static char FULLPATH[] = "mountpoint/some_file.txt";
163 	const static char RELPATH[] = "some_file.txt";
164 	int fd1, status;
165 	const static uint64_t ino = 42;
166 	const static uint64_t fh0 = 100, fh1 = 200;
167 
168 	/* Fork a child to open the file with different credentials */
169 	fork(false, &status, [&] {
170 
171 		expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
172 		EXPECT_CALL(*m_mock, process(
173 			ResultOf([=](auto in) {
174 				return (in.header.opcode == FUSE_OPEN &&
175 					in.header.pid == (uint32_t)getpid() &&
176 					in.header.nodeid == ino);
177 			}, Eq(true)),
178 			_)
179 		).WillOnce(Invoke(
180 			ReturnImmediate([](auto in __unused, auto& out) {
181 			out.body.open.fh = fh0;
182 			out.header.len = sizeof(out.header);
183 			SET_OUT_HEADER_LEN(out, open);
184 		})));
185 
186 		EXPECT_CALL(*m_mock, process(
187 			ResultOf([=](auto in) {
188 				return (in.header.opcode == FUSE_OPEN &&
189 					in.header.pid != (uint32_t)getpid() &&
190 					in.header.nodeid == ino);
191 			}, Eq(true)),
192 			_)
193 		).WillOnce(Invoke(
194 			ReturnImmediate([](auto in __unused, auto& out) {
195 			out.body.open.fh = fh1;
196 			out.header.len = sizeof(out.header);
197 			SET_OUT_HEADER_LEN(out, open);
198 		})));
199 		expect_flush(ino, 2, ReturnErrno(0));
200 		expect_release(ino, fh0);
201 		expect_release(ino, fh1);
202 
203 		fd1 = open(FULLPATH, O_RDONLY);
204 		ASSERT_LE(0, fd1) << strerror(errno);
205 	}, [] {
206 		int fd0;
207 
208 		fd0 = open(FULLPATH, O_RDONLY);
209 		if (fd0 < 0) {
210 			perror("open");
211 			return(1);
212 		}
213 		return 0;
214 	}
215 	);
216 	ASSERT_EQ(0, WEXITSTATUS(status));
217 
218 	close(fd1);
219 }
220 
221 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
222 TEST_F(Open, DISABLED_o_append)
223 {
224 	test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
225 }
226 
227 /* The kernel is supposed to filter out this flag */
228 TEST_F(Open, o_creat)
229 {
230 	test_ok(O_WRONLY | O_CREAT, O_WRONLY);
231 }
232 
233 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
234 TEST_F(Open, DISABLED_o_direct)
235 {
236 	test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
237 }
238 
239 /* The kernel is supposed to filter out this flag */
240 TEST_F(Open, o_excl)
241 {
242 	test_ok(O_WRONLY | O_EXCL, O_WRONLY);
243 }
244 
245 TEST_F(Open, o_exec)
246 {
247 	test_ok(O_EXEC, O_EXEC);
248 }
249 
250 /* The kernel is supposed to filter out this flag */
251 TEST_F(Open, o_noctty)
252 {
253 	test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
254 }
255 
256 TEST_F(Open, o_rdonly)
257 {
258 	test_ok(O_RDONLY, O_RDONLY);
259 }
260 
261 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
262 TEST_F(Open, DISABLED_o_trunc)
263 {
264 	test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
265 }
266 
267 TEST_F(Open, o_wronly)
268 {
269 	test_ok(O_WRONLY, O_WRONLY);
270 }
271 
272 TEST_F(Open, o_rdwr)
273 {
274 	test_ok(O_RDWR, O_RDWR);
275 }
276 
277