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
31extern "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
41using namespace testing;
42
43class Open: public FuseTest {
44
45public:
46
47/* Test an OK open of a file with the given flags */
48void 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
74class OpenNoOpenSupport: public FuseTest {
75	virtual void SetUp() {
76		m_init_flags = FUSE_NO_OPEN_SUPPORT;
77		FuseTest::SetUp();
78	}
79};
80
81/*
82 * fusefs(5) does not support I/O on device nodes (neither does UFS).  But it
83 * shouldn't crash
84 */
85TEST_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 */
110TEST_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 */
142TEST_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 */
165TEST_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 */
228TEST_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 */
234TEST_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 */
240TEST_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 */
246TEST_F(Open, o_excl)
247{
248	test_ok(O_WRONLY | O_EXCL, O_WRONLY);
249}
250
251TEST_F(Open, o_exec)
252{
253	test_ok(O_EXEC, O_EXEC);
254}
255
256/* The kernel is supposed to filter out this flag */
257TEST_F(Open, o_noctty)
258{
259	test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
260}
261
262TEST_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 */
268TEST_F(Open, DISABLED_o_trunc)
269{
270	test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
271}
272
273TEST_F(Open, o_wronly)
274{
275	test_ok(O_WRONLY, O_WRONLY);
276}
277
278TEST_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 */
286TEST_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 */
314TEST_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