2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
37 #include <semaphore.h>
43 using namespace testing;
45 class Open: public FuseTest {
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";
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);
64 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
65 out.header.len = sizeof(out.header);
66 SET_OUT_HEADER_LEN(out, open);
69 fd = open(FULLPATH, os_flags);
70 ASSERT_LE(0, fd) << strerror(errno);
76 class OpenNoOpenSupport: public FuseTest {
77 virtual void SetUp() {
78 m_init_flags = FUSE_NO_OPEN_SUPPORT;
84 * fusefs(5) does not support I/O on device nodes (neither does UFS). But it
89 const char FULLPATH[] = "mountpoint/zero";
90 const char RELPATH[] = "zero";
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 */
103 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
104 EXPECT_EQ(EOPNOTSUPP, errno);
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
114 const char FULLPATH[] = "mountpoint/some_file.txt";
115 const char RELPATH[] = "some_file.txt";
119 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
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);
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);
133 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
134 EXPECT_EQ(ENOENT, errno);
141 * The daemon is responsible for checking file permissions (unless the
142 * default_permissions mount option was used)
146 const char FULLPATH[] = "mountpoint/some_file.txt";
147 const char RELPATH[] = "some_file.txt";
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);
157 ).WillOnce(Invoke(ReturnErrno(EPERM)));
158 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
159 EXPECT_EQ(EPERM, errno);
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.
167 TEST_F(Open, multiple_creds)
169 const static char FULLPATH[] = "mountpoint/some_file.txt";
170 const static char RELPATH[] = "some_file.txt";
172 const static uint64_t ino = 42;
173 const static uint64_t fh0 = 100, fh1 = 200;
175 /* Fork a child to open the file with different credentials */
176 fork(false, &status, [&] {
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);
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);
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);
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);
206 expect_flush(ino, 2, ReturnErrno(0));
207 expect_release(ino, fh0);
208 expect_release(ino, fh1);
210 fd1 = open(FULLPATH, O_RDONLY);
211 ASSERT_LE(0, fd1) << strerror(errno);
215 fd0 = open(FULLPATH, O_RDONLY);
224 ASSERT_EQ(0, WEXITSTATUS(status));
229 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
230 TEST_F(Open, DISABLED_o_append)
232 test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
235 /* The kernel is supposed to filter out this flag */
236 TEST_F(Open, o_creat)
238 test_ok(O_WRONLY | O_CREAT, O_WRONLY);
241 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
242 TEST_F(Open, DISABLED_o_direct)
244 test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
247 /* The kernel is supposed to filter out this flag */
250 test_ok(O_WRONLY | O_EXCL, O_WRONLY);
255 test_ok(O_EXEC, O_EXEC);
258 /* The kernel is supposed to filter out this flag */
259 TEST_F(Open, o_noctty)
261 test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
264 TEST_F(Open, o_rdonly)
266 test_ok(O_RDONLY, O_RDONLY);
269 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
270 TEST_F(Open, DISABLED_o_trunc)
272 test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
275 TEST_F(Open, o_wronly)
277 test_ok(O_WRONLY, O_WRONLY);
282 test_ok(O_RDWR, O_RDWR);
286 * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error
290 const char FULLPATH[] = "mountpoint/some_file.txt";
291 const char RELPATH[] = "some_file.txt";
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);
304 .WillOnce(Invoke(ReturnErrno(ENOSYS)));
306 fd = open(FULLPATH, O_RDONLY);
307 ASSERT_EQ(-1, fd) << strerror(errno);
308 EXPECT_EQ(ENOSYS, errno);
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.
316 TEST_F(OpenNoOpenSupport, enosys)
318 const char FULLPATH[] = "mountpoint/some_file.txt";
319 const char RELPATH[] = "some_file.txt";
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);
332 .WillOnce(Invoke(ReturnErrno(ENOSYS)));
333 expect_flush(ino, 1, ReturnErrno(ENOSYS));
335 fd = open(FULLPATH, O_RDONLY);
336 ASSERT_LE(0, fd) << strerror(errno);
339 fd = open(FULLPATH, O_RDONLY);
340 ASSERT_LE(0, fd) << strerror(errno);