Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/getattr.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/param.h>
33
34
#include <semaphore.h>
35
}
36
37
#include "mockfs.hh"
38
#include "utils.hh"
39
40
using namespace testing;
41
42
class Getattr : public FuseTest {
43
public:
44
void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
45
uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
46
{
47
EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
48
.Times(times)
49
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
50
SET_OUT_HEADER_LEN(out, entry);
51
out.body.entry.attr.mode = mode;
52
out.body.entry.nodeid = ino;
53
out.body.entry.attr.nlink = 1;
54
out.body.entry.attr_valid = attr_valid;
55
out.body.entry.attr_valid_nsec = attr_valid_nsec;
56
out.body.entry.attr.size = size;
57
out.body.entry.entry_valid = UINT64_MAX;
58
})));
59
}
60
};
61
62
class Getattr_7_8: public FuseTest {
63
public:
64
virtual void SetUp() {
65
m_kernel_minor_version = 8;
66
FuseTest::SetUp();
67
}
68
};
69
70
/*
71
* If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
72
* should use the cached attributes, rather than query the daemon
73
*/
74
TEST_F(Getattr, attr_cache)
75
{
76
const char FULLPATH[] = "mountpoint/some_file.txt";
77
const char RELPATH[] = "some_file.txt";
78
const uint64_t ino = 42;
79
struct stat sb;
80
81
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
82
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
83
SET_OUT_HEADER_LEN(out, entry);
84
out.body.entry.attr.mode = S_IFREG | 0644;
85
out.body.entry.nodeid = ino;
86
out.body.entry.entry_valid = UINT64_MAX;
87
})));
88
EXPECT_CALL(*m_mock, process(
89
ResultOf([](auto in) {
90
return (in.header.opcode == FUSE_GETATTR &&
91
in.header.nodeid == ino);
92
}, Eq(true)),
93
_)
94
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
95
SET_OUT_HEADER_LEN(out, attr);
96
out.body.attr.attr_valid = UINT64_MAX;
97
out.body.attr.attr.ino = ino; // Must match nodeid
98
out.body.attr.attr.mode = S_IFREG | 0644;
99
})));
100
EXPECT_EQ(0, stat(FULLPATH, &sb));
101
/* The second stat(2) should use cached attributes */
102
EXPECT_EQ(0, stat(FULLPATH, &sb));
103
}
104
105
/*
106
* If getattr returns a finite but non-zero cache timeout, then we should
107
* discard the cached attributes and requery the daemon after the timeout
108
* period passes.
109
*/
110
TEST_F(Getattr, attr_cache_timeout)
111
{
112
const char FULLPATH[] = "mountpoint/some_file.txt";
113
const char RELPATH[] = "some_file.txt";
114
const uint64_t ino = 42;
115
struct stat sb;
116
117
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
118
EXPECT_CALL(*m_mock, process(
119
ResultOf([](auto in) {
120
return (in.header.opcode == FUSE_GETATTR &&
121
in.header.nodeid == ino);
122
}, Eq(true)),
123
_)
124
).Times(2)
125
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
126
SET_OUT_HEADER_LEN(out, attr);
127
out.body.attr.attr_valid_nsec = NAP_NS / 2;
128
out.body.attr.attr_valid = 0;
129
out.body.attr.attr.ino = ino; // Must match nodeid
130
out.body.attr.attr.mode = S_IFREG | 0644;
131
})));
132
133
EXPECT_EQ(0, stat(FULLPATH, &sb));
134
nap();
135
/* Timeout has expired. stat(2) should requery the daemon */
136
EXPECT_EQ(0, stat(FULLPATH, &sb));
137
}
138
139
/*
140
* If attr.blksize is zero, then the kernel should use a default value for
141
* st_blksize
142
*/
143
TEST_F(Getattr, blksize_zero)
144
{
145
const char FULLPATH[] = "mountpoint/some_file.txt";
146
const char RELPATH[] = "some_file.txt";
147
const uint64_t ino = 42;
148
struct stat sb;
149
150
expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
151
EXPECT_CALL(*m_mock, process(
152
ResultOf([](auto in) {
153
return (in.header.opcode == FUSE_GETATTR &&
154
in.header.nodeid == ino);
155
}, Eq(true)),
156
_)
157
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
158
SET_OUT_HEADER_LEN(out, attr);
159
out.body.attr.attr.mode = S_IFREG | 0644;
160
out.body.attr.attr.ino = ino; // Must match nodeid
161
out.body.attr.attr.blksize = 0;
162
out.body.attr.attr.size = 1;
163
})));
164
165
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
166
EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
167
}
168
169
TEST_F(Getattr, enoent)
170
{
171
const char FULLPATH[] = "mountpoint/some_file.txt";
172
const char RELPATH[] = "some_file.txt";
173
struct stat sb;
174
const uint64_t ino = 42;
175
sem_t sem;
176
177
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
178
179
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
180
EXPECT_CALL(*m_mock, process(
181
ResultOf([](auto in) {
182
return (in.header.opcode == FUSE_GETATTR &&
183
in.header.nodeid == ino);
184
}, Eq(true)),
185
_)
186
).WillOnce(Invoke(ReturnErrno(ENOENT)));
187
// Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
188
// and send a FUSE_FORGET
189
expect_forget(ino, 1, &sem);
190
191
EXPECT_NE(0, stat(FULLPATH, &sb));
192
EXPECT_EQ(ENOENT, errno);
193
194
sem_wait(&sem);
195
sem_destroy(&sem);
196
}
197
198
TEST_F(Getattr, ok)
199
{
200
const char FULLPATH[] = "mountpoint/some_file.txt";
201
const char RELPATH[] = "some_file.txt";
202
const uint64_t ino = 42;
203
struct stat sb;
204
205
expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
206
EXPECT_CALL(*m_mock, process(
207
ResultOf([](auto in) {
208
return (in.header.opcode == FUSE_GETATTR &&
209
in.body.getattr.getattr_flags == 0 &&
210
in.header.nodeid == ino);
211
}, Eq(true)),
212
_)
213
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
214
SET_OUT_HEADER_LEN(out, attr);
215
out.body.attr.attr.ino = ino; // Must match nodeid
216
out.body.attr.attr.mode = S_IFREG | 0644;
217
out.body.attr.attr.size = 1;
218
out.body.attr.attr.blocks = 2;
219
out.body.attr.attr.atime = 3;
220
out.body.attr.attr.mtime = 4;
221
out.body.attr.attr.ctime = 5;
222
out.body.attr.attr.atimensec = 6;
223
out.body.attr.attr.mtimensec = 7;
224
out.body.attr.attr.ctimensec = 8;
225
out.body.attr.attr.nlink = 9;
226
out.body.attr.attr.uid = 10;
227
out.body.attr.attr.gid = 11;
228
out.body.attr.attr.rdev = 12;
229
out.body.attr.attr.blksize = 12345;
230
})));
231
232
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
233
EXPECT_EQ(1, sb.st_size);
234
EXPECT_EQ(2, sb.st_blocks);
235
EXPECT_EQ(3, sb.st_atim.tv_sec);
236
EXPECT_EQ(6, sb.st_atim.tv_nsec);
237
EXPECT_EQ(4, sb.st_mtim.tv_sec);
238
EXPECT_EQ(7, sb.st_mtim.tv_nsec);
239
EXPECT_EQ(5, sb.st_ctim.tv_sec);
240
EXPECT_EQ(8, sb.st_ctim.tv_nsec);
241
EXPECT_EQ(9ull, sb.st_nlink);
242
EXPECT_EQ(10ul, sb.st_uid);
243
EXPECT_EQ(11ul, sb.st_gid);
244
EXPECT_EQ(12ul, sb.st_rdev);
245
EXPECT_EQ((blksize_t)12345, sb.st_blksize);
246
EXPECT_EQ(ino, sb.st_ino);
247
EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
248
249
/*
250
* st_birthtim and st_flags are not supported by the fuse protocol.
251
* They're only supported as OS-specific extensions to OSX. For
252
* birthtime, the convention for "not supported" is "negative one
253
* second".
254
*/
255
EXPECT_EQ(-1, sb.st_birthtim.tv_sec);
256
EXPECT_EQ(0, sb.st_birthtim.tv_nsec);
257
EXPECT_EQ(0u, sb.st_flags);
258
}
259
260
/*
261
* FUSE_GETATTR returns a different file type, even though the entry cache
262
* hasn't expired. This is a server bug! It probably means that the server
263
* removed the file and recreated it with the same inode but a different vtyp.
264
* The best thing fusefs can do is return ENOENT to the caller. After all, the
265
* entry must not have existed recently.
266
*/
267
TEST_F(Getattr, vtyp_conflict)
268
{
269
const char FULLPATH[] = "mountpoint/some_file.txt";
270
const char RELPATH[] = "some_file.txt";
271
const uint64_t ino = 42;
272
struct stat sb;
273
sem_t sem;
274
275
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
276
277
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
278
.WillOnce(Invoke(
279
ReturnImmediate([=](auto in __unused, auto& out) {
280
SET_OUT_HEADER_LEN(out, entry);
281
out.body.entry.attr.mode = S_IFREG | 0644;
282
out.body.entry.nodeid = ino;
283
out.body.entry.attr.nlink = 1;
284
out.body.entry.attr_valid = 0;
285
out.body.entry.entry_valid = UINT64_MAX;
286
})));
287
EXPECT_CALL(*m_mock, process(
288
ResultOf([](auto in) {
289
return (in.header.opcode == FUSE_GETATTR &&
290
in.body.getattr.getattr_flags == 0 &&
291
in.header.nodeid == ino);
292
}, Eq(true)),
293
_)
294
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
295
SET_OUT_HEADER_LEN(out, attr);
296
out.body.attr.attr.ino = ino; // Must match nodeid
297
out.body.attr.attr.mode = S_IFDIR | 0755; // Changed!
298
out.body.attr.attr.nlink = 2;
299
})));
300
// We should reclaim stale vnodes
301
expect_forget(ino, 1, &sem);
302
303
ASSERT_NE(0, stat(FULLPATH, &sb));
304
EXPECT_EQ(errno, ENOENT);
305
306
sem_wait(&sem);
307
sem_destroy(&sem);
308
}
309
310
TEST_F(Getattr_7_8, ok)
311
{
312
const char FULLPATH[] = "mountpoint/some_file.txt";
313
const char RELPATH[] = "some_file.txt";
314
const uint64_t ino = 42;
315
struct stat sb;
316
317
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
318
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
319
SET_OUT_HEADER_LEN(out, entry_7_8);
320
out.body.entry.attr.mode = S_IFREG | 0644;
321
out.body.entry.nodeid = ino;
322
out.body.entry.attr.nlink = 1;
323
out.body.entry.attr.size = 1;
324
})));
325
EXPECT_CALL(*m_mock, process(
326
ResultOf([](auto in) {
327
return (in.header.opcode == FUSE_GETATTR &&
328
in.header.nodeid == ino);
329
}, Eq(true)),
330
_)
331
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
332
SET_OUT_HEADER_LEN(out, attr_7_8);
333
out.body.attr.attr.ino = ino; // Must match nodeid
334
out.body.attr.attr.mode = S_IFREG | 0644;
335
out.body.attr.attr.size = 1;
336
out.body.attr.attr.blocks = 2;
337
out.body.attr.attr.atime = 3;
338
out.body.attr.attr.mtime = 4;
339
out.body.attr.attr.ctime = 5;
340
out.body.attr.attr.atimensec = 6;
341
out.body.attr.attr.mtimensec = 7;
342
out.body.attr.attr.ctimensec = 8;
343
out.body.attr.attr.nlink = 9;
344
out.body.attr.attr.uid = 10;
345
out.body.attr.attr.gid = 11;
346
out.body.attr.attr.rdev = 12;
347
})));
348
349
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
350
EXPECT_EQ(1, sb.st_size);
351
EXPECT_EQ(2, sb.st_blocks);
352
EXPECT_EQ(3, sb.st_atim.tv_sec);
353
EXPECT_EQ(6, sb.st_atim.tv_nsec);
354
EXPECT_EQ(4, sb.st_mtim.tv_sec);
355
EXPECT_EQ(7, sb.st_mtim.tv_nsec);
356
EXPECT_EQ(5, sb.st_ctim.tv_sec);
357
EXPECT_EQ(8, sb.st_ctim.tv_nsec);
358
EXPECT_EQ(9ull, sb.st_nlink);
359
EXPECT_EQ(10ul, sb.st_uid);
360
EXPECT_EQ(11ul, sb.st_gid);
361
EXPECT_EQ(12ul, sb.st_rdev);
362
EXPECT_EQ(ino, sb.st_ino);
363
EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
364
365
//st_birthtim and st_flags are not supported by protocol 7.8. They're
366
//only supported as OS-specific extensions to OSX.
367
}
368
369