]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/notify.cc
fusefs: support name cache 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 <pthread.h>
33 }
34
35 #include "mockfs.hh"
36 #include "utils.hh"
37
38 using namespace testing;
39
40 /*
41  * FUSE asynchonous notification
42  *
43  * FUSE servers can send unprompted notification messages for things like cache
44  * invalidation.  This file tests our client's handling of those messages.
45  */
46
47 class Notify: public FuseTest {
48 public:
49 public:
50 virtual void SetUp() {
51         m_init_flags = FUSE_EXPORT_SUPPORT;
52         FuseTest::SetUp();
53 }
54
55 void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino,
56         Sequence &seq)
57 {
58         EXPECT_LOOKUP(parent, relpath)
59         .InSequence(seq)
60         .WillOnce(Invoke(
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;
69         })));
70 }
71 };
72
73 struct inval_entry_args {
74         MockFS          *mock;
75         ino_t           parent;
76         const char      *name;
77         size_t          namelen;
78 };
79
80 static void* inval_entry(void* arg) {
81         const struct inval_entry_args *iea = (struct inval_entry_args*)arg;
82         ssize_t r;
83
84         r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen);
85         if (r >= 0)
86                 return 0;
87         else
88                 return (void*)(intptr_t)errno;
89 }
90
91 /* Invalidate a nonexistent entry */
92 TEST_F(Notify, inval_entry_nonexistent)
93 {
94         const static char *name = "foo";
95         struct inval_entry_args iea;
96         void *thr0_value;
97         pthread_t th0;
98
99         iea.mock = m_mock;
100         iea.parent = FUSE_ROOT_ID;
101         iea.name = name;
102         iea.namelen = strlen(name);
103         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
104                 << strerror(errno);
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);
108 }
109
110 /* Invalidate a cached entry */
111 TEST_F(Notify, inval_entry)
112 {
113         const static char FULLPATH[] = "mountpoint/foo";
114         const static char RELPATH[] = "foo";
115         struct inval_entry_args iea;
116         struct stat sb;
117         void *thr0_value;
118         uint64_t ino0 = 42;
119         uint64_t ino1 = 43;
120         Sequence seq;
121         pthread_t th0;
122
123         expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, seq);
124         expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, seq);
125
126         /* Fill the entry cache */
127         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
128         EXPECT_EQ(ino0, sb.st_ino);
129
130         /* Now invalidate the entry */
131         iea.mock = m_mock;
132         iea.parent = FUSE_ROOT_ID;
133         iea.name = RELPATH;
134         iea.namelen = strlen(RELPATH);
135         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
136                 << strerror(errno);
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);
140
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);
144 }
145
146 /*
147  * Invalidate a cached entry beneath the root, which uses a slightly different
148  * code path.
149  */
150 TEST_F(Notify, inval_entry_below_root)
151 {
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;
156         struct stat sb;
157         void *thr0_value;
158         uint64_t dir_ino = 41;
159         uint64_t ino0 = 42;
160         uint64_t ino1 = 43;
161         Sequence seq;
162         pthread_t th0;
163
164         EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME)
165         .WillOnce(Invoke(
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;
173         })));
174         expect_lookup(dir_ino, FNAME, ino0, seq);
175         expect_lookup(dir_ino, FNAME, ino1, seq);
176
177         /* Fill the entry cache */
178         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
179         EXPECT_EQ(ino0, sb.st_ino);
180
181         /* Now invalidate the entry */
182         iea.mock = m_mock;
183         iea.parent = dir_ino;
184         iea.name = FNAME;
185         iea.namelen = strlen(FNAME);
186         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
187                 << strerror(errno);
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);
191
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);
195 }
196
197 /* Invalidating an entry invalidates the parent directory's attributes */
198 TEST_F(Notify, inval_entry_invalidates_parent_attrs)
199 {
200         const static char FULLPATH[] = "mountpoint/foo";
201         const static char RELPATH[] = "foo";
202         struct inval_entry_args iea;
203         struct stat sb;
204         void *thr0_value;
205         uint64_t ino = 42;
206         Sequence seq;
207         pthread_t th0;
208
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);
214                 }, Eq(true)),
215                 _)
216         ).Times(2)
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;
221         })));
222
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);
226
227         /* Now invalidate the entry */
228         iea.mock = m_mock;
229         iea.parent = FUSE_ROOT_ID;
230         iea.name = RELPATH;
231         iea.namelen = strlen(RELPATH);
232         ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea))
233                 << strerror(errno);
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);
237
238         /* /'s attribute cache should be cleared */
239         ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
240 }