]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/open.cc
Merge llvm-project release/15.x llvmorg-15.0.2-10-gf3c5289e7846
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / open.cc
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                 leak(fd0);
221                 return 0;
222         }
223         );
224         ASSERT_EQ(0, WEXITSTATUS(status));
225
226         close(fd1);
227 }
228
229 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
230 TEST_F(Open, DISABLED_o_append)
231 {
232         test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
233 }
234
235 /* The kernel is supposed to filter out this flag */
236 TEST_F(Open, o_creat)
237 {
238         test_ok(O_WRONLY | O_CREAT, O_WRONLY);
239 }
240
241 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
242 TEST_F(Open, DISABLED_o_direct)
243 {
244         test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
245 }
246
247 /* The kernel is supposed to filter out this flag */
248 TEST_F(Open, o_excl)
249 {
250         test_ok(O_WRONLY | O_EXCL, O_WRONLY);
251 }
252
253 TEST_F(Open, o_exec)
254 {
255         test_ok(O_EXEC, O_EXEC);
256 }
257
258 /* The kernel is supposed to filter out this flag */
259 TEST_F(Open, o_noctty)
260 {
261         test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
262 }
263
264 TEST_F(Open, o_rdonly)
265 {
266         test_ok(O_RDONLY, O_RDONLY);
267 }
268
269 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
270 TEST_F(Open, DISABLED_o_trunc)
271 {
272         test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
273 }
274
275 TEST_F(Open, o_wronly)
276 {
277         test_ok(O_WRONLY, O_WRONLY);
278 }
279
280 TEST_F(Open, o_rdwr)
281 {
282         test_ok(O_RDWR, O_RDWR);
283 }
284
285 /*
286  * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error
287  */
288 TEST_F(Open, enosys)
289 {
290         const char FULLPATH[] = "mountpoint/some_file.txt";
291         const char RELPATH[] = "some_file.txt";
292         uint64_t ino = 42;
293         int fd;
294
295         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
296         EXPECT_CALL(*m_mock, process(
297                 ResultOf([=](auto in) {
298                         return (in.header.opcode == FUSE_OPEN &&
299                                 in.body.open.flags == (uint32_t)O_RDONLY &&
300                                 in.header.nodeid == ino);
301                 }, Eq(true)),
302                 _)
303         ).Times(1)
304         .WillOnce(Invoke(ReturnErrno(ENOSYS)));
305
306         fd = open(FULLPATH, O_RDONLY);
307         ASSERT_EQ(-1, fd) << strerror(errno);
308         EXPECT_EQ(ENOSYS, errno);
309 }
310
311 /*
312  * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a
313  * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will
314  * also succeed automatically without being sent to the server.
315  */
316 TEST_F(OpenNoOpenSupport, enosys)
317 {
318         const char FULLPATH[] = "mountpoint/some_file.txt";
319         const char RELPATH[] = "some_file.txt";
320         uint64_t ino = 42;
321         int fd;
322
323         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
324         EXPECT_CALL(*m_mock, process(
325                 ResultOf([=](auto in) {
326                         return (in.header.opcode == FUSE_OPEN &&
327                                 in.body.open.flags == (uint32_t)O_RDONLY &&
328                                 in.header.nodeid == ino);
329                 }, Eq(true)),
330                 _)
331         ).Times(1)
332         .WillOnce(Invoke(ReturnErrno(ENOSYS)));
333         expect_flush(ino, 1, ReturnErrno(ENOSYS));
334
335         fd = open(FULLPATH, O_RDONLY);
336         ASSERT_LE(0, fd) << strerror(errno);
337         close(fd);
338
339         fd = open(FULLPATH, O_RDONLY);
340         ASSERT_LE(0, fd) << strerror(errno);
341
342         leak(fd);
343 }