Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/access.cc
39537 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
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
31
extern "C" {
32
#include <sys/types.h>
33
#include <sys/extattr.h>
34
35
#include <fcntl.h>
36
#include <unistd.h>
37
}
38
39
#include "mockfs.hh"
40
#include "utils.hh"
41
42
using namespace testing;
43
44
class Access: public FuseTest {
45
public:
46
virtual void SetUp() {
47
FuseTest::SetUp();
48
// Clear the default FUSE_ACCESS expectation
49
Mock::VerifyAndClearExpectations(m_mock);
50
}
51
52
void expect_lookup(const char *relpath, uint64_t ino)
53
{
54
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
55
}
56
57
/*
58
* Expect that FUSE_ACCESS will never be called for the given inode, with any
59
* bits in the supplied access_mask set
60
*/
61
void expect_noaccess(uint64_t ino, mode_t access_mask)
62
{
63
EXPECT_CALL(*m_mock, process(
64
ResultOf([=](auto in) {
65
return (in.header.opcode == FUSE_ACCESS &&
66
in.header.nodeid == ino &&
67
in.body.access.mask & access_mask);
68
}, Eq(true)),
69
_)
70
).Times(0);
71
}
72
73
};
74
75
class RofsAccess: public Access {
76
public:
77
virtual void SetUp() {
78
m_ro = true;
79
Access::SetUp();
80
}
81
};
82
83
/*
84
* Change the mode of a file.
85
*
86
* There should never be a FUSE_ACCESS sent for this operation, except for
87
* search permissions on the parent directory.
88
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
89
*/
90
TEST_F(Access, chmod)
91
{
92
const char FULLPATH[] = "mountpoint/some_file.txt";
93
const char RELPATH[] = "some_file.txt";
94
const uint64_t ino = 42;
95
const mode_t newmode = 0644;
96
97
expect_access(FUSE_ROOT_ID, X_OK, 0);
98
expect_lookup(RELPATH, ino);
99
expect_noaccess(ino, 0);
100
EXPECT_CALL(*m_mock, process(
101
ResultOf([](auto in) {
102
return (in.header.opcode == FUSE_SETATTR &&
103
in.header.nodeid == ino);
104
}, Eq(true)),
105
_)
106
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
107
SET_OUT_HEADER_LEN(out, attr);
108
out.body.attr.attr.ino = ino; // Must match nodeid
109
out.body.attr.attr.mode = S_IFREG | newmode;
110
})));
111
112
EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
113
}
114
115
/*
116
* Create a new file
117
*
118
* There should never be a FUSE_ACCESS sent for this operation, except for
119
* search permissions on the parent directory.
120
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
121
*/
122
TEST_F(Access, create)
123
{
124
const char FULLPATH[] = "mountpoint/some_file.txt";
125
const char RELPATH[] = "some_file.txt";
126
mode_t mode = S_IFREG | 0755;
127
uint64_t ino = 42;
128
129
expect_access(FUSE_ROOT_ID, X_OK, 0);
130
expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK);
131
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
132
.WillOnce(Invoke(ReturnErrno(ENOENT)));
133
expect_noaccess(ino, 0);
134
EXPECT_CALL(*m_mock, process(
135
ResultOf([=](auto in) {
136
return (in.header.opcode == FUSE_CREATE);
137
}, Eq(true)),
138
_)
139
).WillOnce(ReturnErrno(EPERM));
140
141
EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
142
EXPECT_EQ(EPERM, errno);
143
}
144
145
/* The error case of FUSE_ACCESS. */
146
TEST_F(Access, eaccess)
147
{
148
const char FULLPATH[] = "mountpoint/some_file.txt";
149
const char RELPATH[] = "some_file.txt";
150
uint64_t ino = 42;
151
mode_t access_mode = X_OK;
152
153
expect_access(FUSE_ROOT_ID, X_OK, 0);
154
expect_lookup(RELPATH, ino);
155
expect_access(ino, access_mode, EACCES);
156
157
ASSERT_NE(0, access(FULLPATH, access_mode));
158
ASSERT_EQ(EACCES, errno);
159
}
160
161
/*
162
* If the filesystem returns ENOSYS, then it is treated as a permanent success,
163
* and subsequent VOP_ACCESS calls will succeed automatically without querying
164
* the daemon.
165
*/
166
TEST_F(Access, enosys)
167
{
168
const char FULLPATH[] = "mountpoint/some_file.txt";
169
const char RELPATH[] = "some_file.txt";
170
uint64_t ino = 42;
171
mode_t access_mode = R_OK;
172
173
expect_access(FUSE_ROOT_ID, X_OK, ENOSYS);
174
FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
175
176
ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
177
ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
178
}
179
180
TEST_F(RofsAccess, erofs)
181
{
182
const char FULLPATH[] = "mountpoint/some_file.txt";
183
const char RELPATH[] = "some_file.txt";
184
uint64_t ino = 42;
185
mode_t access_mode = W_OK;
186
187
expect_access(FUSE_ROOT_ID, X_OK, 0);
188
expect_lookup(RELPATH, ino);
189
190
ASSERT_NE(0, access(FULLPATH, access_mode));
191
ASSERT_EQ(EROFS, errno);
192
}
193
194
195
/*
196
* Lookup an extended attribute
197
*
198
* There should never be a FUSE_ACCESS sent for this operation, except for
199
* search permissions on the parent directory.
200
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
201
*/
202
TEST_F(Access, Getxattr)
203
{
204
const char FULLPATH[] = "mountpoint/some_file.txt";
205
const char RELPATH[] = "some_file.txt";
206
uint64_t ino = 42;
207
char data[80];
208
int ns = EXTATTR_NAMESPACE_USER;
209
ssize_t r;
210
211
expect_access(FUSE_ROOT_ID, X_OK, 0);
212
expect_lookup(RELPATH, ino);
213
expect_noaccess(ino, 0);
214
expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
215
216
r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
217
ASSERT_EQ(-1, r);
218
ASSERT_EQ(ENOATTR, errno);
219
}
220
221
/* The successful case of FUSE_ACCESS. */
222
TEST_F(Access, ok)
223
{
224
const char FULLPATH[] = "mountpoint/some_file.txt";
225
const char RELPATH[] = "some_file.txt";
226
uint64_t ino = 42;
227
mode_t access_mode = R_OK;
228
229
expect_access(FUSE_ROOT_ID, X_OK, 0);
230
expect_lookup(RELPATH, ino);
231
expect_access(ino, access_mode, 0);
232
233
ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
234
}
235
236
/*
237
* Unlink a file
238
*
239
* There should never be a FUSE_ACCESS sent for this operation, except for
240
* search permissions on the parent directory.
241
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
242
*/
243
TEST_F(Access, unlink)
244
{
245
const char FULLPATH[] = "mountpoint/some_file.txt";
246
const char RELPATH[] = "some_file.txt";
247
uint64_t ino = 42;
248
249
expect_access(FUSE_ROOT_ID, X_OK, 0);
250
expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
251
expect_noaccess(ino, 0);
252
expect_lookup(RELPATH, ino);
253
expect_unlink(1, RELPATH, EPERM);
254
255
ASSERT_NE(0, unlink(FULLPATH));
256
ASSERT_EQ(EPERM, errno);
257
}
258
259
/*
260
* Unlink a file whose parent diretory's sticky bit is set
261
*
262
* There should never be a FUSE_ACCESS sent for this operation, except for
263
* search permissions on the parent directory.
264
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689
265
*/
266
TEST_F(Access, unlink_sticky_directory)
267
{
268
const char FULLPATH[] = "mountpoint/some_file.txt";
269
const char RELPATH[] = "some_file.txt";
270
uint64_t ino = 42;
271
272
expect_access(FUSE_ROOT_ID, X_OK, 0);
273
expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK);
274
expect_noaccess(ino, 0);
275
EXPECT_CALL(*m_mock, process(
276
ResultOf([=](auto in) {
277
return (in.header.opcode == FUSE_GETATTR &&
278
in.header.nodeid == FUSE_ROOT_ID);
279
}, Eq(true)),
280
_)
281
).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out)
282
{
283
SET_OUT_HEADER_LEN(out, attr);
284
out.body.attr.attr.ino = FUSE_ROOT_ID;
285
out.body.attr.attr.mode = S_IFDIR | 01777;
286
out.body.attr.attr.uid = 0;
287
out.body.attr.attr_valid = UINT64_MAX;
288
})));
289
EXPECT_CALL(*m_mock, process(
290
ResultOf([=](auto in) {
291
return (in.header.opcode == FUSE_ACCESS &&
292
in.header.nodeid == ino);
293
}, Eq(true)),
294
_)
295
).Times(0);
296
expect_lookup(RELPATH, ino);
297
expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM);
298
299
ASSERT_EQ(-1, unlink(FULLPATH));
300
ASSERT_EQ(EPERM, errno);
301
}
302
303