xref: /freebsd/tests/sys/fs/fusefs/access.cc (revision bfcb817bcd70fa17c7e65e72f15089eee9e53ce3)
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" {
34*bfcb817bSAlan Somers #include <sys/types.h>
35*bfcb817bSAlan Somers #include <sys/extattr.h>
36*bfcb817bSAlan 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:
48*bfcb817bSAlan Somers virtual void SetUp() {
49*bfcb817bSAlan Somers 	FuseTest::SetUp();
50*bfcb817bSAlan Somers 	// Clear the default FUSE_ACCESS expectation
51*bfcb817bSAlan Somers 	Mock::VerifyAndClearExpectations(m_mock);
52*bfcb817bSAlan Somers }
53*bfcb817bSAlan 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 }
58*bfcb817bSAlan Somers 
59*bfcb817bSAlan Somers /*
60*bfcb817bSAlan Somers  * Expect tha FUSE_ACCESS will never be called for the given inode, with any
61*bfcb817bSAlan Somers  * bits in the supplied access_mask set
62*bfcb817bSAlan Somers  */
63*bfcb817bSAlan Somers void expect_noaccess(uint64_t ino, mode_t access_mask)
64*bfcb817bSAlan Somers {
65*bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
66*bfcb817bSAlan Somers 		ResultOf([=](auto in) {
67*bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_ACCESS &&
68*bfcb817bSAlan Somers 				in.header.nodeid == ino &&
69*bfcb817bSAlan Somers 				in.body.access.mask & access_mask);
70*bfcb817bSAlan Somers 		}, Eq(true)),
71*bfcb817bSAlan Somers 		_)
72*bfcb817bSAlan Somers 	).Times(0);
73*bfcb817bSAlan Somers }
74*bfcb817bSAlan 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 
85*bfcb817bSAlan Somers /*
86*bfcb817bSAlan Somers  * Change the mode of a file.
87*bfcb817bSAlan Somers  *
88*bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
89*bfcb817bSAlan Somers  * search permissions on the parent directory.
90*bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
91*bfcb817bSAlan Somers  */
92*bfcb817bSAlan Somers TEST_F(Access, chmod)
93*bfcb817bSAlan Somers {
94*bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
95*bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
96*bfcb817bSAlan Somers 	const uint64_t ino = 42;
97*bfcb817bSAlan Somers 	const mode_t newmode = 0644;
98*bfcb817bSAlan Somers 
99*bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
100*bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
101*bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
102*bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
103*bfcb817bSAlan Somers 		ResultOf([](auto in) {
104*bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
105*bfcb817bSAlan Somers 				in.header.nodeid == ino);
106*bfcb817bSAlan Somers 		}, Eq(true)),
107*bfcb817bSAlan Somers 		_)
108*bfcb817bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
109*bfcb817bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
110*bfcb817bSAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
111*bfcb817bSAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
112*bfcb817bSAlan Somers 	})));
113*bfcb817bSAlan Somers 
114*bfcb817bSAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
115*bfcb817bSAlan Somers }
116*bfcb817bSAlan Somers 
117*bfcb817bSAlan Somers /*
118*bfcb817bSAlan Somers  * Create a new file
119*bfcb817bSAlan Somers  *
120*bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
121*bfcb817bSAlan Somers  * search permissions on the parent directory.
122*bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
123*bfcb817bSAlan Somers  */
124*bfcb817bSAlan Somers TEST_F(Access, create)
125*bfcb817bSAlan Somers {
126*bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
127*bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
128*bfcb817bSAlan Somers 	mode_t mode = S_IFREG | 0755;
129*bfcb817bSAlan Somers 	uint64_t ino = 42;
130*bfcb817bSAlan Somers 
131*bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
132*bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK);
133*bfcb817bSAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
134*bfcb817bSAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
135*bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
136*bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
137*bfcb817bSAlan Somers 		ResultOf([=](auto in) {
138*bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_CREATE);
139*bfcb817bSAlan Somers 		}, Eq(true)),
140*bfcb817bSAlan Somers 		_)
141*bfcb817bSAlan Somers 	).WillOnce(ReturnErrno(EPERM));
142*bfcb817bSAlan Somers 
143*bfcb817bSAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
144*bfcb817bSAlan Somers 	EXPECT_EQ(EPERM, errno);
145*bfcb817bSAlan Somers }
146*bfcb817bSAlan 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 
196*bfcb817bSAlan Somers 
197*bfcb817bSAlan Somers /*
198*bfcb817bSAlan Somers  * Lookup an extended attribute
199*bfcb817bSAlan Somers  *
200*bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
201*bfcb817bSAlan Somers  * search permissions on the parent directory.
202*bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
203*bfcb817bSAlan Somers  */
204*bfcb817bSAlan Somers TEST_F(Access, Getxattr)
205*bfcb817bSAlan Somers {
206*bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
207*bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
208*bfcb817bSAlan Somers 	uint64_t ino = 42;
209*bfcb817bSAlan Somers 	char data[80];
210*bfcb817bSAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
211*bfcb817bSAlan Somers 	ssize_t r;
212*bfcb817bSAlan Somers 
213*bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
214*bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
215*bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
216*bfcb817bSAlan Somers 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
217*bfcb817bSAlan Somers 
218*bfcb817bSAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
219*bfcb817bSAlan Somers 	ASSERT_EQ(-1, r);
220*bfcb817bSAlan Somers 	ASSERT_EQ(ENOATTR, errno);
221*bfcb817bSAlan Somers }
222*bfcb817bSAlan 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 }
237*bfcb817bSAlan Somers 
238*bfcb817bSAlan Somers /*
239*bfcb817bSAlan Somers  * Unlink a file
240*bfcb817bSAlan Somers  *
241*bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
242*bfcb817bSAlan Somers  * search permissions on the parent directory.
243*bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
244*bfcb817bSAlan Somers  */
245*bfcb817bSAlan Somers TEST_F(Access, unlink)
246*bfcb817bSAlan Somers {
247*bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
248*bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
249*bfcb817bSAlan Somers 	uint64_t ino = 42;
250*bfcb817bSAlan Somers 
251*bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
252*bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
253*bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
254*bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
255*bfcb817bSAlan Somers 	expect_unlink(1, RELPATH, EPERM);
256*bfcb817bSAlan Somers 
257*bfcb817bSAlan Somers 	ASSERT_NE(0, unlink(FULLPATH));
258*bfcb817bSAlan Somers 	ASSERT_EQ(EPERM, errno);
259*bfcb817bSAlan Somers }
260*bfcb817bSAlan Somers 
261*bfcb817bSAlan Somers /*
262*bfcb817bSAlan Somers  * Unlink a file whose parent diretory's sticky bit is set
263*bfcb817bSAlan Somers  *
264*bfcb817bSAlan Somers  * There should never be a FUSE_ACCESS sent for this operation, except for
265*bfcb817bSAlan Somers  * search permissions on the parent directory.
266*bfcb817bSAlan Somers  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
267*bfcb817bSAlan Somers  */
268*bfcb817bSAlan Somers TEST_F(Access, unlink_sticky_directory)
269*bfcb817bSAlan Somers {
270*bfcb817bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
271*bfcb817bSAlan Somers 	const char RELPATH[] = "some_file.txt";
272*bfcb817bSAlan Somers 	uint64_t ino = 42;
273*bfcb817bSAlan Somers 
274*bfcb817bSAlan Somers 	expect_access(FUSE_ROOT_ID, X_OK, 0);
275*bfcb817bSAlan Somers 	expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
276*bfcb817bSAlan Somers 	expect_noaccess(ino, 0);
277*bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
278*bfcb817bSAlan Somers 		ResultOf([=](auto in) {
279*bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
280*bfcb817bSAlan Somers 				in.header.nodeid == FUSE_ROOT_ID);
281*bfcb817bSAlan Somers 		}, Eq(true)),
282*bfcb817bSAlan Somers 		_)
283*bfcb817bSAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
284*bfcb817bSAlan Somers 	{
285*bfcb817bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
286*bfcb817bSAlan Somers 		out.body.attr.attr.ino = FUSE_ROOT_ID;
287*bfcb817bSAlan Somers 		out.body.attr.attr.mode = S_IFDIR | 01777;
288*bfcb817bSAlan Somers 		out.body.attr.attr.uid = 0;
289*bfcb817bSAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
290*bfcb817bSAlan Somers 	})));
291*bfcb817bSAlan Somers 	EXPECT_CALL(*m_mock, process(
292*bfcb817bSAlan Somers 		ResultOf([=](auto in) {
293*bfcb817bSAlan Somers 			return (in.header.opcode == FUSE_ACCESS &&
294*bfcb817bSAlan Somers 				in.header.nodeid == ino);
295*bfcb817bSAlan Somers 		}, Eq(true)),
296*bfcb817bSAlan Somers 		_)
297*bfcb817bSAlan Somers 	).Times(0);
298*bfcb817bSAlan Somers 	expect_lookup(RELPATH, ino);
299*bfcb817bSAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM);
300*bfcb817bSAlan Somers 
301*bfcb817bSAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
302*bfcb817bSAlan Somers 	ASSERT_EQ(EPERM, errno);
303*bfcb817bSAlan Somers }
304