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