xref: /freebsd/tests/sys/fs/fusefs/access.cc (revision 4d846d260e2b9a3d4d0a701462568268cbfe7a5b)
19821f1d3SAlan Somers /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
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" {
34bfcb817bSAlan Somers #include <sys/types.h>
35bfcb817bSAlan Somers #include <sys/extattr.h>
36bfcb817bSAlan Somers 
379821f1d3SAlan Somers #include <fcntl.h>
389821f1d3SAlan Somers #include <unistd.h>
399821f1d3SAlan Somers }
409821f1d3SAlan Somers 
419821f1d3SAlan Somers #include "mockfs.hh"
429821f1d3SAlan Somers #include "utils.hh"
439821f1d3SAlan Somers 
449821f1d3SAlan Somers using namespace testing;
459821f1d3SAlan Somers 
469821f1d3SAlan Somers class Access: public FuseTest {
479821f1d3SAlan Somers public:
48bfcb817bSAlan Somers virtual void SetUp() {
49bfcb817bSAlan Somers 	FuseTest::SetUp();
50bfcb817bSAlan Somers 	// Clear the default FUSE_ACCESS expectation
51bfcb817bSAlan Somers 	Mock::VerifyAndClearExpectations(m_mock);
52bfcb817bSAlan Somers }
53bfcb817bSAlan Somers 
549821f1d3SAlan Somers void expect_lookup(const char *relpath, uint64_t ino)
559821f1d3SAlan Somers {
569821f1d3SAlan Somers 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
579821f1d3SAlan Somers }
58bfcb817bSAlan Somers 
59bfcb817bSAlan Somers /*
60bfcb817bSAlan Somers  * Expect tha FUSE_ACCESS will never be called for the given inode, with any
61bfcb817bSAlan Somers  * bits in the supplied access_mask set
62bfcb817bSAlan Somers  */
63bfcb817bSAlan Somers void expect_noaccess(uint64_t ino, mode_t access_mask)
64bfcb817bSAlan Somers {
65bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
66bfcb817bSAlan Somers 		ResultOf([=](auto in) {
67bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_ACCESS &&
68bfcb817bSAlan Somers 				in.header.nodeid == ino &&
69bfcb817bSAlan Somers 				in.body.access.mask & access_mask);
70bfcb817bSAlan Somers 		}, Eq(true)),
71bfcb817bSAlan Somers 		_)
72bfcb817bSAlan Somers 	).Times(0);
73bfcb817bSAlan Somers }
74bfcb817bSAlan Somers 
759821f1d3SAlan Somers };
769821f1d3SAlan Somers 
77140bb492SAlan Somers class RofsAccess: public Access {
78140bb492SAlan Somers public:
79140bb492SAlan Somers virtual void SetUp() {
80140bb492SAlan Somers 	m_ro = true;
81140bb492SAlan Somers 	Access::SetUp();
82140bb492SAlan Somers }
83140bb492SAlan Somers };
84140bb492SAlan Somers 
85bfcb817bSAlan Somers /*
86bfcb817bSAlan Somers  * Change the mode of a file.
87bfcb817bSAlan Somers  *
88bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
89bfcb817bSAlan Somers  * search permissions on the parent directory.
90bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
91bfcb817bSAlan Somers  */
92bfcb817bSAlan Somers TEST_F(Access, chmod)
93bfcb817bSAlan Somers {
94bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
95bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
96bfcb817bSAlan Somers 	const uint64_t ino = 42;
97bfcb817bSAlan Somers 	const mode_t newmode = 0644;
98bfcb817bSAlan Somers 
99bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
100bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
101bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
102bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
103bfcb817bSAlan Somers 		ResultOf([](auto in) {
104bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
105bfcb817bSAlan Somers 				in.header.nodeid == ino);
106bfcb817bSAlan Somers 		}, Eq(true)),
107bfcb817bSAlan Somers 		_)
108bfcb817bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
109bfcb817bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
110bfcb817bSAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
111bfcb817bSAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
112bfcb817bSAlan Somers 	})));
113bfcb817bSAlan Somers 
114bfcb817bSAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
115bfcb817bSAlan Somers }
116bfcb817bSAlan Somers 
117bfcb817bSAlan Somers /*
118bfcb817bSAlan Somers  * Create a new file
119bfcb817bSAlan Somers  *
120bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
121bfcb817bSAlan Somers  * search permissions on the parent directory.
122bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
123bfcb817bSAlan Somers  */
124bfcb817bSAlan Somers TEST_F(Access, create)
125bfcb817bSAlan Somers {
126bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
127bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
128bfcb817bSAlan Somers 	mode_t mode = S_IFREG | 0755;
129bfcb817bSAlan Somers 	uint64_t ino = 42;
130bfcb817bSAlan Somers 
131bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
132bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK);
133bfcb817bSAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
134bfcb817bSAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
135bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
136bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
137bfcb817bSAlan Somers 		ResultOf([=](auto in) {
138bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_CREATE);
139bfcb817bSAlan Somers 		}, Eq(true)),
140bfcb817bSAlan Somers 		_)
141bfcb817bSAlan Somers 	).WillOnce(ReturnErrno(EPERM));
142bfcb817bSAlan Somers 
143bfcb817bSAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
144bfcb817bSAlan Somers 	EXPECT_EQ(EPERM, errno);
145bfcb817bSAlan Somers }
146bfcb817bSAlan Somers 
1479821f1d3SAlan Somers /* The error case of FUSE_ACCESS.  */
148caf5f57dSAlan Somers TEST_F(Access, eaccess)
1499821f1d3SAlan Somers {
1509821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1519821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1529821f1d3SAlan Somers 	uint64_t ino = 42;
1539821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
1549821f1d3SAlan Somers 
155a34cdd26SAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
1569821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
1579821f1d3SAlan Somers 	expect_access(ino, access_mode, EACCES);
1589821f1d3SAlan Somers 
1599821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
1609821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
1619821f1d3SAlan Somers }
1629821f1d3SAlan Somers 
1639821f1d3SAlan Somers /*
1649821f1d3SAlan Somers  * If the filesystem returns ENOSYS, then it is treated as a permanent success,
1659821f1d3SAlan Somers  * and subsequent VOP_ACCESS calls will succeed automatically without querying
1669821f1d3SAlan Somers  * the daemon.
1679821f1d3SAlan Somers  */
168caf5f57dSAlan Somers TEST_F(Access, enosys)
1699821f1d3SAlan Somers {
1709821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1719821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1729821f1d3SAlan Somers 	uint64_t ino = 42;
1739821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
1749821f1d3SAlan Somers 
175a34cdd26SAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, ENOSYS);
176caf5f57dSAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
1779821f1d3SAlan Somers 
1789821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
1799821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
1809821f1d3SAlan Somers }
1819821f1d3SAlan Somers 
182140bb492SAlan Somers TEST_F(RofsAccess, erofs)
183140bb492SAlan Somers {
184140bb492SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
185140bb492SAlan Somers 	const char RELPATH[] = "some_file.txt";
186140bb492SAlan Somers 	uint64_t ino = 42;
187140bb492SAlan Somers 	mode_t	access_mode = W_OK;
188140bb492SAlan Somers 
189a34cdd26SAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
190140bb492SAlan Somers 	expect_lookup(RELPATH, ino);
191140bb492SAlan Somers 
192140bb492SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
193140bb492SAlan Somers 	ASSERT_EQ(EROFS, errno);
194140bb492SAlan Somers }
195140bb492SAlan Somers 
196bfcb817bSAlan Somers 
197bfcb817bSAlan Somers /*
198bfcb817bSAlan Somers  * Lookup an extended attribute
199bfcb817bSAlan Somers  *
200bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
201bfcb817bSAlan Somers  * search permissions on the parent directory.
202bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
203bfcb817bSAlan Somers  */
204bfcb817bSAlan Somers TEST_F(Access, Getxattr)
205bfcb817bSAlan Somers {
206bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
207bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
208bfcb817bSAlan Somers 	uint64_t ino = 42;
209bfcb817bSAlan Somers 	char data[80];
210bfcb817bSAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
211bfcb817bSAlan Somers 	ssize_t r;
212bfcb817bSAlan Somers 
213bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
214bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
215bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
216bfcb817bSAlan Somers 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
217bfcb817bSAlan Somers 
218bfcb817bSAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
219bfcb817bSAlan Somers 	ASSERT_EQ(-1, r);
220bfcb817bSAlan Somers 	ASSERT_EQ(ENOATTR, errno);
221bfcb817bSAlan Somers }
222bfcb817bSAlan Somers 
2239821f1d3SAlan Somers /* The successful case of FUSE_ACCESS.  */
224caf5f57dSAlan Somers TEST_F(Access, ok)
2259821f1d3SAlan Somers {
2269821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2279821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2289821f1d3SAlan Somers 	uint64_t ino = 42;
2299821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
2309821f1d3SAlan Somers 
231a34cdd26SAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
2329821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
2339821f1d3SAlan Somers 	expect_access(ino, access_mode, 0);
2349821f1d3SAlan Somers 
2359821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
2369821f1d3SAlan Somers }
237bfcb817bSAlan Somers 
238bfcb817bSAlan Somers /*
239bfcb817bSAlan Somers  * Unlink a file
240bfcb817bSAlan Somers  *
241bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
242bfcb817bSAlan Somers  * search permissions on the parent directory.
243bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
244bfcb817bSAlan Somers  */
245bfcb817bSAlan Somers TEST_F(Access, unlink)
246bfcb817bSAlan Somers {
247bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
248bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
249bfcb817bSAlan Somers 	uint64_t ino = 42;
250bfcb817bSAlan Somers 
251bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
252bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
253bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
254bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
255bfcb817bSAlan Somers 	expect_unlink(1, RELPATH, EPERM);
256bfcb817bSAlan Somers 
257bfcb817bSAlan Somers 	ASSERT_NE(0, unlink(FULLPATH));
258bfcb817bSAlan Somers 	ASSERT_EQ(EPERM, errno);
259bfcb817bSAlan Somers }
260bfcb817bSAlan Somers 
261bfcb817bSAlan Somers /*
262bfcb817bSAlan Somers  * Unlink a file whose parent diretory's sticky bit is set
263bfcb817bSAlan Somers  *
264bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
265bfcb817bSAlan Somers  * search permissions on the parent directory.
266bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
267bfcb817bSAlan Somers  */
268bfcb817bSAlan Somers TEST_F(Access, unlink_sticky_directory)
269bfcb817bSAlan Somers {
270bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
271bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
272bfcb817bSAlan Somers 	uint64_t ino = 42;
273bfcb817bSAlan Somers 
274bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
275bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
276bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
277bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
278bfcb817bSAlan Somers 		ResultOf([=](auto in) {
279bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
280bfcb817bSAlan Somers 				in.header.nodeid == FUSE_ROOT_ID);
281bfcb817bSAlan Somers 		}, Eq(true)),
282bfcb817bSAlan Somers 		_)
283bfcb817bSAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
284bfcb817bSAlan Somers 	{
285bfcb817bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
286bfcb817bSAlan Somers 		out.body.attr.attr.ino = FUSE_ROOT_ID;
287bfcb817bSAlan Somers 		out.body.attr.attr.mode = S_IFDIR | 01777;
288bfcb817bSAlan Somers 		out.body.attr.attr.uid = 0;
289bfcb817bSAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
290bfcb817bSAlan Somers 	})));
291bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
292bfcb817bSAlan Somers 		ResultOf([=](auto in) {
293bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_ACCESS &&
294bfcb817bSAlan Somers 				in.header.nodeid == ino);
295bfcb817bSAlan Somers 		}, Eq(true)),
296bfcb817bSAlan Somers 		_)
297bfcb817bSAlan Somers 	).Times(0);
298bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
299bfcb817bSAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM);
300bfcb817bSAlan Somers 
301bfcb817bSAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
302bfcb817bSAlan Somers 	ASSERT_EQ(EPERM, errno);
303bfcb817bSAlan Somers }
304