2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
34 #include <sys/types.h>
35 #include <sys/extattr.h>
44 using namespace testing;
46 class Access: public FuseTest {
48 virtual void SetUp() {
50 // Clear the default FUSE_ACCESS expectation
51 Mock::VerifyAndClearExpectations(m_mock);
54 void expect_lookup(const char *relpath, uint64_t ino)
56 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
60 * Expect tha FUSE_ACCESS will never be called for the given inode, with any
61 * bits in the supplied access_mask set
63 void expect_noaccess(uint64_t ino, mode_t access_mask)
65 EXPECT_CALL(*m_mock, process(
66 ResultOf([=](auto in) {
67 return (in.header.opcode == FUSE_ACCESS &&
68 in.header.nodeid == ino &&
69 in.body.access.mask & access_mask);
77 class RofsAccess: public Access {
79 virtual void SetUp() {
86 * Change the mode of a file.
88 * There should never be a FUSE_ACCESS sent for this operation, except for
89 * search permissions on the parent directory.
90 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
94 const char FULLPATH[] = "mountpoint/some_file.txt";
95 const char RELPATH[] = "some_file.txt";
96 const uint64_t ino = 42;
97 const mode_t newmode = 0644;
99 expect_access(FUSE_ROOT_ID, X_OK, 0);
100 expect_lookup(RELPATH, ino);
101 expect_noaccess(ino, 0);
102 EXPECT_CALL(*m_mock, process(
103 ResultOf([](auto in) {
104 return (in.header.opcode == FUSE_SETATTR &&
105 in.header.nodeid == ino);
108 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
109 SET_OUT_HEADER_LEN(out, attr);
110 out.body.attr.attr.ino = ino; // Must match nodeid
111 out.body.attr.attr.mode = S_IFREG | newmode;
114 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
120 * There should never be a FUSE_ACCESS sent for this operation, except for
121 * search permissions on the parent directory.
122 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
124 TEST_F(Access, create)
126 const char FULLPATH[] = "mountpoint/some_file.txt";
127 const char RELPATH[] = "some_file.txt";
128 mode_t mode = S_IFREG | 0755;
131 expect_access(FUSE_ROOT_ID, X_OK, 0);
132 expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK);
133 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
134 .WillOnce(Invoke(ReturnErrno(ENOENT)));
135 expect_noaccess(ino, 0);
136 EXPECT_CALL(*m_mock, process(
137 ResultOf([=](auto in) {
138 return (in.header.opcode == FUSE_CREATE);
141 ).WillOnce(ReturnErrno(EPERM));
143 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
144 EXPECT_EQ(EPERM, errno);
147 /* The error case of FUSE_ACCESS. */
148 TEST_F(Access, eaccess)
150 const char FULLPATH[] = "mountpoint/some_file.txt";
151 const char RELPATH[] = "some_file.txt";
153 mode_t access_mode = X_OK;
155 expect_access(FUSE_ROOT_ID, X_OK, 0);
156 expect_lookup(RELPATH, ino);
157 expect_access(ino, access_mode, EACCES);
159 ASSERT_NE(0, access(FULLPATH, access_mode));
160 ASSERT_EQ(EACCES, errno);
164 * If the filesystem returns ENOSYS, then it is treated as a permanent success,
165 * and subsequent VOP_ACCESS calls will succeed automatically without querying
168 TEST_F(Access, enosys)
170 const char FULLPATH[] = "mountpoint/some_file.txt";
171 const char RELPATH[] = "some_file.txt";
173 mode_t access_mode = R_OK;
175 expect_access(FUSE_ROOT_ID, X_OK, ENOSYS);
176 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
178 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
179 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
182 TEST_F(RofsAccess, erofs)
184 const char FULLPATH[] = "mountpoint/some_file.txt";
185 const char RELPATH[] = "some_file.txt";
187 mode_t access_mode = W_OK;
189 expect_access(FUSE_ROOT_ID, X_OK, 0);
190 expect_lookup(RELPATH, ino);
192 ASSERT_NE(0, access(FULLPATH, access_mode));
193 ASSERT_EQ(EROFS, errno);
198 * Lookup an extended attribute
200 * There should never be a FUSE_ACCESS sent for this operation, except for
201 * search permissions on the parent directory.
202 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
204 TEST_F(Access, Getxattr)
206 const char FULLPATH[] = "mountpoint/some_file.txt";
207 const char RELPATH[] = "some_file.txt";
210 int ns = EXTATTR_NAMESPACE_USER;
213 expect_access(FUSE_ROOT_ID, X_OK, 0);
214 expect_lookup(RELPATH, ino);
215 expect_noaccess(ino, 0);
216 expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
218 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
220 ASSERT_EQ(ENOATTR, errno);
223 /* The successful case of FUSE_ACCESS. */
226 const char FULLPATH[] = "mountpoint/some_file.txt";
227 const char RELPATH[] = "some_file.txt";
229 mode_t access_mode = R_OK;
231 expect_access(FUSE_ROOT_ID, X_OK, 0);
232 expect_lookup(RELPATH, ino);
233 expect_access(ino, access_mode, 0);
235 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
241 * There should never be a FUSE_ACCESS sent for this operation, except for
242 * search permissions on the parent directory.
243 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
245 TEST_F(Access, unlink)
247 const char FULLPATH[] = "mountpoint/some_file.txt";
248 const char RELPATH[] = "some_file.txt";
251 expect_access(FUSE_ROOT_ID, X_OK, 0);
252 expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
253 expect_noaccess(ino, 0);
254 expect_lookup(RELPATH, ino);
255 expect_unlink(1, RELPATH, EPERM);
257 ASSERT_NE(0, unlink(FULLPATH));
258 ASSERT_EQ(EPERM, errno);
262 * Unlink a file whose parent diretory's sticky bit is set
264 * There should never be a FUSE_ACCESS sent for this operation, except for
265 * search permissions on the parent directory.
266 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
268 TEST_F(Access, unlink_sticky_directory)
270 const char FULLPATH[] = "mountpoint/some_file.txt";
271 const char RELPATH[] = "some_file.txt";
274 expect_access(FUSE_ROOT_ID, X_OK, 0);
275 expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
276 expect_noaccess(ino, 0);
277 EXPECT_CALL(*m_mock, process(
278 ResultOf([=](auto in) {
279 return (in.header.opcode == FUSE_GETATTR &&
280 in.header.nodeid == FUSE_ROOT_ID);
283 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
285 SET_OUT_HEADER_LEN(out, attr);
286 out.body.attr.attr.ino = FUSE_ROOT_ID;
287 out.body.attr.attr.mode = S_IFDIR | 01777;
288 out.body.attr.attr.uid = 0;
289 out.body.attr.attr_valid = UINT64_MAX;
291 EXPECT_CALL(*m_mock, process(
292 ResultOf([=](auto in) {
293 return (in.header.opcode == FUSE_ACCESS &&
294 in.header.nodeid == ino);
298 expect_lookup(RELPATH, ino);
299 expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM);
301 ASSERT_EQ(-1, unlink(FULLPATH));
302 ASSERT_EQ(EPERM, errno);