Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/notify.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
34
#include <fcntl.h>
35
#include <pthread.h>
36
}
37
38
#include "mockfs.hh"
39
#include "utils.hh"
40
41
using namespace testing;
42
43
/*
44
* FUSE asynchonous notification
45
*
46
* FUSE servers can send unprompted notification messages for things like cache
47
* invalidation. This file tests our client's handling of those messages.
48
*/
49
50
class Notify: public FuseTest {
51
public:
52
/* Ignore an optional FUSE_FSYNC */
53
void maybe_expect_fsync(uint64_t ino)
54
{
55
EXPECT_CALL(*m_mock, process(
56
ResultOf([=](auto in) {
57
return (in.header.opcode == FUSE_FSYNC &&
58
in.header.nodeid == ino);
59
}, Eq(true)),
60
_)
61
).WillOnce(Invoke(ReturnErrno(0)));
62
}
63
64
void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino,
65
off_t size, Sequence &seq)
66
{
67
EXPECT_LOOKUP(parent, relpath)
68
.InSequence(seq)
69
.WillOnce(Invoke(
70
ReturnImmediate([=](auto in __unused, auto& out) {
71
SET_OUT_HEADER_LEN(out, entry);
72
out.body.entry.attr.mode = S_IFREG | 0644;
73
out.body.entry.nodeid = ino;
74
out.body.entry.attr.ino = ino;
75
out.body.entry.attr.nlink = 1;
76
out.body.entry.attr.size = size;
77
out.body.entry.attr_valid = UINT64_MAX;
78
out.body.entry.entry_valid = UINT64_MAX;
79
})));
80
}
81
};
82
83
class NotifyWriteback: public Notify {
84
public:
85
virtual void SetUp() {
86
m_init_flags |= FUSE_WRITEBACK_CACHE;
87
m_async = true;
88
Notify::SetUp();
89
if (IsSkipped())
90
return;
91
}
92
93
void expect_write(uint64_t ino, uint64_t offset, uint64_t size,
94
const void *contents)
95
{
96
FuseTest::expect_write(ino, offset, size, size, 0, 0, contents);
97
}
98
99
};
100
101
struct inval_entry_args {
102
MockFS *mock;
103
ino_t parent;
104
const char *name;
105
size_t namelen;
106
};
107
108
static void* inval_entry(void* arg) {
109
const struct inval_entry_args *iea = (struct inval_entry_args*)arg;
110
ssize_t r;
111
112
r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen);
113
if (r >= 0)
114
return 0;
115
else
116
return (void*)(intptr_t)errno;
117
}
118
119
struct inval_inode_args {
120
MockFS *mock;
121
ino_t ino;
122
off_t off;
123
ssize_t len;
124
};
125
126
struct store_args {
127
MockFS *mock;
128
ino_t nodeid;
129
off_t offset;
130
ssize_t size;
131
const void* data;
132
};
133
134
static void* inval_inode(void* arg) {
135
const struct inval_inode_args *iia = (struct inval_inode_args*)arg;
136
ssize_t r;
137
138
r = iia->mock->notify_inval_inode(iia->ino, iia->off, iia->len);
139
if (r >= 0)
140
return 0;
141
else
142
return (void*)(intptr_t)errno;
143
}
144
145
static void* store(void* arg) {
146
const struct store_args *sa = (struct store_args*)arg;
147
ssize_t r;
148
149
r = sa->mock->notify_store(sa->nodeid, sa->offset, sa->data, sa->size);
150
if (r >= 0)
151
return 0;
152
else
153
return (void*)(intptr_t)errno;
154
}
155
156
/* Invalidate a nonexistent entry */
157
TEST_F(Notify, inval_entry_nonexistent)
158
{
159
const static char *name = "foo";
160
struct inval_entry_args iea;
161
void *thr0_value;
162
pthread_t th0;
163
164
iea.mock = m_mock;
165
iea.parent = FUSE_ROOT_ID;
166
iea.name = name;
167
iea.namelen = strlen(name);
168
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
169
<< strerror(errno);
170
pthread_join(th0, &thr0_value);
171
/* It's not an error for an entry to not be cached */
172
EXPECT_EQ(0, (intptr_t)thr0_value);
173
}
174
175
/* Invalidate a cached entry */
176
TEST_F(Notify, inval_entry)
177
{
178
const static char FULLPATH[] = "mountpoint/foo";
179
const static char RELPATH[] = "foo";
180
struct inval_entry_args iea;
181
struct stat sb;
182
void *thr0_value;
183
uint64_t ino0 = 42;
184
uint64_t ino1 = 43;
185
Sequence seq;
186
pthread_t th0;
187
188
expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, 0, seq);
189
expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, 0, seq);
190
191
/* Fill the entry cache */
192
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
193
EXPECT_EQ(ino0, sb.st_ino);
194
195
/* Now invalidate the entry */
196
iea.mock = m_mock;
197
iea.parent = FUSE_ROOT_ID;
198
iea.name = RELPATH;
199
iea.namelen = strlen(RELPATH);
200
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
201
<< strerror(errno);
202
pthread_join(th0, &thr0_value);
203
EXPECT_EQ(0, (intptr_t)thr0_value);
204
205
/* The second lookup should return the alternate ino */
206
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
207
EXPECT_EQ(ino1, sb.st_ino);
208
}
209
210
/*
211
* Invalidate a cached entry beneath the root, which uses a slightly different
212
* code path.
213
*/
214
TEST_F(Notify, inval_entry_below_root)
215
{
216
const static char FULLPATH[] = "mountpoint/some_dir/foo";
217
const static char DNAME[] = "some_dir";
218
const static char FNAME[] = "foo";
219
struct inval_entry_args iea;
220
struct stat sb;
221
void *thr0_value;
222
uint64_t dir_ino = 41;
223
uint64_t ino0 = 42;
224
uint64_t ino1 = 43;
225
Sequence seq;
226
pthread_t th0;
227
228
EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME)
229
.WillOnce(Invoke(
230
ReturnImmediate([=](auto in __unused, auto& out) {
231
SET_OUT_HEADER_LEN(out, entry);
232
out.body.entry.attr.mode = S_IFDIR | 0755;
233
out.body.entry.nodeid = dir_ino;
234
out.body.entry.attr.nlink = 2;
235
out.body.entry.attr_valid = UINT64_MAX;
236
out.body.entry.entry_valid = UINT64_MAX;
237
})));
238
expect_lookup(dir_ino, FNAME, ino0, 0, seq);
239
expect_lookup(dir_ino, FNAME, ino1, 0, seq);
240
241
/* Fill the entry cache */
242
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
243
EXPECT_EQ(ino0, sb.st_ino);
244
245
/* Now invalidate the entry */
246
iea.mock = m_mock;
247
iea.parent = dir_ino;
248
iea.name = FNAME;
249
iea.namelen = strlen(FNAME);
250
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
251
<< strerror(errno);
252
pthread_join(th0, &thr0_value);
253
EXPECT_EQ(0, (intptr_t)thr0_value);
254
255
/* The second lookup should return the alternate ino */
256
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
257
EXPECT_EQ(ino1, sb.st_ino);
258
}
259
260
/* Invalidating an entry invalidates the parent directory's attributes */
261
TEST_F(Notify, inval_entry_invalidates_parent_attrs)
262
{
263
const static char FULLPATH[] = "mountpoint/foo";
264
const static char RELPATH[] = "foo";
265
struct inval_entry_args iea;
266
struct stat sb;
267
void *thr0_value;
268
uint64_t ino = 42;
269
Sequence seq;
270
pthread_t th0;
271
272
expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
273
EXPECT_CALL(*m_mock, process(
274
ResultOf([=](auto in) {
275
return (in.header.opcode == FUSE_GETATTR &&
276
in.header.nodeid == FUSE_ROOT_ID);
277
}, Eq(true)),
278
_)
279
).Times(2)
280
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
281
SET_OUT_HEADER_LEN(out, attr);
282
out.body.attr.attr.mode = S_IFDIR | 0755;
283
out.body.attr.attr_valid = UINT64_MAX;
284
})));
285
286
/* Fill the attr and entry cache */
287
ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
288
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
289
290
/* Now invalidate the entry */
291
iea.mock = m_mock;
292
iea.parent = FUSE_ROOT_ID;
293
iea.name = RELPATH;
294
iea.namelen = strlen(RELPATH);
295
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
296
<< strerror(errno);
297
pthread_join(th0, &thr0_value);
298
EXPECT_EQ(0, (intptr_t)thr0_value);
299
300
/* /'s attribute cache should be cleared */
301
ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
302
}
303
304
305
TEST_F(Notify, inval_inode_nonexistent)
306
{
307
struct inval_inode_args iia;
308
ino_t ino = 42;
309
void *thr0_value;
310
pthread_t th0;
311
312
iia.mock = m_mock;
313
iia.ino = ino;
314
iia.off = 0;
315
iia.len = 0;
316
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
317
<< strerror(errno);
318
pthread_join(th0, &thr0_value);
319
/* It's not an error for an inode to not be cached */
320
EXPECT_EQ(0, (intptr_t)thr0_value);
321
}
322
323
TEST_F(Notify, inval_inode_with_clean_cache)
324
{
325
const static char FULLPATH[] = "mountpoint/foo";
326
const static char RELPATH[] = "foo";
327
const char CONTENTS0[] = "abcdefgh";
328
const char CONTENTS1[] = "ijklmnopqrstuvwxyz";
329
struct inval_inode_args iia;
330
struct stat sb;
331
ino_t ino = 42;
332
void *thr0_value;
333
Sequence seq;
334
uid_t uid = 12345;
335
pthread_t th0;
336
ssize_t size0 = sizeof(CONTENTS0);
337
ssize_t size1 = sizeof(CONTENTS1);
338
char buf[80];
339
int fd;
340
341
expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size0, seq);
342
expect_open(ino, 0, 1);
343
EXPECT_CALL(*m_mock, process(
344
ResultOf([=](auto in) {
345
return (in.header.opcode == FUSE_GETATTR &&
346
in.header.nodeid == ino);
347
}, Eq(true)),
348
_)
349
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
350
SET_OUT_HEADER_LEN(out, attr);
351
out.body.attr.attr.mode = S_IFREG | 0644;
352
out.body.attr.attr_valid = UINT64_MAX;
353
out.body.attr.attr.size = size1;
354
out.body.attr.attr.uid = uid;
355
})));
356
expect_read(ino, 0, size0, size0, CONTENTS0);
357
expect_read(ino, 0, size1, size1, CONTENTS1);
358
359
/* Fill the data cache */
360
fd = open(FULLPATH, O_RDWR);
361
ASSERT_LE(0, fd) << strerror(errno);
362
ASSERT_EQ(size0, read(fd, buf, size0)) << strerror(errno);
363
EXPECT_EQ(0, memcmp(buf, CONTENTS0, size0));
364
365
/* Evict the data cache */
366
iia.mock = m_mock;
367
iia.ino = ino;
368
iia.off = 0;
369
iia.len = 0;
370
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
371
<< strerror(errno);
372
pthread_join(th0, &thr0_value);
373
EXPECT_EQ(0, (intptr_t)thr0_value);
374
375
/* cache attributes were purged; this will trigger a new GETATTR */
376
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
377
EXPECT_EQ(uid, sb.st_uid);
378
EXPECT_EQ(size1, sb.st_size);
379
380
/* This read should not be serviced by cache */
381
ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
382
ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno);
383
EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1));
384
385
leak(fd);
386
}
387
388
/* FUSE_NOTIFY_STORE with a file that's not in the entry cache */
389
/* disabled because FUSE_NOTIFY_STORE is not yet implemented */
390
TEST_F(Notify, DISABLED_store_nonexistent)
391
{
392
struct store_args sa;
393
ino_t ino = 42;
394
void *thr0_value;
395
pthread_t th0;
396
397
sa.mock = m_mock;
398
sa.nodeid = ino;
399
sa.offset = 0;
400
sa.size = 0;
401
ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno);
402
pthread_join(th0, &thr0_value);
403
/* It's not an error for a file to be unknown to the kernel */
404
EXPECT_EQ(0, (intptr_t)thr0_value);
405
}
406
407
/* Store data into for a file that does not yet have anything cached */
408
/* disabled because FUSE_NOTIFY_STORE is not yet implemented */
409
TEST_F(Notify, DISABLED_store_with_blank_cache)
410
{
411
const static char FULLPATH[] = "mountpoint/foo";
412
const static char RELPATH[] = "foo";
413
const char CONTENTS1[] = "ijklmnopqrstuvwxyz";
414
struct store_args sa;
415
ino_t ino = 42;
416
void *thr0_value;
417
Sequence seq;
418
pthread_t th0;
419
ssize_t size1 = sizeof(CONTENTS1);
420
char buf[80];
421
int fd;
422
423
expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size1, seq);
424
expect_open(ino, 0, 1);
425
426
/* Fill the data cache */
427
fd = open(FULLPATH, O_RDWR);
428
ASSERT_LE(0, fd) << strerror(errno);
429
430
/* Evict the data cache */
431
sa.mock = m_mock;
432
sa.nodeid = ino;
433
sa.offset = 0;
434
sa.size = size1;
435
sa.data = (const void*)CONTENTS1;
436
ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno);
437
pthread_join(th0, &thr0_value);
438
EXPECT_EQ(0, (intptr_t)thr0_value);
439
440
/* This read should be serviced by cache */
441
ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno);
442
EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1));
443
444
leak(fd);
445
}
446
447
TEST_F(NotifyWriteback, inval_inode_with_dirty_cache)
448
{
449
const static char FULLPATH[] = "mountpoint/foo";
450
const static char RELPATH[] = "foo";
451
const char CONTENTS[] = "abcdefgh";
452
struct inval_inode_args iia;
453
ino_t ino = 42;
454
void *thr0_value;
455
Sequence seq;
456
pthread_t th0;
457
ssize_t bufsize = sizeof(CONTENTS);
458
int fd;
459
460
expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
461
expect_open(ino, 0, 1);
462
463
/* Fill the data cache */
464
fd = open(FULLPATH, O_RDWR);
465
ASSERT_LE(0, fd);
466
ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
467
468
expect_write(ino, 0, bufsize, CONTENTS);
469
/*
470
* The FUSE protocol does not require an fsync here, but FreeBSD's
471
* bufobj_invalbuf sends it anyway
472
*/
473
maybe_expect_fsync(ino);
474
475
/* Evict the data cache */
476
iia.mock = m_mock;
477
iia.ino = ino;
478
iia.off = 0;
479
iia.len = 0;
480
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
481
<< strerror(errno);
482
pthread_join(th0, &thr0_value);
483
EXPECT_EQ(0, (intptr_t)thr0_value);
484
485
leak(fd);
486
}
487
488
TEST_F(NotifyWriteback, inval_inode_attrs_only)
489
{
490
const static char FULLPATH[] = "mountpoint/foo";
491
const static char RELPATH[] = "foo";
492
const char CONTENTS[] = "abcdefgh";
493
struct inval_inode_args iia;
494
struct stat sb;
495
uid_t uid = 12345;
496
ino_t ino = 42;
497
void *thr0_value;
498
Sequence seq;
499
pthread_t th0;
500
ssize_t bufsize = sizeof(CONTENTS);
501
int fd;
502
503
expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
504
expect_open(ino, 0, 1);
505
EXPECT_CALL(*m_mock, process(
506
ResultOf([=](auto in) {
507
return (in.header.opcode == FUSE_WRITE);
508
}, Eq(true)),
509
_)
510
).Times(0);
511
EXPECT_CALL(*m_mock, process(
512
ResultOf([=](auto in) {
513
return (in.header.opcode == FUSE_GETATTR &&
514
in.header.nodeid == ino);
515
}, Eq(true)),
516
_)
517
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
518
SET_OUT_HEADER_LEN(out, attr);
519
out.body.attr.attr.mode = S_IFREG | 0644;
520
out.body.attr.attr_valid = UINT64_MAX;
521
out.body.attr.attr.size = bufsize;
522
out.body.attr.attr.uid = uid;
523
})));
524
525
/* Fill the data cache */
526
fd = open(FULLPATH, O_RDWR);
527
ASSERT_LE(0, fd) << strerror(errno);
528
ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
529
530
/* Evict the attributes, but not data cache */
531
iia.mock = m_mock;
532
iia.ino = ino;
533
iia.off = -1;
534
iia.len = 0;
535
ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
536
<< strerror(errno);
537
pthread_join(th0, &thr0_value);
538
EXPECT_EQ(0, (intptr_t)thr0_value);
539
540
/* cache attributes were been purged; this will trigger a new GETATTR */
541
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
542
EXPECT_EQ(uid, sb.st_uid);
543
EXPECT_EQ(bufsize, sb.st_size);
544
545
leak(fd);
546
}
547
548