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
38 using namespace testing;
41 * FUSE asynchonous notification
43 * FUSE servers can send unprompted notification messages for things like cache
44 * invalidation. This file tests our client's handling of those messages.
47 class Notify: public FuseTest {
50 virtual void SetUp() {
51 m_init_flags = FUSE_EXPORT_SUPPORT;
55 void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino,
58 EXPECT_LOOKUP(parent, relpath)
61 ReturnImmediate([=](auto in __unused, auto& out) {
62 SET_OUT_HEADER_LEN(out, entry);
63 out.body.entry.attr.mode = S_IFREG | 0644;
64 out.body.entry.nodeid = ino;
65 out.body.entry.attr.ino = ino;
66 out.body.entry.attr.nlink = 1;
67 out.body.entry.attr_valid = UINT64_MAX;
68 out.body.entry.entry_valid = UINT64_MAX;
73 struct inval_entry_args {
80 static void* inval_entry(void* arg) {
81 const struct inval_entry_args *iea = (struct inval_entry_args*)arg;
84 r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen);
88 return (void*)(intptr_t)errno;
91 /* Invalidate a nonexistent entry */
92 TEST_F(Notify, inval_entry_nonexistent)
94 const static char *name = "foo";
95 struct inval_entry_args iea;
100 iea.parent = FUSE_ROOT_ID;
102 iea.namelen = strlen(name);
103 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
105 pthread_join(th0, &thr0_value);
106 /* It's not an error for an entry to not be cached */
107 EXPECT_EQ(0, (intptr_t)thr0_value);
110 /* Invalidate a cached entry */
111 TEST_F(Notify, inval_entry)
113 const static char FULLPATH[] = "mountpoint/foo";
114 const static char RELPATH[] = "foo";
115 struct inval_entry_args iea;
123 expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, seq);
124 expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, seq);
126 /* Fill the entry cache */
127 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
128 EXPECT_EQ(ino0, sb.st_ino);
130 /* Now invalidate the entry */
132 iea.parent = FUSE_ROOT_ID;
134 iea.namelen = strlen(RELPATH);
135 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
137 pthread_join(th0, &thr0_value);
138 /* It's not an error for an entry to not be cached */
139 EXPECT_EQ(0, (intptr_t)thr0_value);
141 /* The second lookup should return the alternate ino */
142 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
143 EXPECT_EQ(ino1, sb.st_ino);
147 * Invalidate a cached entry beneath the root, which uses a slightly different
150 TEST_F(Notify, inval_entry_below_root)
152 const static char FULLPATH[] = "mountpoint/some_dir/foo";
153 const static char DNAME[] = "some_dir";
154 const static char FNAME[] = "foo";
155 struct inval_entry_args iea;
158 uint64_t dir_ino = 41;
164 EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME)
166 ReturnImmediate([=](auto in __unused, auto& out) {
167 SET_OUT_HEADER_LEN(out, entry);
168 out.body.entry.attr.mode = S_IFDIR | 0755;
169 out.body.entry.nodeid = dir_ino;
170 out.body.entry.attr.nlink = 2;
171 out.body.entry.attr_valid = UINT64_MAX;
172 out.body.entry.entry_valid = UINT64_MAX;
174 expect_lookup(dir_ino, FNAME, ino0, seq);
175 expect_lookup(dir_ino, FNAME, ino1, seq);
177 /* Fill the entry cache */
178 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
179 EXPECT_EQ(ino0, sb.st_ino);
181 /* Now invalidate the entry */
183 iea.parent = dir_ino;
185 iea.namelen = strlen(FNAME);
186 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
188 pthread_join(th0, &thr0_value);
189 /* It's not an error for an entry to not be cached */
190 EXPECT_EQ(0, (intptr_t)thr0_value);
192 /* The second lookup should return the alternate ino */
193 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
194 EXPECT_EQ(ino1, sb.st_ino);
197 /* Invalidating an entry invalidates the parent directory's attributes */
198 TEST_F(Notify, inval_entry_invalidates_parent_attrs)
200 const static char FULLPATH[] = "mountpoint/foo";
201 const static char RELPATH[] = "foo";
202 struct inval_entry_args iea;
209 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, seq);
210 EXPECT_CALL(*m_mock, process(
211 ResultOf([=](auto in) {
212 return (in.header.opcode == FUSE_GETATTR &&
213 in.header.nodeid == FUSE_ROOT_ID);
217 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
218 SET_OUT_HEADER_LEN(out, attr);
219 out.body.attr.attr.mode = S_IFDIR | 0755;
220 out.body.attr.attr_valid = UINT64_MAX;
223 /* Fill the attr and entry cache */
224 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
225 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
227 /* Now invalidate the entry */
229 iea.parent = FUSE_ROOT_ID;
231 iea.namelen = strlen(RELPATH);
232 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
234 pthread_join(th0, &thr0_value);
235 /* It's not an error for an entry to not be cached */
236 EXPECT_EQ(0, (intptr_t)thr0_value);
238 /* /'s attribute cache should be cleared */
239 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);