xref: /freebsd/tests/sys/fs/fusefs/open.cc (revision bf4d70841fb676dacb70f0cb67c1ccd8085db96c)
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 
31 extern "C" {
32 #include <fcntl.h>
33 }
34 
35 #include "mockfs.hh"
36 #include "utils.hh"
37 
38 using namespace testing;
39 
40 class Open: public FuseTest {
41 
42 public:
43 
44 /* Test an OK open of a file with the given flags */
45 void test_ok(int os_flags, int fuse_flags) {
46 	const char FULLPATH[] = "mountpoint/some_file.txt";
47 	const char RELPATH[] = "some_file.txt";
48 	uint64_t ino = 42;
49 	int fd;
50 
51 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
52 	EXPECT_CALL(*m_mock, process(
53 		ResultOf([=](auto in) {
54 			return (in->header.opcode == FUSE_OPEN &&
55 				in->body.open.flags == (uint32_t)fuse_flags &&
56 				in->header.nodeid == ino);
57 		}, Eq(true)),
58 		_)
59 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
60 		out->header.len = sizeof(out->header);
61 		SET_OUT_HEADER_LEN(out, open);
62 	})));
63 
64 	/* Until the attr cache is working, we may send an additional GETATTR */
65 	EXPECT_CALL(*m_mock, process(
66 		ResultOf([=](auto in) {
67 			return (in->header.opcode == FUSE_GETATTR &&
68 				in->header.nodeid == ino);
69 		}, Eq(true)),
70 		_)
71 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
72 		SET_OUT_HEADER_LEN(out, attr);
73 		out->body.attr.attr.ino = ino;	// Must match nodeid
74 		out->body.attr.attr.mode = S_IFREG | 0644;
75 	})));
76 
77 	fd = open(FULLPATH, os_flags);
78 	EXPECT_LE(0, fd) << strerror(errno);
79 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
80 }
81 };
82 
83 
84 /*
85  * fusefs(5) does not support I/O on device nodes (neither does UFS).  But it
86  * shouldn't crash
87  */
88 TEST_F(Open, chr)
89 {
90 	const char FULLPATH[] = "mountpoint/zero";
91 	const char RELPATH[] = "zero";
92 	uint64_t ino = 42;
93 
94 	EXPECT_LOOKUP(1, RELPATH)
95 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
96 		SET_OUT_HEADER_LEN(out, entry);
97 		out->body.entry.attr.mode = S_IFCHR | 0644;
98 		out->body.entry.nodeid = ino;
99 		out->body.entry.attr.nlink = 1;
100 		out->body.entry.attr_valid = UINT64_MAX;
101 		out->body.entry.attr.rdev = 44;	/* /dev/zero's rdev */
102 	})));
103 
104 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
105 	EXPECT_EQ(EOPNOTSUPP, errno);
106 }
107 
108 /*
109  * The fuse daemon fails the request with enoent.  This usually indicates a
110  * race condition: some other FUSE client removed the file in between when the
111  * kernel checked for it with lookup and tried to open it
112  */
113 TEST_F(Open, enoent)
114 {
115 	const char FULLPATH[] = "mountpoint/some_file.txt";
116 	const char RELPATH[] = "some_file.txt";
117 	uint64_t ino = 42;
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 	EXPECT_NE(0, open(FULLPATH, O_RDONLY));
128 	EXPECT_EQ(ENOENT, errno);
129 }
130 
131 /*
132  * The daemon is responsible for checking file permissions (unless the
133  * default_permissions mount option was used)
134  */
135 TEST_F(Open, eperm)
136 {
137 	const char FULLPATH[] = "mountpoint/some_file.txt";
138 	const char RELPATH[] = "some_file.txt";
139 	uint64_t ino = 42;
140 
141 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
142 	EXPECT_CALL(*m_mock, process(
143 		ResultOf([=](auto in) {
144 			return (in->header.opcode == FUSE_OPEN &&
145 				in->header.nodeid == ino);
146 		}, Eq(true)),
147 		_)
148 	).WillOnce(Invoke(ReturnErrno(EPERM)));
149 	EXPECT_NE(0, open(FULLPATH, O_RDONLY));
150 	EXPECT_EQ(EPERM, errno);
151 }
152 
153 /* fusefs(5) does not yet support I/O on fifos.  But it shouldn't crash. */
154 TEST_F(Open, fifo)
155 {
156 	const char FULLPATH[] = "mountpoint/zero";
157 	const char RELPATH[] = "zero";
158 	uint64_t ino = 42;
159 
160 	EXPECT_LOOKUP(1, RELPATH)
161 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
162 		SET_OUT_HEADER_LEN(out, entry);
163 		out->body.entry.attr.mode = S_IFIFO | 0644;
164 		out->body.entry.nodeid = ino;
165 		out->body.entry.attr.nlink = 1;
166 		out->body.entry.attr_valid = UINT64_MAX;
167 	})));
168 
169 	ASSERT_EQ(-1, open(FULLPATH, O_RDONLY));
170 	EXPECT_EQ(EOPNOTSUPP, errno);
171 }
172 
173 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
174 TEST_F(Open, DISABLED_o_append)
175 {
176 	test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND);
177 }
178 
179 /* The kernel is supposed to filter out this flag */
180 TEST_F(Open, o_creat)
181 {
182 	test_ok(O_WRONLY | O_CREAT, O_WRONLY);
183 }
184 
185 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
186 TEST_F(Open, DISABLED_o_direct)
187 {
188 	test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT);
189 }
190 
191 /* The kernel is supposed to filter out this flag */
192 TEST_F(Open, o_excl)
193 {
194 	test_ok(O_WRONLY | O_EXCL, O_WRONLY);
195 }
196 
197 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236329 */
198 TEST_F(Open, DISABLED_o_exec)
199 {
200 	test_ok(O_EXEC, O_EXEC);
201 }
202 
203 /* The kernel is supposed to filter out this flag */
204 TEST_F(Open, o_noctty)
205 {
206 	test_ok(O_WRONLY | O_NOCTTY, O_WRONLY);
207 }
208 
209 TEST_F(Open, o_rdonly)
210 {
211 	test_ok(O_RDONLY, O_RDONLY);
212 }
213 
214 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
215 TEST_F(Open, DISABLED_o_trunc)
216 {
217 	test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC);
218 }
219 
220 TEST_F(Open, o_wronly)
221 {
222 	test_ok(O_WRONLY, O_WRONLY);
223 }
224 
225 TEST_F(Open, o_rdwr)
226 {
227 	test_ok(O_RDWR, O_RDWR);
228 }
229 
230