Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/lookup.cc
39536 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
#include <sys/mount.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 Lookup: public FuseTest {};
45
46
class Lookup_7_8: public Lookup {
47
public:
48
virtual void SetUp() {
49
m_kernel_minor_version = 8;
50
Lookup::SetUp();
51
}
52
};
53
54
class LookupExportable: public Lookup {
55
public:
56
virtual void SetUp() {
57
m_init_flags = FUSE_EXPORT_SUPPORT;
58
Lookup::SetUp();
59
}
60
};
61
62
/*
63
* If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs
64
* should use the cached attributes, rather than query the daemon
65
*/
66
TEST_F(Lookup, attr_cache)
67
{
68
const char FULLPATH[] = "mountpoint/some_file.txt";
69
const char RELPATH[] = "some_file.txt";
70
const uint64_t ino = 42;
71
const uint64_t generation = 13;
72
struct stat sb;
73
74
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
75
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
76
SET_OUT_HEADER_LEN(out, entry);
77
out.body.entry.nodeid = ino;
78
out.body.entry.attr_valid = UINT64_MAX;
79
out.body.entry.attr.ino = ino; // Must match nodeid
80
out.body.entry.attr.mode = S_IFREG | 0644;
81
out.body.entry.attr.size = 1;
82
out.body.entry.attr.blocks = 2;
83
out.body.entry.attr.atime = 3;
84
out.body.entry.attr.mtime = 4;
85
out.body.entry.attr.ctime = 5;
86
out.body.entry.attr.atimensec = 6;
87
out.body.entry.attr.mtimensec = 7;
88
out.body.entry.attr.ctimensec = 8;
89
out.body.entry.attr.nlink = 9;
90
out.body.entry.attr.uid = 10;
91
out.body.entry.attr.gid = 11;
92
out.body.entry.attr.rdev = 12;
93
out.body.entry.generation = generation;
94
})));
95
/* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */
96
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
97
EXPECT_EQ(1, sb.st_size);
98
EXPECT_EQ(2, sb.st_blocks);
99
EXPECT_EQ(3, sb.st_atim.tv_sec);
100
EXPECT_EQ(6, sb.st_atim.tv_nsec);
101
EXPECT_EQ(4, sb.st_mtim.tv_sec);
102
EXPECT_EQ(7, sb.st_mtim.tv_nsec);
103
EXPECT_EQ(5, sb.st_ctim.tv_sec);
104
EXPECT_EQ(8, sb.st_ctim.tv_nsec);
105
EXPECT_EQ(9ull, sb.st_nlink);
106
EXPECT_EQ(10ul, sb.st_uid);
107
EXPECT_EQ(11ul, sb.st_gid);
108
EXPECT_EQ(12ul, sb.st_rdev);
109
EXPECT_EQ(ino, sb.st_ino);
110
EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
111
112
// fuse(4) does not _yet_ support inode generations
113
//EXPECT_EQ(generation, sb.st_gen);
114
115
/*
116
* st_birthtim and st_flags are not supported by the fuse protocol.
117
* They're only supported as OS-specific extensions to OSX. For
118
* birthtime, the convention for "not supported" is "negative one
119
* second".
120
*/
121
EXPECT_EQ(-1, sb.st_birthtim.tv_sec);
122
EXPECT_EQ(0, sb.st_birthtim.tv_nsec);
123
EXPECT_EQ(0u, sb.st_flags);
124
}
125
126
/*
127
* If lookup returns a finite but non-zero cache timeout, then we should discard
128
* the cached attributes and requery the daemon.
129
*/
130
TEST_F(Lookup, attr_cache_timeout)
131
{
132
const char FULLPATH[] = "mountpoint/some_file.txt";
133
const char RELPATH[] = "some_file.txt";
134
const uint64_t ino = 42;
135
struct stat sb;
136
137
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
138
.Times(2)
139
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
140
SET_OUT_HEADER_LEN(out, entry);
141
out.body.entry.nodeid = ino;
142
out.body.entry.attr_valid_nsec = NAP_NS / 2;
143
out.body.entry.attr.ino = ino; // Must match nodeid
144
out.body.entry.attr.mode = S_IFREG | 0644;
145
})));
146
147
/* access(2) will issue a VOP_LOOKUP and fill the attr cache */
148
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
149
/* Next access(2) will use the cached attributes */
150
nap();
151
/* The cache has timed out; VOP_GETATTR should query the daemon*/
152
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
153
}
154
155
TEST_F(Lookup, dot)
156
{
157
const char FULLPATH[] = "mountpoint/some_dir/.";
158
const char RELDIRPATH[] = "some_dir";
159
uint64_t ino = 42;
160
161
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
162
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
163
SET_OUT_HEADER_LEN(out, entry);
164
out.body.entry.attr.mode = S_IFDIR | 0755;
165
out.body.entry.nodeid = ino;
166
out.body.entry.attr_valid = UINT64_MAX;
167
out.body.entry.entry_valid = UINT64_MAX;
168
})));
169
170
/*
171
* access(2) is one of the few syscalls that will not (always) follow
172
* up a successful VOP_LOOKUP with another VOP.
173
*/
174
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
175
}
176
177
TEST_F(Lookup, dotdot)
178
{
179
const char FULLPATH[] = "mountpoint/some_dir/..";
180
const char RELDIRPATH[] = "some_dir";
181
182
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
183
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
184
SET_OUT_HEADER_LEN(out, entry);
185
out.body.entry.attr.mode = S_IFDIR | 0755;
186
out.body.entry.nodeid = 14;
187
out.body.entry.attr_valid = UINT64_MAX;
188
out.body.entry.entry_valid = UINT64_MAX;
189
})));
190
191
/*
192
* access(2) is one of the few syscalls that will not (always) follow
193
* up a successful VOP_LOOKUP with another VOP.
194
*/
195
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
196
}
197
198
/*
199
* Lookup ".." when that vnode's entry cache has timed out, but its child's
200
* hasn't. Since this file system doesn't set FUSE_EXPORT_SUPPORT, we have no
201
* choice but to use the cached entry, even though it expired.
202
*/
203
TEST_F(Lookup, dotdot_entry_cache_timeout)
204
{
205
uint64_t foo_ino = 42;
206
uint64_t bar_ino = 43;
207
208
EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
209
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
210
SET_OUT_HEADER_LEN(out, entry);
211
out.body.entry.attr.mode = S_IFDIR | 0755;
212
out.body.entry.nodeid = foo_ino;
213
out.body.entry.attr_valid = UINT64_MAX;
214
out.body.entry.entry_valid = 0; // immediate timeout
215
})));
216
EXPECT_LOOKUP(foo_ino, "bar")
217
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
218
SET_OUT_HEADER_LEN(out, entry);
219
out.body.entry.attr.mode = S_IFDIR | 0755;
220
out.body.entry.nodeid = bar_ino;
221
out.body.entry.attr_valid = UINT64_MAX;
222
out.body.entry.entry_valid = UINT64_MAX;
223
})));
224
expect_opendir(bar_ino);
225
226
int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
227
ASSERT_LE(0, fd) << strerror(errno);
228
EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
229
}
230
231
/*
232
* Lookup ".." for a vnode with no valid parent nid
233
* Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974
234
* Since the file system is not exportable, we have no choice but to return an
235
* error.
236
*/
237
TEST_F(Lookup, dotdot_no_parent_nid)
238
{
239
uint64_t foo_ino = 42;
240
uint64_t bar_ino = 43;
241
int fd;
242
243
EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
244
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
245
SET_OUT_HEADER_LEN(out, entry);
246
out.body.entry.attr.mode = S_IFDIR | 0755;
247
out.body.entry.nodeid = foo_ino;
248
out.body.entry.attr_valid = UINT64_MAX;
249
out.body.entry.entry_valid = UINT64_MAX;
250
})));
251
EXPECT_LOOKUP(foo_ino, "bar")
252
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
253
SET_OUT_HEADER_LEN(out, entry);
254
out.body.entry.attr.mode = S_IFDIR | 0755;
255
out.body.entry.nodeid = bar_ino;
256
out.body.entry.attr_valid = UINT64_MAX;
257
out.body.entry.entry_valid = UINT64_MAX;
258
})));
259
EXPECT_CALL(*m_mock, process(
260
ResultOf([=](auto in) {
261
return (in.header.opcode == FUSE_OPENDIR);
262
}, Eq(true)),
263
_)
264
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
265
SET_OUT_HEADER_LEN(out, open);
266
})));
267
expect_forget(foo_ino, 1, NULL);
268
269
fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
270
ASSERT_LE(0, fd) << strerror(errno);
271
// Try (and fail) to unmount the file system, to reclaim the mountpoint
272
// and foo vnodes.
273
ASSERT_NE(0, unmount("mountpoint", 0));
274
EXPECT_EQ(EBUSY, errno);
275
nap(); // Because vnode reclamation is asynchronous
276
EXPECT_NE(0, faccessat(fd, "../..", F_OK, 0));
277
EXPECT_EQ(ESTALE, errno);
278
}
279
280
/*
281
* A daemon that returns an illegal error value should be handled gracefully.
282
* Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263220
283
*/
284
TEST_F(Lookup, ejustreturn)
285
{
286
const char FULLPATH[] = "mountpoint/does_not_exist";
287
const char RELPATH[] = "does_not_exist";
288
289
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
290
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
291
out.header.len = sizeof(out.header);
292
out.header.error = 2;
293
out.expected_errno = EINVAL;
294
})));
295
296
EXPECT_NE(0, access(FULLPATH, F_OK));
297
298
EXPECT_EQ(EIO, errno);
299
}
300
301
TEST_F(Lookup, enoent)
302
{
303
const char FULLPATH[] = "mountpoint/does_not_exist";
304
const char RELPATH[] = "does_not_exist";
305
306
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
307
.WillOnce(Invoke(ReturnErrno(ENOENT)));
308
EXPECT_NE(0, access(FULLPATH, F_OK));
309
EXPECT_EQ(ENOENT, errno);
310
}
311
312
TEST_F(Lookup, enotdir)
313
{
314
const char FULLPATH[] = "mountpoint/not_a_dir/some_file.txt";
315
const char RELPATH[] = "not_a_dir";
316
317
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
318
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
319
SET_OUT_HEADER_LEN(out, entry);
320
out.body.entry.entry_valid = UINT64_MAX;
321
out.body.entry.attr.mode = S_IFREG | 0644;
322
out.body.entry.nodeid = 42;
323
})));
324
325
ASSERT_EQ(-1, access(FULLPATH, F_OK));
326
ASSERT_EQ(ENOTDIR, errno);
327
}
328
329
/*
330
* If lookup returns a non-zero entry timeout, then subsequent VOP_LOOKUPs
331
* should use the cached inode rather than requery the daemon
332
*/
333
TEST_F(Lookup, entry_cache)
334
{
335
const char FULLPATH[] = "mountpoint/some_file.txt";
336
const char RELPATH[] = "some_file.txt";
337
338
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
339
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
340
SET_OUT_HEADER_LEN(out, entry);
341
out.body.entry.entry_valid = UINT64_MAX;
342
out.body.entry.attr.mode = S_IFREG | 0644;
343
out.body.entry.nodeid = 14;
344
})));
345
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
346
/* The second access(2) should use the cache */
347
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
348
}
349
350
/*
351
* If the daemon returns an error of 0 and an inode of 0, that's a flag for
352
* "ENOENT and cache it" with the given entry_timeout
353
*/
354
TEST_F(Lookup, entry_cache_negative)
355
{
356
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
357
358
EXPECT_LOOKUP(FUSE_ROOT_ID, "does_not_exist")
359
.Times(1)
360
.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)));
361
362
EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
363
EXPECT_EQ(ENOENT, errno);
364
EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
365
EXPECT_EQ(ENOENT, errno);
366
}
367
368
/* Negative entry caches should timeout, too */
369
TEST_F(Lookup, entry_cache_negative_timeout)
370
{
371
const char *RELPATH = "does_not_exist";
372
const char *FULLPATH = "mountpoint/does_not_exist";
373
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = NAP_NS / 2};
374
375
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
376
.Times(2)
377
.WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid)));
378
379
EXPECT_NE(0, access(FULLPATH, F_OK));
380
EXPECT_EQ(ENOENT, errno);
381
382
nap();
383
384
/* The cache has timed out; VOP_LOOKUP should requery the daemon*/
385
EXPECT_NE(0, access(FULLPATH, F_OK));
386
EXPECT_EQ(ENOENT, errno);
387
}
388
389
/*
390
* If lookup returns a finite but non-zero entry cache timeout, then we should
391
* discard the cached inode and requery the daemon
392
*/
393
TEST_F(Lookup, entry_cache_timeout)
394
{
395
const char FULLPATH[] = "mountpoint/some_file.txt";
396
const char RELPATH[] = "some_file.txt";
397
398
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
399
.Times(2)
400
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
401
SET_OUT_HEADER_LEN(out, entry);
402
out.body.entry.entry_valid_nsec = NAP_NS / 2;
403
out.body.entry.attr.mode = S_IFREG | 0644;
404
out.body.entry.nodeid = 14;
405
})));
406
407
/* access(2) will issue a VOP_LOOKUP and fill the entry cache */
408
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
409
/* Next access(2) will use the cached entry */
410
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
411
nap();
412
/* The cache has timed out; VOP_LOOKUP should requery the daemon*/
413
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
414
}
415
416
TEST_F(Lookup, ok)
417
{
418
const char FULLPATH[] = "mountpoint/some_file.txt";
419
const char RELPATH[] = "some_file.txt";
420
421
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
422
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
423
SET_OUT_HEADER_LEN(out, entry);
424
out.body.entry.attr.mode = S_IFREG | 0644;
425
out.body.entry.nodeid = 14;
426
})));
427
/*
428
* access(2) is one of the few syscalls that will not (always) follow
429
* up a successful VOP_LOOKUP with another VOP.
430
*/
431
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
432
}
433
434
/*
435
* Lookup in a subdirectory of the fuse mount. The naughty server returns the
436
* same inode for the child as for the parent.
437
*/
438
TEST_F(Lookup, parent_inode)
439
{
440
const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
441
const char DIRPATH[] = "some_dir";
442
const char RELPATH[] = "some_file.txt";
443
uint64_t dir_ino = 2;
444
445
EXPECT_LOOKUP(FUSE_ROOT_ID, DIRPATH)
446
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
447
SET_OUT_HEADER_LEN(out, entry);
448
out.body.entry.attr.mode = S_IFDIR | 0755;
449
out.body.entry.nodeid = dir_ino;
450
})));
451
EXPECT_LOOKUP(dir_ino, RELPATH)
452
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
453
SET_OUT_HEADER_LEN(out, entry);
454
out.body.entry.attr.mode = S_IFREG | 0644;
455
out.body.entry.nodeid = dir_ino;
456
})));
457
/*
458
* access(2) is one of the few syscalls that will not (always) follow
459
* up a successful VOP_LOOKUP with another VOP.
460
*/
461
ASSERT_EQ(-1, access(FULLPATH, F_OK));
462
ASSERT_EQ(EIO, errno);
463
}
464
465
// Lookup in a subdirectory of the fuse mount
466
TEST_F(Lookup, subdir)
467
{
468
const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
469
const char DIRPATH[] = "some_dir";
470
const char RELPATH[] = "some_file.txt";
471
uint64_t dir_ino = 2;
472
uint64_t file_ino = 3;
473
474
EXPECT_LOOKUP(FUSE_ROOT_ID, DIRPATH)
475
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
476
SET_OUT_HEADER_LEN(out, entry);
477
out.body.entry.attr.mode = S_IFDIR | 0755;
478
out.body.entry.nodeid = dir_ino;
479
})));
480
EXPECT_LOOKUP(dir_ino, RELPATH)
481
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
482
SET_OUT_HEADER_LEN(out, entry);
483
out.body.entry.attr.mode = S_IFREG | 0644;
484
out.body.entry.nodeid = file_ino;
485
})));
486
/*
487
* access(2) is one of the few syscalls that will not (always) follow
488
* up a successful VOP_LOOKUP with another VOP.
489
*/
490
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
491
}
492
493
/*
494
* The server returns two different vtypes for the same nodeid. This is
495
* technically allowed if the entry's cache has already expired.
496
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=258022
497
*/
498
TEST_F(Lookup, vtype_conflict)
499
{
500
const char FIRSTFULLPATH[] = "mountpoint/foo";
501
const char SECONDFULLPATH[] = "mountpoint/bar";
502
const char FIRSTRELPATH[] = "foo";
503
const char SECONDRELPATH[] = "bar";
504
uint64_t ino = 42;
505
506
EXPECT_LOOKUP(FUSE_ROOT_ID, FIRSTRELPATH)
507
.WillOnce(Invoke(
508
ReturnImmediate([=](auto in __unused, auto& out) {
509
SET_OUT_HEADER_LEN(out, entry);
510
out.body.entry.attr.mode = S_IFDIR | 0644;
511
out.body.entry.nodeid = ino;
512
out.body.entry.attr.nlink = 1;
513
})));
514
expect_lookup(SECONDRELPATH, ino, S_IFREG | 0755, 0, 1, UINT64_MAX);
515
// VOP_FORGET happens asynchronously, so it may or may not arrive
516
// before the test completes.
517
EXPECT_CALL(*m_mock, process(
518
ResultOf([=](auto in) {
519
return (in.header.opcode == FUSE_FORGET &&
520
in.header.nodeid == ino &&
521
in.body.forget.nlookup == 1);
522
}, Eq(true)),
523
_)
524
).Times(AtMost(1))
525
.WillOnce(Invoke([=](auto in __unused, auto &out __unused) { }));
526
527
ASSERT_EQ(0, access(FIRSTFULLPATH, F_OK)) << strerror(errno);
528
EXPECT_EQ(0, access(SECONDFULLPATH, F_OK)) << strerror(errno);
529
}
530
531
TEST_F(Lookup_7_8, ok)
532
{
533
const char FULLPATH[] = "mountpoint/some_file.txt";
534
const char RELPATH[] = "some_file.txt";
535
536
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
537
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
538
SET_OUT_HEADER_LEN(out, entry_7_8);
539
out.body.entry.attr.mode = S_IFREG | 0644;
540
out.body.entry.nodeid = 14;
541
})));
542
/*
543
* access(2) is one of the few syscalls that will not (always) follow
544
* up a successful VOP_LOOKUP with another VOP.
545
*/
546
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
547
}
548
549
/*
550
* Lookup ".." when that vnode's entry cache has timed out, but its child's
551
* hasn't.
552
*/
553
TEST_F(LookupExportable, dotdot_entry_cache_timeout)
554
{
555
uint64_t foo_ino = 42;
556
uint64_t bar_ino = 43;
557
558
EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
559
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
560
SET_OUT_HEADER_LEN(out, entry);
561
out.body.entry.attr.mode = S_IFDIR | 0755;
562
out.body.entry.nodeid = foo_ino;
563
out.body.entry.attr.ino = foo_ino;
564
out.body.entry.attr_valid = UINT64_MAX;
565
out.body.entry.entry_valid = 0; // immediate timeout
566
})));
567
EXPECT_LOOKUP(foo_ino, "bar")
568
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
569
SET_OUT_HEADER_LEN(out, entry);
570
out.body.entry.attr.mode = S_IFDIR | 0755;
571
out.body.entry.nodeid = bar_ino;
572
out.body.entry.attr.ino = bar_ino;
573
out.body.entry.attr_valid = UINT64_MAX;
574
out.body.entry.entry_valid = UINT64_MAX;
575
})));
576
expect_opendir(bar_ino);
577
EXPECT_LOOKUP(foo_ino, "..")
578
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
579
SET_OUT_HEADER_LEN(out, entry);
580
out.body.entry.attr.mode = S_IFDIR | 0755;
581
out.body.entry.nodeid = FUSE_ROOT_ID;
582
out.body.entry.attr.ino = FUSE_ROOT_ID;
583
out.body.entry.attr_valid = UINT64_MAX;
584
out.body.entry.entry_valid = UINT64_MAX;
585
})));
586
587
int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
588
ASSERT_LE(0, fd) << strerror(errno);
589
/* FreeBSD's fusefs driver always uses the same cache expiration time
590
* for ".." as for the directory itself. So we need to look up two
591
* levels to find an expired ".." cache entry.
592
*/
593
EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
594
}
595
596
/*
597
* Lookup ".." for a vnode with no valid parent nid
598
* Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974
599
* Since the file system is exportable, we should resolve the problem by
600
* sending a FUSE_LOOKUP for "..".
601
*/
602
TEST_F(LookupExportable, dotdot_no_parent_nid)
603
{
604
uint64_t foo_ino = 42;
605
uint64_t bar_ino = 43;
606
int fd;
607
608
EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
609
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
610
SET_OUT_HEADER_LEN(out, entry);
611
out.body.entry.attr.mode = S_IFDIR | 0755;
612
out.body.entry.nodeid = foo_ino;
613
out.body.entry.attr.ino = foo_ino;
614
out.body.entry.attr_valid = UINT64_MAX;
615
out.body.entry.entry_valid = UINT64_MAX;
616
})));
617
EXPECT_LOOKUP(foo_ino, "bar")
618
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
619
SET_OUT_HEADER_LEN(out, entry);
620
out.body.entry.attr.mode = S_IFDIR | 0755;
621
out.body.entry.nodeid = bar_ino;
622
out.body.entry.attr.ino = bar_ino;
623
out.body.entry.attr_valid = UINT64_MAX;
624
out.body.entry.entry_valid = UINT64_MAX;
625
})));
626
EXPECT_CALL(*m_mock, process(
627
ResultOf([=](auto in) {
628
return (in.header.opcode == FUSE_OPENDIR);
629
}, Eq(true)),
630
_)
631
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
632
SET_OUT_HEADER_LEN(out, open);
633
})));
634
expect_forget(foo_ino, 1, NULL);
635
EXPECT_LOOKUP(bar_ino, "..")
636
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
637
SET_OUT_HEADER_LEN(out, entry);
638
out.body.entry.attr.mode = S_IFDIR | 0755;
639
out.body.entry.nodeid = foo_ino;
640
out.body.entry.attr.ino = foo_ino;
641
out.body.entry.attr_valid = UINT64_MAX;
642
out.body.entry.entry_valid = UINT64_MAX;
643
})));
644
EXPECT_LOOKUP(foo_ino, "..")
645
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
646
SET_OUT_HEADER_LEN(out, entry);
647
out.body.entry.attr.mode = S_IFDIR | 0755;
648
out.body.entry.nodeid = FUSE_ROOT_ID;
649
out.body.entry.attr.ino = FUSE_ROOT_ID;
650
out.body.entry.attr_valid = UINT64_MAX;
651
out.body.entry.entry_valid = UINT64_MAX;
652
})));
653
654
fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
655
ASSERT_LE(0, fd) << strerror(errno);
656
// Try (and fail) to unmount the file system, to reclaim the mountpoint
657
// and foo vnodes.
658
ASSERT_NE(0, unmount("mountpoint", 0));
659
EXPECT_EQ(EBUSY, errno);
660
nap(); // Because vnode reclamation is asynchronous
661
EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
662
}
663
664