2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/types.h>
33 #include <sys/sysctl.h>
42 using namespace testing;
45 * FUSE asynchonous notification
47 * FUSE servers can send unprompted notification messages for things like cache
48 * invalidation. This file tests our client's handling of those messages.
51 class Notify: public FuseTest {
53 void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino,
54 off_t size, Sequence &seq)
56 EXPECT_LOOKUP(parent, relpath)
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;
72 class NotifyWriteback: public Notify {
74 virtual void SetUp() {
75 const char *node = "vfs.fusefs.data_cache_mode";
77 size_t size = sizeof(val);
83 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
86 GTEST_SKIP() << "vfs.fusefs.data_cache_mode must be set to 2 "
87 "(writeback) for this test";
90 void expect_write(uint64_t ino, uint64_t offset, uint64_t size,
93 FuseTest::expect_write(ino, offset, size, size, 0, 0, contents);
98 struct inval_entry_args {
105 static void* inval_entry(void* arg) {
106 const struct inval_entry_args *iea = (struct inval_entry_args*)arg;
109 r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen);
113 return (void*)(intptr_t)errno;
116 struct inval_inode_args {
123 static void* inval_inode(void* arg) {
124 const struct inval_inode_args *iia = (struct inval_inode_args*)arg;
127 r = iia->mock->notify_inval_inode(iia->ino, iia->off, iia->len);
131 return (void*)(intptr_t)errno;
134 /* Invalidate a nonexistent entry */
135 TEST_F(Notify, inval_entry_nonexistent)
137 const static char *name = "foo";
138 struct inval_entry_args iea;
143 iea.parent = FUSE_ROOT_ID;
145 iea.namelen = strlen(name);
146 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
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);
153 /* Invalidate a cached entry */
154 TEST_F(Notify, inval_entry)
156 const static char FULLPATH[] = "mountpoint/foo";
157 const static char RELPATH[] = "foo";
158 struct inval_entry_args iea;
166 expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, 0, seq);
167 expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, 0, seq);
169 /* Fill the entry cache */
170 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
171 EXPECT_EQ(ino0, sb.st_ino);
173 /* Now invalidate the entry */
175 iea.parent = FUSE_ROOT_ID;
177 iea.namelen = strlen(RELPATH);
178 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
180 pthread_join(th0, &thr0_value);
181 EXPECT_EQ(0, (intptr_t)thr0_value);
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);
189 * Invalidate a cached entry beneath the root, which uses a slightly different
192 TEST_F(Notify, inval_entry_below_root)
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;
200 uint64_t dir_ino = 41;
206 EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME)
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;
216 expect_lookup(dir_ino, FNAME, ino0, 0, seq);
217 expect_lookup(dir_ino, FNAME, ino1, 0, seq);
219 /* Fill the entry cache */
220 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
221 EXPECT_EQ(ino0, sb.st_ino);
223 /* Now invalidate the entry */
225 iea.parent = dir_ino;
227 iea.namelen = strlen(FNAME);
228 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
230 pthread_join(th0, &thr0_value);
231 EXPECT_EQ(0, (intptr_t)thr0_value);
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);
238 /* Invalidating an entry invalidates the parent directory's attributes */
239 TEST_F(Notify, inval_entry_invalidates_parent_attrs)
241 const static char FULLPATH[] = "mountpoint/foo";
242 const static char RELPATH[] = "foo";
243 struct inval_entry_args iea;
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);
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;
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);
268 /* Now invalidate the entry */
270 iea.parent = FUSE_ROOT_ID;
272 iea.namelen = strlen(RELPATH);
273 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
275 pthread_join(th0, &thr0_value);
276 EXPECT_EQ(0, (intptr_t)thr0_value);
278 /* /'s attribute cache should be cleared */
279 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
283 TEST_F(Notify, inval_inode_nonexistent)
285 struct inval_inode_args iia;
294 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
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);
301 TEST_F(Notify, inval_inode_with_clean_cache)
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;
314 ssize_t size0 = sizeof(CONTENTS0);
315 ssize_t size1 = sizeof(CONTENTS1);
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);
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;
334 expect_read(ino, 0, size0, size0, CONTENTS0);
335 expect_read(ino, 0, size1, size1, CONTENTS1);
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));
343 /* Evict the data cache */
348 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
350 pthread_join(th0, &thr0_value);
351 EXPECT_EQ(0, (intptr_t)thr0_value);
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);
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));
363 /* Deliberately leak fd. close(2) will be tested in release.cc */
366 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238312 */
367 TEST_F(NotifyWriteback, DISABLED_inval_inode_with_dirty_cache)
369 const static char FULLPATH[] = "mountpoint/foo";
370 const static char RELPATH[] = "foo";
371 const char CONTENTS[] = "abcdefgh";
372 struct inval_inode_args iia;
377 ssize_t bufsize = sizeof(CONTENTS);
380 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq);
381 expect_open(ino, 0, 1);
383 /* Fill the data cache */
384 fd = open(FULLPATH, O_RDWR);
385 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
387 /* Evict the data cache */
388 expect_write(ino, 0, bufsize, CONTENTS);
393 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
395 pthread_join(th0, &thr0_value);
396 EXPECT_EQ(0, (intptr_t)thr0_value);
398 /* Deliberately leak fd. close(2) will be tested in release.cc */
401 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238312 */
402 TEST_F(NotifyWriteback, DISABLED_inval_inode_attrs_only)
404 const static char FULLPATH[] = "mountpoint/foo";
405 const static char RELPATH[] = "foo";
406 const char CONTENTS[] = "abcdefgh";
407 struct inval_inode_args iia;
414 ssize_t bufsize = sizeof(CONTENTS);
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);
425 EXPECT_CALL(*m_mock, process(
426 ResultOf([=](auto in) {
427 return (in.header.opcode == FUSE_GETATTR &&
428 in.header.nodeid == FUSE_ROOT_ID);
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;
439 /* Fill the data cache */
440 fd = open(FULLPATH, O_RDWR);
441 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
443 /* Evict the attributes, but not data cache */
448 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia))
450 pthread_join(th0, &thr0_value);
451 EXPECT_EQ(0, (intptr_t)thr0_value);
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);
458 /* Deliberately leak fd. close(2) will be tested in release.cc */