xref: /freebsd/tests/sys/fs/fusefs/open.cc (revision 7124d2bc3a3bd58f6d3803a0579f2e0fa853b56d)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
291fa8ebfbSAlan Somers  *
301fa8ebfbSAlan Somers  * $FreeBSD$
319821f1d3SAlan Somers  */
329821f1d3SAlan Somers 
339821f1d3SAlan Somers extern "C" {
34a1542146SAlan Somers #include <sys/wait.h>
3527537990SAlan Somers 
369821f1d3SAlan Somers #include <fcntl.h>
3727537990SAlan Somers #include <semaphore.h>
389821f1d3SAlan Somers }
399821f1d3SAlan Somers 
409821f1d3SAlan Somers #include "mockfs.hh"
419821f1d3SAlan Somers #include "utils.hh"
429821f1d3SAlan Somers 
439821f1d3SAlan Somers using namespace testing;
449821f1d3SAlan Somers 
459821f1d3SAlan Somers class Open: public FuseTest {
469821f1d3SAlan Somers 
479821f1d3SAlan Somers public:
489821f1d3SAlan Somers 
499821f1d3SAlan Somers /* Test an OK open of a file with the given flags */
509821f1d3SAlan Somers void test_ok(int os_flags, int fuse_flags) {
519821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
529821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
539821f1d3SAlan Somers 	uint64_t ino = 42;
549821f1d3SAlan Somers 	int fd;
559821f1d3SAlan Somers 
569821f1d3SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
579821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
589821f1d3SAlan Somers 		ResultOf([=](auto in) {
5929edc611SAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
6029edc611SAlan Somers 				in.body.open.flags == (uint32_t)fuse_flags &&
6129edc611SAlan Somers 				in.header.nodeid == ino);
629821f1d3SAlan Somers 		}, Eq(true)),
639821f1d3SAlan Somers 		_)
6429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
6529edc611SAlan Somers 		out.header.len = sizeof(out.header);
669821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, open);
679821f1d3SAlan Somers 	})));
689821f1d3SAlan Somers 
699821f1d3SAlan Somers 	fd = open(FULLPATH, os_flags);
70d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
717fc0921dSAlan Somers 	leak(fd);
729821f1d3SAlan Somers }
739821f1d3SAlan Somers };
749821f1d3SAlan Somers 
759821f1d3SAlan Somers 
76*7124d2bcSAlan Somers class OpenNoOpenSupport: public FuseTest {
77*7124d2bcSAlan Somers 	virtual void SetUp() {
78*7124d2bcSAlan Somers 		m_init_flags = FUSE_NO_OPEN_SUPPORT;
79*7124d2bcSAlan Somers 		FuseTest::SetUp();
80*7124d2bcSAlan Somers 	}
81*7124d2bcSAlan Somers };
82*7124d2bcSAlan Somers 
839821f1d3SAlan Somers /*
84bf4d7084SAlan Somers  * fusefs(5) does not support I/O on device nodes (neither does UFS).  But it
85bf4d7084SAlan Somers  * shouldn't crash
86bf4d7084SAlan Somers  */
87bf4d7084SAlan Somers TEST_F(Open, chr)
88bf4d7084SAlan Somers {
89bf4d7084SAlan Somers 	const char FULLPATH[] = "mountpoint/zero";
90bf4d7084SAlan Somers 	const char RELPATH[] = "zero";
91bf4d7084SAlan Somers 	uint64_t ino = 42;
92bf4d7084SAlan Somers 
93a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
9429edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
95bf4d7084SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
9629edc611SAlan Somers 		out.body.entry.attr.mode = S_IFCHR | 0644;
9729edc611SAlan Somers 		out.body.entry.nodeid = ino;
9829edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
9929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
10029edc611SAlan Somers 		out.body.entry.attr.rdev = 44;	/* /dev/zero's rdev */
101bf4d7084SAlan Somers 	})));
102bf4d7084SAlan Somers 
103bf4d7084SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
104bf4d7084SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
105bf4d7084SAlan Somers }
106bf4d7084SAlan Somers 
107bf4d7084SAlan Somers /*
1089821f1d3SAlan Somers  * The fuse daemon fails the request with enoent.  This usually indicates a
1099821f1d3SAlan Somers  * race condition: some other FUSE client removed the file in between when the
1109821f1d3SAlan Somers  * kernel checked for it with lookup and tried to open it
1119821f1d3SAlan Somers  */
1129821f1d3SAlan Somers TEST_F(Open, enoent)
1139821f1d3SAlan Somers {
1149821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1159821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1169821f1d3SAlan Somers 	uint64_t ino = 42;
11727537990SAlan Somers 	sem_t sem;
11827537990SAlan Somers 
11927537990SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1209821f1d3SAlan Somers 
1219821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
1229821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1239821f1d3SAlan Somers 		ResultOf([=](auto in) {
12429edc611SAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
12529edc611SAlan Somers 				in.header.nodeid == ino);
1269821f1d3SAlan Somers 		}, Eq(true)),
1279821f1d3SAlan Somers 		_)
1289821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOENT)));
12927537990SAlan Somers 	// Since FUSE_OPEN returns ENOENT, the kernel will reclaim the vnode
13027537990SAlan Somers 	// and send a FUSE_FORGET
13127537990SAlan Somers 	expect_forget(ino, 1, &sem);
13227537990SAlan Somers 
1338e765737SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
1349821f1d3SAlan Somers 	EXPECT_EQ(ENOENT, errno);
13527537990SAlan Somers 
13627537990SAlan Somers 	sem_wait(&sem);
13727537990SAlan Somers 	sem_destroy(&sem);
1389821f1d3SAlan Somers }
1399821f1d3SAlan Somers 
1409821f1d3SAlan Somers /*
1419821f1d3SAlan Somers  * The daemon is responsible for checking file permissions (unless the
1429821f1d3SAlan Somers  * default_permissions mount option was used)
1439821f1d3SAlan Somers  */
1449821f1d3SAlan Somers TEST_F(Open, eperm)
1459821f1d3SAlan Somers {
1469821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1479821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1489821f1d3SAlan Somers 	uint64_t ino = 42;
1499821f1d3SAlan Somers 
1509821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
1519821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1529821f1d3SAlan Somers 		ResultOf([=](auto in) {
15329edc611SAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
15429edc611SAlan Somers 				in.header.nodeid == ino);
1559821f1d3SAlan Somers 		}, Eq(true)),
1569821f1d3SAlan Somers 		_)
1579821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(EPERM)));
1588e765737SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
1599821f1d3SAlan Somers 	EXPECT_EQ(EPERM, errno);
1609821f1d3SAlan Somers }
1619821f1d3SAlan Somers 
16242d50d16SAlan Somers /*
16342d50d16SAlan Somers  * fusefs must issue multiple FUSE_OPEN operations if clients with different
16442d50d16SAlan Somers  * credentials open the same file, even if they use the same mode.  This is
16542d50d16SAlan Somers  * necessary so that the daemon can validate each set of credentials.
16642d50d16SAlan Somers  */
167f8d4af10SAlan Somers TEST_F(Open, multiple_creds)
16842d50d16SAlan Somers {
16942d50d16SAlan Somers 	const static char FULLPATH[] = "mountpoint/some_file.txt";
17042d50d16SAlan Somers 	const static char RELPATH[] = "some_file.txt";
171a1542146SAlan Somers 	int fd1, status;
17242d50d16SAlan Somers 	const static uint64_t ino = 42;
17342d50d16SAlan Somers 	const static uint64_t fh0 = 100, fh1 = 200;
17442d50d16SAlan Somers 
17542d50d16SAlan Somers 	/* Fork a child to open the file with different credentials */
176a1542146SAlan Somers 	fork(false, &status, [&] {
17742d50d16SAlan Somers 
17842d50d16SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
17942d50d16SAlan Somers 		EXPECT_CALL(*m_mock, process(
18042d50d16SAlan Somers 			ResultOf([=](auto in) {
18129edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
18229edc611SAlan Somers 					in.header.pid == (uint32_t)getpid() &&
18329edc611SAlan Somers 					in.header.nodeid == ino);
18442d50d16SAlan Somers 			}, Eq(true)),
18542d50d16SAlan Somers 			_)
18642d50d16SAlan Somers 		).WillOnce(Invoke(
18729edc611SAlan Somers 			ReturnImmediate([](auto in __unused, auto& out) {
18829edc611SAlan Somers 			out.body.open.fh = fh0;
18929edc611SAlan Somers 			out.header.len = sizeof(out.header);
19042d50d16SAlan Somers 			SET_OUT_HEADER_LEN(out, open);
19142d50d16SAlan Somers 		})));
19242d50d16SAlan Somers 
19342d50d16SAlan Somers 		EXPECT_CALL(*m_mock, process(
19442d50d16SAlan Somers 			ResultOf([=](auto in) {
19529edc611SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
19629edc611SAlan Somers 					in.header.pid != (uint32_t)getpid() &&
19729edc611SAlan Somers 					in.header.nodeid == ino);
19842d50d16SAlan Somers 			}, Eq(true)),
19942d50d16SAlan Somers 			_)
20042d50d16SAlan Somers 		).WillOnce(Invoke(
20129edc611SAlan Somers 			ReturnImmediate([](auto in __unused, auto& out) {
20229edc611SAlan Somers 			out.body.open.fh = fh1;
20329edc611SAlan Somers 			out.header.len = sizeof(out.header);
20442d50d16SAlan Somers 			SET_OUT_HEADER_LEN(out, open);
20542d50d16SAlan Somers 		})));
2069f10f423SAlan Somers 		expect_flush(ino, 2, ReturnErrno(0));
20742d50d16SAlan Somers 		expect_release(ino, fh0);
20842d50d16SAlan Somers 		expect_release(ino, fh1);
20942d50d16SAlan Somers 
21042d50d16SAlan Somers 		fd1 = open(FULLPATH, O_RDONLY);
211d2621689SAlan Somers 		ASSERT_LE(0, fd1) << strerror(errno);
21242d50d16SAlan Somers 	}, [] {
21342d50d16SAlan Somers 		int fd0;
21442d50d16SAlan Somers 
21542d50d16SAlan Somers 		fd0 = open(FULLPATH, O_RDONLY);
21642d50d16SAlan Somers 		if (fd0 < 0) {
21742d50d16SAlan Somers 			perror("open");
21842d50d16SAlan Somers 			return(1);
21942d50d16SAlan Somers 		}
22042d50d16SAlan Somers 		return 0;
22142d50d16SAlan Somers 	}
22242d50d16SAlan Somers 	);
223a1542146SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
22442d50d16SAlan Somers 
22542d50d16SAlan Somers 	close(fd1);
22642d50d16SAlan Somers }
22742d50d16SAlan Somers 
2289821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
2299821f1d3SAlan Somers TEST_F(Open, DISABLED_o_append)
2309821f1d3SAlan Somers {
2319821f1d3SAlan Somers 	test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
2329821f1d3SAlan Somers }
2339821f1d3SAlan Somers 
2349821f1d3SAlan Somers /* The kernel is supposed to filter out this flag */
2359821f1d3SAlan Somers TEST_F(Open, o_creat)
2369821f1d3SAlan Somers {
2379821f1d3SAlan Somers 	test_ok(O_WRONLY | O_CREAT, O_WRONLY);
2389821f1d3SAlan Somers }
2399821f1d3SAlan Somers 
2409821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
2419821f1d3SAlan Somers TEST_F(Open, DISABLED_o_direct)
2429821f1d3SAlan Somers {
2439821f1d3SAlan Somers 	test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
2449821f1d3SAlan Somers }
2459821f1d3SAlan Somers 
2469821f1d3SAlan Somers /* The kernel is supposed to filter out this flag */
2479821f1d3SAlan Somers TEST_F(Open, o_excl)
2489821f1d3SAlan Somers {
2499821f1d3SAlan Somers 	test_ok(O_WRONLY | O_EXCL, O_WRONLY);
2509821f1d3SAlan Somers }
2519821f1d3SAlan Somers 
252363a7416SAlan Somers TEST_F(Open, o_exec)
2539821f1d3SAlan Somers {
2549821f1d3SAlan Somers 	test_ok(O_EXEC, O_EXEC);
2559821f1d3SAlan Somers }
2569821f1d3SAlan Somers 
2579821f1d3SAlan Somers /* The kernel is supposed to filter out this flag */
2589821f1d3SAlan Somers TEST_F(Open, o_noctty)
2599821f1d3SAlan Somers {
2609821f1d3SAlan Somers 	test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
2619821f1d3SAlan Somers }
2629821f1d3SAlan Somers 
2639821f1d3SAlan Somers TEST_F(Open, o_rdonly)
2649821f1d3SAlan Somers {
2659821f1d3SAlan Somers 	test_ok(O_RDONLY, O_RDONLY);
2669821f1d3SAlan Somers }
2679821f1d3SAlan Somers 
2689821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
2699821f1d3SAlan Somers TEST_F(Open, DISABLED_o_trunc)
2709821f1d3SAlan Somers {
2719821f1d3SAlan Somers 	test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
2729821f1d3SAlan Somers }
2739821f1d3SAlan Somers 
2749821f1d3SAlan Somers TEST_F(Open, o_wronly)
2759821f1d3SAlan Somers {
2769821f1d3SAlan Somers 	test_ok(O_WRONLY, O_WRONLY);
2779821f1d3SAlan Somers }
2789821f1d3SAlan Somers 
2799821f1d3SAlan Somers TEST_F(Open, o_rdwr)
2809821f1d3SAlan Somers {
2819821f1d3SAlan Somers 	test_ok(O_RDWR, O_RDWR);
2829821f1d3SAlan Somers }
2839821f1d3SAlan Somers 
284*7124d2bcSAlan Somers /*
285*7124d2bcSAlan Somers  * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error
286*7124d2bcSAlan Somers  */
287*7124d2bcSAlan Somers TEST_F(Open, enosys)
288*7124d2bcSAlan Somers {
289*7124d2bcSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
290*7124d2bcSAlan Somers 	const char RELPATH[] = "some_file.txt";
291*7124d2bcSAlan Somers 	uint64_t ino = 42;
292*7124d2bcSAlan Somers 	int fd;
293*7124d2bcSAlan Somers 
294*7124d2bcSAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
295*7124d2bcSAlan Somers 	EXPECT_CALL(*m_mock, process(
296*7124d2bcSAlan Somers 		ResultOf([=](auto in) {
297*7124d2bcSAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
298*7124d2bcSAlan Somers 				in.body.open.flags == (uint32_t)O_RDONLY &&
299*7124d2bcSAlan Somers 				in.header.nodeid == ino);
300*7124d2bcSAlan Somers 		}, Eq(true)),
301*7124d2bcSAlan Somers 		_)
302*7124d2bcSAlan Somers 	).Times(1)
303*7124d2bcSAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOSYS)));
304*7124d2bcSAlan Somers 
305*7124d2bcSAlan Somers 	fd = open(FULLPATH, O_RDONLY);
306*7124d2bcSAlan Somers 	ASSERT_EQ(-1, fd) << strerror(errno);
307*7124d2bcSAlan Somers 	EXPECT_EQ(ENOSYS, errno);
308*7124d2bcSAlan Somers }
309*7124d2bcSAlan Somers 
310*7124d2bcSAlan Somers /*
311*7124d2bcSAlan Somers  * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a
312*7124d2bcSAlan Somers  * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will
313*7124d2bcSAlan Somers  * also succeed automatically without being sent to the server.
314*7124d2bcSAlan Somers  */
315*7124d2bcSAlan Somers TEST_F(OpenNoOpenSupport, enosys)
316*7124d2bcSAlan Somers {
317*7124d2bcSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
318*7124d2bcSAlan Somers 	const char RELPATH[] = "some_file.txt";
319*7124d2bcSAlan Somers 	uint64_t ino = 42;
320*7124d2bcSAlan Somers 	int fd;
321*7124d2bcSAlan Somers 
322*7124d2bcSAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
323*7124d2bcSAlan Somers 	EXPECT_CALL(*m_mock, process(
324*7124d2bcSAlan Somers 		ResultOf([=](auto in) {
325*7124d2bcSAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
326*7124d2bcSAlan Somers 				in.body.open.flags == (uint32_t)O_RDONLY &&
327*7124d2bcSAlan Somers 				in.header.nodeid == ino);
328*7124d2bcSAlan Somers 		}, Eq(true)),
329*7124d2bcSAlan Somers 		_)
330*7124d2bcSAlan Somers 	).Times(1)
331*7124d2bcSAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOSYS)));
332*7124d2bcSAlan Somers 	expect_flush(ino, 1, ReturnErrno(ENOSYS));
333*7124d2bcSAlan Somers 
334*7124d2bcSAlan Somers 	fd = open(FULLPATH, O_RDONLY);
335*7124d2bcSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
336*7124d2bcSAlan Somers 	close(fd);
337*7124d2bcSAlan Somers 
338*7124d2bcSAlan Somers 	fd = open(FULLPATH, O_RDONLY);
339*7124d2bcSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
340*7124d2bcSAlan Somers 
341*7124d2bcSAlan Somers 	leak(fd);
342*7124d2bcSAlan Somers }
343