Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/nfs.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
/* This file tests functionality needed by NFS servers */
32
extern "C" {
33
#include <sys/param.h>
34
#include <sys/mount.h>
35
36
#include <dirent.h>
37
#include <fcntl.h>
38
#include <unistd.h>
39
}
40
41
#include "mockfs.hh"
42
#include "utils.hh"
43
44
using namespace std;
45
using namespace testing;
46
47
48
class Nfs: public FuseTest {
49
public:
50
virtual void SetUp() {
51
if (geteuid() != 0)
52
GTEST_SKIP() << "This test requires a privileged user";
53
FuseTest::SetUp();
54
}
55
};
56
57
class Exportable: public Nfs {
58
public:
59
virtual void SetUp() {
60
m_init_flags = FUSE_EXPORT_SUPPORT;
61
Nfs::SetUp();
62
}
63
};
64
65
class Fhstat: public Exportable {};
66
class FhstatNotExportable: public Nfs {};
67
class Getfh: public Exportable {};
68
class Readdir: public Exportable {};
69
70
/* If the server returns a different generation number, then file is stale */
71
TEST_F(Fhstat, estale)
72
{
73
const char FULLPATH[] = "mountpoint/some_dir/.";
74
const char RELDIRPATH[] = "some_dir";
75
fhandle_t fhp;
76
struct stat sb;
77
const uint64_t ino = 42;
78
const mode_t mode = S_IFDIR | 0755;
79
Sequence seq;
80
81
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
82
.InSequence(seq)
83
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
84
SET_OUT_HEADER_LEN(out, entry);
85
out.body.entry.attr.mode = mode;
86
out.body.entry.nodeid = ino;
87
out.body.entry.attr.ino = ino;
88
out.body.entry.generation = 1;
89
out.body.entry.attr_valid = UINT64_MAX;
90
out.body.entry.entry_valid = 0;
91
})));
92
93
EXPECT_LOOKUP(ino, ".")
94
.InSequence(seq)
95
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
96
SET_OUT_HEADER_LEN(out, entry);
97
out.body.entry.attr.mode = mode;
98
out.body.entry.nodeid = ino;
99
out.body.entry.attr.ino = ino;
100
out.body.entry.generation = 2;
101
out.body.entry.attr_valid = UINT64_MAX;
102
out.body.entry.entry_valid = 0;
103
})));
104
105
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
106
ASSERT_EQ(-1, fhstat(&fhp, &sb));
107
EXPECT_EQ(ESTALE, errno);
108
}
109
110
/* If we must lookup an entry from the server, send a LOOKUP request for "." */
111
TEST_F(Fhstat, lookup_dot)
112
{
113
const char FULLPATH[] = "mountpoint/some_dir/.";
114
const char RELDIRPATH[] = "some_dir";
115
fhandle_t fhp;
116
struct stat sb;
117
const uint64_t ino = 42;
118
const mode_t mode = S_IFDIR | 0755;
119
const uid_t uid = 12345;
120
121
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
122
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
123
SET_OUT_HEADER_LEN(out, entry);
124
out.body.entry.attr.mode = mode;
125
out.body.entry.nodeid = ino;
126
out.body.entry.attr.ino = ino;
127
out.body.entry.generation = 1;
128
out.body.entry.attr.uid = uid;
129
out.body.entry.attr_valid = UINT64_MAX;
130
out.body.entry.entry_valid = 0;
131
})));
132
133
EXPECT_LOOKUP(ino, ".")
134
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
135
SET_OUT_HEADER_LEN(out, entry);
136
out.body.entry.attr.mode = mode;
137
out.body.entry.nodeid = ino;
138
out.body.entry.attr.ino = ino;
139
out.body.entry.generation = 1;
140
out.body.entry.attr.uid = uid;
141
out.body.entry.attr_valid = UINT64_MAX;
142
out.body.entry.entry_valid = 0;
143
})));
144
145
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
146
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
147
EXPECT_EQ(uid, sb.st_uid);
148
EXPECT_EQ(mode, sb.st_mode);
149
}
150
151
/* Gracefully handle failures to lookup ".". */
152
TEST_F(Fhstat, lookup_dot_error)
153
{
154
const char FULLPATH[] = "mountpoint/some_dir/.";
155
const char RELDIRPATH[] = "some_dir";
156
fhandle_t fhp;
157
struct stat sb;
158
const uint64_t ino = 42;
159
const mode_t mode = S_IFDIR | 0755;
160
const uid_t uid = 12345;
161
162
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
163
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
164
SET_OUT_HEADER_LEN(out, entry);
165
out.body.entry.attr.mode = mode;
166
out.body.entry.nodeid = ino;
167
out.body.entry.attr.ino = ino;
168
out.body.entry.generation = 1;
169
out.body.entry.attr.uid = uid;
170
out.body.entry.attr_valid = UINT64_MAX;
171
out.body.entry.entry_valid = 0;
172
})));
173
174
EXPECT_LOOKUP(ino, ".")
175
.WillOnce(Invoke(ReturnErrno(EDOOFUS)));
176
177
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
178
ASSERT_EQ(-1, fhstat(&fhp, &sb));
179
EXPECT_EQ(EDOOFUS, errno);
180
}
181
182
/* Use a file handle whose entry is still cached */
183
TEST_F(Fhstat, cached)
184
{
185
const char FULLPATH[] = "mountpoint/some_dir/.";
186
const char RELDIRPATH[] = "some_dir";
187
fhandle_t fhp;
188
struct stat sb;
189
const uint64_t ino = 42;
190
const mode_t mode = S_IFDIR | 0755;
191
192
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
193
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
194
SET_OUT_HEADER_LEN(out, entry);
195
out.body.entry.attr.mode = mode;
196
out.body.entry.nodeid = ino;
197
out.body.entry.attr.ino = ino;
198
out.body.entry.generation = 1;
199
out.body.entry.attr.ino = ino;
200
out.body.entry.attr_valid = UINT64_MAX;
201
out.body.entry.entry_valid = UINT64_MAX;
202
})));
203
204
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
205
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
206
EXPECT_EQ(ino, sb.st_ino);
207
}
208
209
/* File handle entries should expire from the cache, too */
210
TEST_F(Fhstat, cache_expired)
211
{
212
const char FULLPATH[] = "mountpoint/some_dir/.";
213
const char RELDIRPATH[] = "some_dir";
214
fhandle_t fhp;
215
struct stat sb;
216
const uint64_t ino = 42;
217
const mode_t mode = S_IFDIR | 0755;
218
219
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
220
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
221
SET_OUT_HEADER_LEN(out, entry);
222
out.body.entry.attr.mode = mode;
223
out.body.entry.nodeid = ino;
224
out.body.entry.attr.ino = ino;
225
out.body.entry.generation = 1;
226
out.body.entry.attr.ino = ino;
227
out.body.entry.attr_valid = UINT64_MAX;
228
out.body.entry.entry_valid_nsec = NAP_NS / 2;
229
})));
230
231
EXPECT_LOOKUP(ino, ".")
232
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
233
SET_OUT_HEADER_LEN(out, entry);
234
out.body.entry.attr.mode = mode;
235
out.body.entry.nodeid = ino;
236
out.body.entry.attr.ino = ino;
237
out.body.entry.generation = 1;
238
out.body.entry.attr.ino = ino;
239
out.body.entry.attr_valid = UINT64_MAX;
240
out.body.entry.entry_valid = 0;
241
})));
242
243
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
244
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
245
EXPECT_EQ(ino, sb.st_ino);
246
247
nap();
248
249
/* Cache should be expired; fuse should issue a FUSE_LOOKUP */
250
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
251
EXPECT_EQ(ino, sb.st_ino);
252
}
253
254
/*
255
* If the server returns a FUSE_LOOKUP response for a nodeid that we didn't
256
* lookup, it's a bug. But we should handle it gracefully.
257
*/
258
TEST_F(Fhstat, inconsistent_nodeid)
259
{
260
const char FULLPATH[] = "mountpoint/some_dir/.";
261
const char RELDIRPATH[] = "some_dir";
262
fhandle_t fhp;
263
struct stat sb;
264
const uint64_t ino_in = 42;
265
const uint64_t ino_out = 43;
266
const mode_t mode = S_IFDIR | 0755;
267
const uid_t uid = 12345;
268
269
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
270
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
271
SET_OUT_HEADER_LEN(out, entry);
272
out.body.entry.nodeid = ino_in;
273
out.body.entry.attr.ino = ino_in;
274
out.body.entry.attr.mode = mode;
275
out.body.entry.generation = 1;
276
out.body.entry.attr.uid = uid;
277
out.body.entry.attr_valid = UINT64_MAX;
278
out.body.entry.entry_valid = 0;
279
})));
280
281
EXPECT_LOOKUP(ino_in, ".")
282
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
283
SET_OUT_HEADER_LEN(out, entry);
284
out.body.entry.nodeid = ino_out;
285
out.body.entry.attr.ino = ino_out;
286
out.body.entry.attr.mode = mode;
287
out.body.entry.generation = 1;
288
out.body.entry.attr.uid = uid;
289
out.body.entry.attr_valid = UINT64_MAX;
290
out.body.entry.entry_valid = 0;
291
})));
292
293
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
294
EXPECT_NE(0, fhstat(&fhp, &sb)) << strerror(errno);
295
EXPECT_EQ(EIO, errno);
296
}
297
298
/*
299
* If the server returns a FUSE_LOOKUP response where the nodeid doesn't match
300
* the inode number, and the file system is exported, it's a bug. But we
301
* should handle it gracefully.
302
*/
303
TEST_F(Fhstat, inconsistent_ino)
304
{
305
const char FULLPATH[] = "mountpoint/some_dir/.";
306
const char RELDIRPATH[] = "some_dir";
307
fhandle_t fhp;
308
struct stat sb;
309
const uint64_t nodeid = 42;
310
const uint64_t ino = 711; // Could be anything that != nodeid
311
const mode_t mode = S_IFDIR | 0755;
312
const uid_t uid = 12345;
313
314
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
315
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
316
SET_OUT_HEADER_LEN(out, entry);
317
out.body.entry.nodeid = nodeid;
318
out.body.entry.attr.ino = nodeid;
319
out.body.entry.attr.mode = mode;
320
out.body.entry.generation = 1;
321
out.body.entry.attr.uid = uid;
322
out.body.entry.attr_valid = UINT64_MAX;
323
out.body.entry.entry_valid = 0;
324
})));
325
326
EXPECT_LOOKUP(nodeid, ".")
327
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
328
SET_OUT_HEADER_LEN(out, entry);
329
out.body.entry.nodeid = nodeid;
330
out.body.entry.attr.ino = ino;
331
out.body.entry.attr.mode = mode;
332
out.body.entry.generation = 1;
333
out.body.entry.attr.uid = uid;
334
out.body.entry.attr_valid = UINT64_MAX;
335
out.body.entry.entry_valid = 0;
336
})));
337
338
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
339
/*
340
* The fhstat operation will actually succeed. But future operations
341
* will likely fail.
342
*/
343
ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
344
EXPECT_EQ(ino, sb.st_ino);
345
}
346
347
/*
348
* If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
349
* lookups
350
*/
351
TEST_F(FhstatNotExportable, lookup_dot)
352
{
353
const char FULLPATH[] = "mountpoint/some_dir/.";
354
const char RELDIRPATH[] = "some_dir";
355
fhandle_t fhp;
356
const uint64_t ino = 42;
357
const mode_t mode = S_IFDIR | 0755;
358
359
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
360
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
361
SET_OUT_HEADER_LEN(out, entry);
362
out.body.entry.attr.mode = mode;
363
out.body.entry.nodeid = ino;
364
out.body.entry.attr.ino = ino;
365
out.body.entry.generation = 1;
366
out.body.entry.attr_valid = UINT64_MAX;
367
out.body.entry.entry_valid = 0;
368
})));
369
370
ASSERT_EQ(-1, getfh(FULLPATH, &fhp));
371
ASSERT_EQ(EOPNOTSUPP, errno);
372
}
373
374
/* FreeBSD's fid struct doesn't have enough space for 64-bit generations */
375
TEST_F(Getfh, eoverflow)
376
{
377
const char FULLPATH[] = "mountpoint/some_dir/.";
378
const char RELDIRPATH[] = "some_dir";
379
fhandle_t fhp;
380
uint64_t ino = 42;
381
382
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
383
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
384
SET_OUT_HEADER_LEN(out, entry);
385
out.body.entry.attr.mode = S_IFDIR | 0755;
386
out.body.entry.nodeid = ino;
387
out.body.entry.attr.ino = ino;
388
out.body.entry.generation = (uint64_t)UINT32_MAX + 1;
389
out.body.entry.attr_valid = UINT64_MAX;
390
out.body.entry.entry_valid = UINT64_MAX;
391
})));
392
393
ASSERT_NE(0, getfh(FULLPATH, &fhp));
394
EXPECT_EQ(EOVERFLOW, errno);
395
}
396
397
/* Get an NFS file handle */
398
TEST_F(Getfh, ok)
399
{
400
const char FULLPATH[] = "mountpoint/some_dir/.";
401
const char RELDIRPATH[] = "some_dir";
402
fhandle_t fhp;
403
uint64_t ino = 42;
404
405
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
406
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
407
SET_OUT_HEADER_LEN(out, entry);
408
out.body.entry.attr.mode = S_IFDIR | 0755;
409
out.body.entry.nodeid = ino;
410
out.body.entry.attr.ino = ino;
411
out.body.entry.attr_valid = UINT64_MAX;
412
out.body.entry.entry_valid = UINT64_MAX;
413
})));
414
415
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
416
}
417
418
/*
419
* Call readdir via a file handle.
420
*
421
* This is how a userspace nfs server like nfs-ganesha or unfs3 would call
422
* readdir. The in-kernel NFS server never does any equivalent of open. I
423
* haven't discovered a way to mimic nfsd's behavior short of actually running
424
* nfsd.
425
*/
426
TEST_F(Readdir, getdirentries)
427
{
428
const char FULLPATH[] = "mountpoint/some_dir";
429
const char RELPATH[] = "some_dir";
430
uint64_t ino = 42;
431
mode_t mode = S_IFDIR | 0755;
432
fhandle_t fhp;
433
int fd;
434
char buf[8192];
435
ssize_t r;
436
437
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
438
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
439
SET_OUT_HEADER_LEN(out, entry);
440
out.body.entry.attr.mode = mode;
441
out.body.entry.nodeid = ino;
442
out.body.entry.attr.ino = ino;
443
out.body.entry.generation = 1;
444
out.body.entry.attr_valid = UINT64_MAX;
445
out.body.entry.entry_valid = 0;
446
})));
447
448
EXPECT_LOOKUP(ino, ".")
449
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
450
SET_OUT_HEADER_LEN(out, entry);
451
out.body.entry.attr.mode = mode;
452
out.body.entry.nodeid = ino;
453
out.body.entry.attr.ino = ino;
454
out.body.entry.generation = 1;
455
out.body.entry.attr_valid = UINT64_MAX;
456
out.body.entry.entry_valid = 0;
457
})));
458
459
expect_opendir(ino);
460
461
EXPECT_CALL(*m_mock, process(
462
ResultOf([=](auto in) {
463
return (in.header.opcode == FUSE_READDIR &&
464
in.header.nodeid == ino &&
465
in.body.readdir.size == sizeof(buf));
466
}, Eq(true)),
467
_)
468
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
469
out.header.error = 0;
470
out.header.len = sizeof(out.header);
471
})));
472
473
ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
474
fd = fhopen(&fhp, O_DIRECTORY);
475
ASSERT_LE(0, fd) << strerror(errno);
476
r = getdirentries(fd, buf, sizeof(buf), 0);
477
ASSERT_EQ(0, r) << strerror(errno);
478
479
leak(fd);
480
}
481
482