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