]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/notify.cc
fusefs: don't require FUSE_EXPORT_SUPPORT for async invalidation
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / notify.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/sysctl.h>
34
35 #include <fcntl.h>
36 #include <pthread.h>
37 }
38
39 #include "mockfs.hh"
40 #include "utils.hh"
41
42 using namespace testing;
43
44 /*
45  * FUSE asynchonous notification
46  *
47  * FUSE servers can send unprompted notification messages for things like cache
48  * invalidation.  This file tests our client's handling of those messages.
49  */
50
51 class Notify: public FuseTest {
52 public:
53 void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino,
54         off_t size, Sequence &seq)
55 {
56         EXPECT_LOOKUP(parent, relpath)
57         .InSequence(seq)
58         .WillOnce(Invoke(
59                 ReturnImmediate([=](auto in __unused, auto& out) {
60                 SET_OUT_HEADER_LEN(out, entry);
61                 out.body.entry.attr.mode = S_IFREG | 0644;
62                 out.body.entry.nodeid = ino;
63                 out.body.entry.attr.ino = ino;
64                 out.body.entry.attr.nlink = 1;
65                 out.body.entry.attr.size = size;
66                 out.body.entry.attr_valid = UINT64_MAX;
67                 out.body.entry.entry_valid = UINT64_MAX;
68         })));
69 }
70 };
71
72 class NotifyWriteback: public Notify {
73 public:
74 virtual void SetUp() {
75         const char *node = "vfs.fusefs.data_cache_mode";
76         int val = 0;
77         size_t size = sizeof(val);
78
79         Notify::SetUp();
80         if (IsSkipped())
81                 return;
82
83         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
84                 << strerror(errno);
85         if (val != 2)
86                 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 2 "
87                         "(writeback) for this test";
88 }
89
90 void expect_write(uint64_t ino, uint64_t offset, uint64_t size,
91         const void *contents)
92 {
93         FuseTest::expect_write(ino, offset, size, size, 0, 0, contents);
94 }
95
96 };
97
98 struct inval_entry_args {
99         MockFS          *mock;
100         ino_t           parent;
101         const char      *name;
102         size_t          namelen;
103 };
104
105 static void* inval_entry(void* arg) {
106         const struct inval_entry_args *iea = (struct inval_entry_args*)arg;
107         ssize_t r;
108
109         r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen);
110         if (r >= 0)
111                 return 0;
112         else
113                 return (void*)(intptr_t)errno;
114 }
115
116 struct inval_inode_args {
117         MockFS          *mock;
118         ino_t           ino;
119         off_t           off;
120         ssize_t         len;
121 };
122
123 static void* inval_inode(void* arg) {
124         const struct inval_inode_args *iia = (struct inval_inode_args*)arg;
125         ssize_t r;
126
127         r = iia->mock->notify_inval_inode(iia->ino, iia->off, iia->len);
128         if (r >= 0)
129                 return 0;
130         else
131                 return (void*)(intptr_t)errno;
132 }
133
134 /* Invalidate a nonexistent entry */
135 TEST_F(Notify, inval_entry_nonexistent)
136 {
137         const static char *name = "foo";
138         struct inval_entry_args iea;
139         void *thr0_value;
140         pthread_t th0;
141
142         iea.mock = m_mock;
143         iea.parent = FUSE_ROOT_ID;
144         iea.name = name;
145         iea.namelen = strlen(name);
146         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
147                 << strerror(errno);
148         pthread_join(th0, &thr0_value);
149         /* It's not an error for an entry to not be cached */
150         EXPECT_EQ(0, (intptr_t)thr0_value);
151 }
152
153 /* Invalidate a cached entry */
154 TEST_F(Notify, inval_entry)
155 {
156         const static char FULLPATH[] = "mountpoint/foo";
157         const static char RELPATH[] = "foo";
158         struct inval_entry_args iea;
159         struct stat sb;
160         void *thr0_value;
161         uint64_t ino0 = 42;
162         uint64_t ino1 = 43;
163         Sequence seq;
164         pthread_t th0;
165
166         expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, 0, seq);
167         expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, 0, seq);
168
169         /* Fill the entry cache */
170         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
171         EXPECT_EQ(ino0, sb.st_ino);
172
173         /* Now invalidate the entry */
174         iea.mock = m_mock;
175         iea.parent = FUSE_ROOT_ID;
176         iea.name = RELPATH;
177         iea.namelen = strlen(RELPATH);
178         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
179                 << strerror(errno);
180         pthread_join(th0, &thr0_value);
181         EXPECT_EQ(0, (intptr_t)thr0_value);
182
183         /* The second lookup should return the alternate ino */
184         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
185         EXPECT_EQ(ino1, sb.st_ino);
186 }
187
188 /*
189  * Invalidate a cached entry beneath the root, which uses a slightly different
190  * code path.
191  */
192 TEST_F(Notify, inval_entry_below_root)
193 {
194         const static char FULLPATH[] = "mountpoint/some_dir/foo";
195         const static char DNAME[] = "some_dir";
196         const static char FNAME[] = "foo";
197         struct inval_entry_args iea;
198         struct stat sb;
199         void *thr0_value;
200         uint64_t dir_ino = 41;
201         uint64_t ino0 = 42;
202         uint64_t ino1 = 43;
203         Sequence seq;
204         pthread_t th0;
205
206         EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME)
207         .WillOnce(Invoke(
208                 ReturnImmediate([=](auto in __unused, auto& out) {
209                 SET_OUT_HEADER_LEN(out, entry);
210                 out.body.entry.attr.mode = S_IFDIR | 0755;
211                 out.body.entry.nodeid = dir_ino;
212                 out.body.entry.attr.nlink = 2;
213                 out.body.entry.attr_valid = UINT64_MAX;
214                 out.body.entry.entry_valid = UINT64_MAX;
215         })));
216         expect_lookup(dir_ino, FNAME, ino0, 0, seq);
217         expect_lookup(dir_ino, FNAME, ino1, 0, seq);
218
219         /* Fill the entry cache */
220         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
221         EXPECT_EQ(ino0, sb.st_ino);
222
223         /* Now invalidate the entry */
224         iea.mock = m_mock;
225         iea.parent = dir_ino;
226         iea.name = FNAME;
227         iea.namelen = strlen(FNAME);
228         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
229                 << strerror(errno);
230         pthread_join(th0, &thr0_value);
231         EXPECT_EQ(0, (intptr_t)thr0_value);
232
233         /* The second lookup should return the alternate ino */
234         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
235         EXPECT_EQ(ino1, sb.st_ino);
236 }
237
238 /* Invalidating an entry invalidates the parent directory's attributes */
239 TEST_F(Notify, inval_entry_invalidates_parent_attrs)
240 {
241         const static char FULLPATH[] = "mountpoint/foo";
242         const static char RELPATH[] = "foo";
243         struct inval_entry_args iea;
244         struct stat sb;
245         void *thr0_value;
246         uint64_t ino = 42;
247         Sequence seq;
248         pthread_t th0;
249
250         expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
251         EXPECT_CALL(*m_mock, process(
252                 ResultOf([=](auto in) {
253                         return (in.header.opcode == FUSE_GETATTR &&
254                                 in.header.nodeid == FUSE_ROOT_ID);
255                 }, Eq(true)),
256                 _)
257         ).Times(2)
258         .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
259                 SET_OUT_HEADER_LEN(out, attr);
260                 out.body.attr.attr.mode = S_IFDIR | 0755;
261                 out.body.attr.attr_valid = UINT64_MAX;
262         })));
263
264         /* Fill the attr and entry cache */
265         ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
266         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
267
268         /* Now invalidate the entry */
269         iea.mock = m_mock;
270         iea.parent = FUSE_ROOT_ID;
271         iea.name = RELPATH;
272         iea.namelen = strlen(RELPATH);
273         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
274                 << strerror(errno);
275         pthread_join(th0, &thr0_value);
276         EXPECT_EQ(0, (intptr_t)thr0_value);
277
278         /* /'s attribute cache should be cleared */
279         ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
280 }
281
282
283 TEST_F(Notify, inval_inode_nonexistent)
284 {
285         struct inval_inode_args iia;
286         ino_t ino = 42;
287         void *thr0_value;
288         pthread_t th0;
289
290         iia.mock = m_mock;
291         iia.ino = ino;
292         iia.off = 0;
293         iia.len = 0;
294         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
295                 << strerror(errno);
296         pthread_join(th0, &thr0_value);
297         /* It's not an error for an inode to not be cached */
298         EXPECT_EQ(0, (intptr_t)thr0_value);
299 }
300
301 TEST_F(Notify, inval_inode_with_clean_cache)
302 {
303         const static char FULLPATH[] = "mountpoint/foo";
304         const static char RELPATH[] = "foo";
305         const char CONTENTS0[] = "abcdefgh";
306         const char CONTENTS1[] = "ijklmnopqrstuvwxyz";
307         struct inval_inode_args iia;
308         struct stat sb;
309         ino_t ino = 42;
310         void *thr0_value;
311         Sequence seq;
312         uid_t uid = 12345;
313         pthread_t th0;
314         ssize_t size0 = sizeof(CONTENTS0);
315         ssize_t size1 = sizeof(CONTENTS1);
316         char buf[80];
317         int fd;
318
319         expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size0, seq);
320         expect_open(ino, 0, 1);
321         EXPECT_CALL(*m_mock, process(
322                 ResultOf([=](auto in) {
323                         return (in.header.opcode == FUSE_GETATTR &&
324                                 in.header.nodeid == ino);
325                 }, Eq(true)),
326                 _)
327         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
328                 SET_OUT_HEADER_LEN(out, attr);
329                 out.body.attr.attr.mode = S_IFREG | 0644;
330                 out.body.attr.attr_valid = UINT64_MAX;
331                 out.body.attr.attr.size = size1;
332                 out.body.attr.attr.uid = uid;
333         })));
334         expect_read(ino, 0, size0, size0, CONTENTS0);
335         expect_read(ino, 0, size1, size1, CONTENTS1);
336
337         /* Fill the data cache */
338         fd = open(FULLPATH, O_RDWR);
339         ASSERT_LE(0, fd) << strerror(errno);
340         ASSERT_EQ(size0, read(fd, buf, size0)) << strerror(errno);
341         EXPECT_EQ(0, memcmp(buf, CONTENTS0, size0));
342
343         /* Evict the data cache */
344         iia.mock = m_mock;
345         iia.ino = ino;
346         iia.off = 0;
347         iia.len = 0;
348         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
349                 << strerror(errno);
350         pthread_join(th0, &thr0_value);
351         EXPECT_EQ(0, (intptr_t)thr0_value);
352
353         /* cache attributes were been purged; this will trigger a new GETATTR */
354         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
355         EXPECT_EQ(uid, sb.st_uid);
356         EXPECT_EQ(size1, sb.st_size);
357
358         /* This read should not be serviced by cache */
359         ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
360         ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno);
361         EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1));
362
363         /* Deliberately leak fd.  close(2) will be tested in release.cc */
364 }
365
366 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238312 */
367 TEST_F(NotifyWriteback, DISABLED_inval_inode_with_dirty_cache)
368 {
369         const static char FULLPATH[] = "mountpoint/foo";
370         const static char RELPATH[] = "foo";
371         const char CONTENTS[] = "abcdefgh";
372         struct inval_inode_args iia;
373         ino_t ino = 42;
374         void *thr0_value;
375         Sequence seq;
376         pthread_t th0;
377         ssize_t bufsize = sizeof(CONTENTS);
378         int fd;
379
380         expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
381         expect_open(ino, 0, 1);
382
383         /* Fill the data cache */
384         fd = open(FULLPATH, O_RDWR);
385         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
386
387         /* Evict the data cache */
388         expect_write(ino, 0, bufsize, CONTENTS);
389         iia.mock = m_mock;
390         iia.ino = ino;
391         iia.off = 0;
392         iia.len = 0;
393         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
394                 << strerror(errno);
395         pthread_join(th0, &thr0_value);
396         EXPECT_EQ(0, (intptr_t)thr0_value);
397
398         /* Deliberately leak fd.  close(2) will be tested in release.cc */
399 }
400
401 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238312 */
402 TEST_F(NotifyWriteback, DISABLED_inval_inode_attrs_only)
403 {
404         const static char FULLPATH[] = "mountpoint/foo";
405         const static char RELPATH[] = "foo";
406         const char CONTENTS[] = "abcdefgh";
407         struct inval_inode_args iia;
408         struct stat sb;
409         uid_t uid = 12345;
410         ino_t ino = 42;
411         void *thr0_value;
412         Sequence seq;
413         pthread_t th0;
414         ssize_t bufsize = sizeof(CONTENTS);
415         int fd;
416
417         expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
418         expect_open(ino, 0, 1);
419         EXPECT_CALL(*m_mock, process(
420                 ResultOf([=](auto in) {
421                         return (in.header.opcode == FUSE_WRITE);
422                 }, Eq(true)),
423                 _)
424         ).Times(0);
425         EXPECT_CALL(*m_mock, process(
426                 ResultOf([=](auto in) {
427                         return (in.header.opcode == FUSE_GETATTR &&
428                                 in.header.nodeid == FUSE_ROOT_ID);
429                 }, Eq(true)),
430                 _)
431         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
432                 SET_OUT_HEADER_LEN(out, attr);
433                 out.body.attr.attr.mode = S_IFREG | 0644;
434                 out.body.attr.attr_valid = UINT64_MAX;
435                 out.body.attr.attr.size = bufsize;
436                 out.body.attr.attr.uid = uid;
437         })));
438
439         /* Fill the data cache */
440         fd = open(FULLPATH, O_RDWR);
441         ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
442
443         /* Evict the attributes, but not data cache */
444         iia.mock = m_mock;
445         iia.ino = ino;
446         iia.off = -1;
447         iia.len = 0;
448         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
449                 << strerror(errno);
450         pthread_join(th0, &thr0_value);
451         EXPECT_EQ(0, (intptr_t)thr0_value);
452
453         /* cache attributes were been purged; this will trigger a new GETATTR */
454         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
455         EXPECT_EQ(uid, sb.st_uid);
456         EXPECT_EQ(bufsize, sb.st_size);
457
458         /* Deliberately leak fd.  close(2) will be tested in release.cc */
459 }