]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/access.cc
Fix issues with FUSE_ACCESS when default_permissions is disabled
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / access.cc
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  * $FreeBSD$
31  */
32
33 extern "C" {
34 #include <sys/types.h>
35 #include <sys/extattr.h>
36
37 #include <fcntl.h>
38 #include <unistd.h>
39 }
40
41 #include "mockfs.hh"
42 #include "utils.hh"
43
44 using namespace testing;
45
46 class Access: public FuseTest {
47 public:
48 virtual void SetUp() {
49         FuseTest::SetUp();
50         // Clear the default FUSE_ACCESS expectation
51         Mock::VerifyAndClearExpectations(m_mock);
52 }
53
54 void expect_lookup(const char *relpath, uint64_t ino)
55 {
56         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
57 }
58
59 /* 
60  * Expect tha FUSE_ACCESS will never be called for the given inode, with any
61  * bits in the supplied access_mask set
62  */
63 void expect_noaccess(uint64_t ino, mode_t access_mask)
64 {
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);
70                 }, Eq(true)),
71                 _)
72         ).Times(0);
73 }
74
75 };
76
77 class RofsAccess: public Access {
78 public:
79 virtual void SetUp() {
80         m_ro = true;
81         Access::SetUp();
82 }
83 };
84
85 /*
86  * Change the mode of a file.
87  *
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
91  */
92 TEST_F(Access, chmod)
93 {
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;
98
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);
106                 }, Eq(true)),
107                 _)
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;
112         })));
113
114         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
115 }
116
117 /*
118  * Create a new file
119  *
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
123  */
124 TEST_F(Access, create)
125 {
126         const char FULLPATH[] = "mountpoint/some_file.txt";
127         const char RELPATH[] = "some_file.txt";
128         mode_t mode = S_IFREG | 0755;
129         uint64_t ino = 42;
130
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);
139                 }, Eq(true)),
140                 _)
141         ).WillOnce(ReturnErrno(EPERM));
142
143         EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
144         EXPECT_EQ(EPERM, errno);
145 }
146
147 /* The error case of FUSE_ACCESS.  */
148 TEST_F(Access, eaccess)
149 {
150         const char FULLPATH[] = "mountpoint/some_file.txt";
151         const char RELPATH[] = "some_file.txt";
152         uint64_t ino = 42;
153         mode_t  access_mode = X_OK;
154
155         expect_access(FUSE_ROOT_ID, X_OK, 0);
156         expect_lookup(RELPATH, ino);
157         expect_access(ino, access_mode, EACCES);
158
159         ASSERT_NE(0, access(FULLPATH, access_mode));
160         ASSERT_EQ(EACCES, errno);
161 }
162
163 /*
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
166  * the daemon.
167  */
168 TEST_F(Access, enosys)
169 {
170         const char FULLPATH[] = "mountpoint/some_file.txt";
171         const char RELPATH[] = "some_file.txt";
172         uint64_t ino = 42;
173         mode_t  access_mode = R_OK;
174
175         expect_access(FUSE_ROOT_ID, X_OK, ENOSYS);
176         FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
177
178         ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
179         ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
180 }
181
182 TEST_F(RofsAccess, erofs)
183 {
184         const char FULLPATH[] = "mountpoint/some_file.txt";
185         const char RELPATH[] = "some_file.txt";
186         uint64_t ino = 42;
187         mode_t  access_mode = W_OK;
188
189         expect_access(FUSE_ROOT_ID, X_OK, 0);
190         expect_lookup(RELPATH, ino);
191
192         ASSERT_NE(0, access(FULLPATH, access_mode));
193         ASSERT_EQ(EROFS, errno);
194 }
195
196
197 /*
198  * Lookup an extended attribute
199  *
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
203  */
204 TEST_F(Access, Getxattr)
205 {
206         const char FULLPATH[] = "mountpoint/some_file.txt";
207         const char RELPATH[] = "some_file.txt";
208         uint64_t ino = 42;
209         char data[80];
210         int ns = EXTATTR_NAMESPACE_USER;
211         ssize_t r;
212
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));
217
218         r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
219         ASSERT_EQ(-1, r);
220         ASSERT_EQ(ENOATTR, errno);
221 }
222
223 /* The successful case of FUSE_ACCESS.  */
224 TEST_F(Access, ok)
225 {
226         const char FULLPATH[] = "mountpoint/some_file.txt";
227         const char RELPATH[] = "some_file.txt";
228         uint64_t ino = 42;
229         mode_t  access_mode = R_OK;
230
231         expect_access(FUSE_ROOT_ID, X_OK, 0);
232         expect_lookup(RELPATH, ino);
233         expect_access(ino, access_mode, 0);
234
235         ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
236 }
237
238 /*
239  * Unlink a file
240  *
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
244  */
245 TEST_F(Access, unlink)
246 {
247         const char FULLPATH[] = "mountpoint/some_file.txt";
248         const char RELPATH[] = "some_file.txt";
249         uint64_t ino = 42;
250
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);
256
257         ASSERT_NE(0, unlink(FULLPATH));
258         ASSERT_EQ(EPERM, errno);
259 }
260
261 /*
262  * Unlink a file whose parent diretory's sticky bit is set
263  *
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
267  */
268 TEST_F(Access, unlink_sticky_directory)
269 {
270         const char FULLPATH[] = "mountpoint/some_file.txt";
271         const char RELPATH[] = "some_file.txt";
272         uint64_t ino = 42;
273
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);
281                 }, Eq(true)),
282                 _)
283         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
284         {
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;
290         })));
291         EXPECT_CALL(*m_mock, process(
292                 ResultOf([=](auto in) {
293                         return (in.header.opcode == FUSE_ACCESS &&
294                                 in.header.nodeid == ino);
295                 }, Eq(true)),
296                 _)
297         ).Times(0);
298         expect_lookup(RELPATH, ino);
299         expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM);
300
301         ASSERT_EQ(-1, unlink(FULLPATH));
302         ASSERT_EQ(EPERM, errno);
303 }