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