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