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