xref: /freebsd/tests/sys/fs/fusefs/xattr.cc (revision 0b57cec536236d46e3dba9bd041533462f33dbb7)
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  * $FreeBSD$
31  */
32 
33 /* Tests for all things relating to extended attributes and FUSE */
34 
35 extern "C" {
36 #include <sys/types.h>
37 #include <sys/extattr.h>
38 #include <sys/wait.h>
39 #include <semaphore.h>
40 #include <signal.h>
41 #include <string.h>
42 }
43 
44 #include "mockfs.hh"
45 #include "utils.hh"
46 
47 using namespace testing;
48 
49 const char FULLPATH[] = "mountpoint/some_file.txt";
50 const char RELPATH[] = "some_file.txt";
51 static sem_t killer_semaphore;
52 
53 void* killer(void* target) {
54 	pid_t pid = *(pid_t*)target;
55 	sem_wait(&killer_semaphore);
56 	if (verbosity > 1)
57 		printf("Killing! pid %d\n", pid);
58 	kill(pid, SIGINT);
59 
60 	return(NULL);
61 }
62 
63 class Xattr: public FuseTest {
64 public:
65 void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r)
66 {
67 	EXPECT_CALL(*m_mock, process(
68 		ResultOf([=](auto in) {
69 			const char *a = (const char*)in.body.bytes +
70 				sizeof(fuse_getxattr_in);
71 			return (in.header.opcode == FUSE_GETXATTR &&
72 				in.header.nodeid == ino &&
73 				0 == strcmp(attr, a));
74 		}, Eq(true)),
75 		_)
76 	).WillOnce(Invoke(r));
77 }
78 
79 void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r,
80     Sequence *seq = NULL)
81 {
82 	if (seq == NULL) {
83 		EXPECT_CALL(*m_mock, process(
84 			ResultOf([=](auto in) {
85 				return (in.header.opcode == FUSE_LISTXATTR &&
86 					in.header.nodeid == ino &&
87 					in.body.listxattr.size == size);
88 			}, Eq(true)),
89 			_)
90 		).WillOnce(Invoke(r))
91 		.RetiresOnSaturation();
92 	} else {
93 		EXPECT_CALL(*m_mock, process(
94 			ResultOf([=](auto in) {
95 				return (in.header.opcode == FUSE_LISTXATTR &&
96 					in.header.nodeid == ino &&
97 					in.body.listxattr.size == size);
98 			}, Eq(true)),
99 			_)
100 		).InSequence(*seq)
101 		.WillOnce(Invoke(r))
102 		.RetiresOnSaturation();
103 	}
104 }
105 
106 void expect_removexattr(uint64_t ino, const char *attr, int error)
107 {
108 	EXPECT_CALL(*m_mock, process(
109 		ResultOf([=](auto in) {
110 			const char *a = (const char*)in.body.bytes;
111 			return (in.header.opcode == FUSE_REMOVEXATTR &&
112 				in.header.nodeid == ino &&
113 				0 == strcmp(attr, a));
114 		}, Eq(true)),
115 		_)
116 	).WillOnce(Invoke(ReturnErrno(error)));
117 }
118 
119 void expect_setxattr(uint64_t ino, const char *attr, const char *value,
120 	ProcessMockerT r)
121 {
122 	EXPECT_CALL(*m_mock, process(
123 		ResultOf([=](auto in) {
124 			const char *a = (const char*)in.body.bytes +
125 				sizeof(fuse_setxattr_in);
126 			const char *v = a + strlen(a) + 1;
127 			return (in.header.opcode == FUSE_SETXATTR &&
128 				in.header.nodeid == ino &&
129 				0 == strcmp(attr, a) &&
130 				0 == strcmp(value, v));
131 		}, Eq(true)),
132 		_)
133 	).WillOnce(Invoke(r));
134 }
135 
136 };
137 
138 class Getxattr: public Xattr {};
139 
140 class Listxattr: public Xattr {};
141 
142 /* Listxattr tests that need to use a signal */
143 class ListxattrSig: public Listxattr {
144 public:
145 pthread_t m_killer_th;
146 pid_t m_child;
147 
148 void SetUp() {
149 	/*
150 	 * Mount with -o nointr so the mount can't get interrupted while
151 	 * waiting for a response from the server
152 	 */
153 	m_nointr = true;
154 	FuseTest::SetUp();
155 
156 	ASSERT_EQ(0, sem_init(&killer_semaphore, 0, 0)) << strerror(errno);
157 }
158 
159 void TearDown() {
160 	if (m_killer_th != NULL) {
161 		pthread_join(m_killer_th, NULL);
162 	}
163 
164 	sem_destroy(&killer_semaphore);
165 
166 	FuseTest::TearDown();
167 }
168 };
169 
170 class Removexattr: public Xattr {};
171 class Setxattr: public Xattr {};
172 class RofsXattr: public Xattr {
173 public:
174 virtual void SetUp() {
175 	m_ro = true;
176 	Xattr::SetUp();
177 }
178 };
179 
180 /*
181  * If the extended attribute does not exist on this file, the daemon should
182  * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the
183  * correct errror code)
184  */
185 TEST_F(Getxattr, enoattr)
186 {
187 	char data[80];
188 	uint64_t ino = 42;
189 	int ns = EXTATTR_NAMESPACE_USER;
190 	ssize_t r;
191 
192 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
193 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
194 
195 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
196 	ASSERT_EQ(-1, r);
197 	ASSERT_EQ(ENOATTR, errno);
198 }
199 
200 /*
201  * If the filesystem returns ENOSYS, then it will be treated as a permanent
202  * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP
203  * without querying the filesystem daemon
204  */
205 TEST_F(Getxattr, enosys)
206 {
207 	char data[80];
208 	uint64_t ino = 42;
209 	int ns = EXTATTR_NAMESPACE_USER;
210 	ssize_t r;
211 
212 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
213 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS));
214 
215 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
216 	ASSERT_EQ(-1, r);
217 	EXPECT_EQ(EOPNOTSUPP, errno);
218 
219 	/* Subsequent attempts should not query the filesystem at all */
220 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
221 	ASSERT_EQ(-1, r);
222 	EXPECT_EQ(EOPNOTSUPP, errno);
223 }
224 
225 /*
226  * On FreeBSD, if the user passes an insufficiently large buffer then the
227  * filesystem is supposed to copy as much of the attribute's value as will fit.
228  *
229  * On Linux, however, the filesystem is supposed to return ERANGE.
230  *
231  * libfuse specifies the Linux behavior.  However, that's probably an error.
232  * It would probably be correct for the filesystem to use platform-dependent
233  * behavior.
234  *
235  * This test case covers a filesystem that uses the Linux behavior
236  * TODO: require FreeBSD Behavior.
237  */
238 TEST_F(Getxattr, erange)
239 {
240 	char data[10];
241 	uint64_t ino = 42;
242 	int ns = EXTATTR_NAMESPACE_USER;
243 	ssize_t r;
244 
245 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
246 	expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE));
247 
248 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
249 	ASSERT_EQ(-1, r);
250 	ASSERT_EQ(ERANGE, errno);
251 }
252 
253 /*
254  * If the user passes a 0-length buffer, then the daemon should just return the
255  * size of the attribute
256  */
257 TEST_F(Getxattr, size_only)
258 {
259 	uint64_t ino = 42;
260 	int ns = EXTATTR_NAMESPACE_USER;
261 
262 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
263 	expect_getxattr(ino, "user.foo",
264 		ReturnImmediate([](auto in __unused, auto& out) {
265 			SET_OUT_HEADER_LEN(out, getxattr);
266 			out.body.getxattr.size = 99;
267 		})
268 	);
269 
270 	ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0))
271 		<< strerror(errno);;
272 }
273 
274 /*
275  * Successfully get an attribute from the system namespace
276  */
277 TEST_F(Getxattr, system)
278 {
279 	uint64_t ino = 42;
280 	char data[80];
281 	const char value[] = "whatever";
282 	ssize_t value_len = strlen(value) + 1;
283 	int ns = EXTATTR_NAMESPACE_SYSTEM;
284 	ssize_t r;
285 
286 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
287 	expect_getxattr(ino, "system.foo",
288 		ReturnImmediate([&](auto in __unused, auto& out) {
289 			memcpy((void*)out.body.bytes, value, value_len);
290 			out.header.len = sizeof(out.header) + value_len;
291 		})
292 	);
293 
294 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
295 	ASSERT_EQ(value_len, r)  << strerror(errno);
296 	EXPECT_STREQ(value, data);
297 }
298 
299 /*
300  * Successfully get an attribute from the user namespace
301  */
302 TEST_F(Getxattr, user)
303 {
304 	uint64_t ino = 42;
305 	char data[80];
306 	const char value[] = "whatever";
307 	ssize_t value_len = strlen(value) + 1;
308 	int ns = EXTATTR_NAMESPACE_USER;
309 	ssize_t r;
310 
311 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
312 	expect_getxattr(ino, "user.foo",
313 		ReturnImmediate([&](auto in __unused, auto& out) {
314 			memcpy((void*)out.body.bytes, value, value_len);
315 			out.header.len = sizeof(out.header) + value_len;
316 		})
317 	);
318 
319 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
320 	ASSERT_EQ(value_len, r)  << strerror(errno);
321 	EXPECT_STREQ(value, data);
322 }
323 
324 /*
325  * If the filesystem returns ENOSYS, then it will be treated as a permanent
326  * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP
327  * without querying the filesystem daemon
328  */
329 TEST_F(Listxattr, enosys)
330 {
331 	uint64_t ino = 42;
332 	int ns = EXTATTR_NAMESPACE_USER;
333 
334 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
335 	expect_listxattr(ino, 0, ReturnErrno(ENOSYS));
336 
337 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
338 	EXPECT_EQ(EOPNOTSUPP, errno);
339 
340 	/* Subsequent attempts should not query the filesystem at all */
341 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
342 	EXPECT_EQ(EOPNOTSUPP, errno);
343 }
344 
345 /*
346  * Listing extended attributes failed because they aren't configured on this
347  * filesystem
348  */
349 TEST_F(Listxattr, enotsup)
350 {
351 	uint64_t ino = 42;
352 	int ns = EXTATTR_NAMESPACE_USER;
353 
354 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
355 	expect_listxattr(ino, 0, ReturnErrno(ENOTSUP));
356 
357 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
358 	ASSERT_EQ(ENOTSUP, errno);
359 }
360 
361 /*
362  * On FreeBSD, if the user passes an insufficiently large buffer to
363  * extattr_list_file(2) or VOP_LISTEXTATTR(9), then the file system is supposed
364  * to copy as much of the attribute's value as will fit.
365  *
366  * On Linux, however, the file system is supposed to return ERANGE if an
367  * insufficiently large buffer is passed to listxattr(2).
368  *
369  * fusefs(5) must guarantee the usual FreeBSD behavior.
370  */
371 TEST_F(Listxattr, erange)
372 {
373 	uint64_t ino = 42;
374 	int ns = EXTATTR_NAMESPACE_USER;
375 	char attrs[9] = "user.foo";
376 	char expected[3] = {3, 'f', 'o'};
377 	char buf[3];
378 
379 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
380 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out)
381 	{
382 		out.body.listxattr.size = sizeof(attrs);
383 		SET_OUT_HEADER_LEN(out, listxattr);
384 	}));
385 	expect_listxattr(ino, sizeof(attrs),
386 	ReturnImmediate([&](auto in __unused, auto& out) {
387 		memcpy((void*)out.body.bytes, attrs, sizeof(attrs));
388 		out.header.len = sizeof(fuse_out_header) + sizeof(attrs);
389 	}));
390 
391 
392 	ASSERT_EQ(static_cast<ssize_t>(sizeof(buf)),
393 		  extattr_list_file(FULLPATH, ns, buf, sizeof(buf)));
394 	ASSERT_EQ(0, memcmp(expected, buf, sizeof(buf)));
395 }
396 
397 /*
398  * A buggy or malicious file system always returns ERANGE, even if we pass an
399  * appropriately sized buffer.  That will send the kernel into an infinite
400  * loop.  This test will ensure that the loop is interruptible by killing the
401  * blocked process with SIGINT.
402  */
403 TEST_F(ListxattrSig, erange_forever)
404 {
405 	uint64_t ino = 42;
406 	uint32_t lie_size = 10;
407 	int status;
408 
409 	fork(false, &status, [&] {
410 		EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
411 		.WillRepeatedly(Invoke(
412 			ReturnImmediate([=](auto in __unused, auto& out) {
413 			SET_OUT_HEADER_LEN(out, entry);
414 			out.body.entry.attr.mode = S_IFREG | 0644;
415 			out.body.entry.nodeid = ino;
416 			out.body.entry.attr.nlink = 1;
417 			out.body.entry.attr_valid = UINT64_MAX;
418 			out.body.entry.entry_valid = UINT64_MAX;
419 		})));
420 		EXPECT_CALL(*m_mock, process(
421 			ResultOf([=](auto in) {
422 				return (in.header.opcode == FUSE_LISTXATTR &&
423 					in.header.nodeid == ino &&
424 					in.body.listxattr.size == 0);
425 			}, Eq(true)),
426 			_)
427 		).WillRepeatedly(ReturnImmediate([=](auto i __unused, auto& out)
428 		{
429 			/* The file system requests 10 bytes, but it's a lie */
430 			out.body.listxattr.size = lie_size;
431 			SET_OUT_HEADER_LEN(out, listxattr);
432 			/*
433 			 * We can send the signal any time after fusefs enters
434 			 * VOP_LISTEXTATTR
435 			 */
436 			sem_post(&killer_semaphore);
437 		}));
438 		/*
439 		 * Even though the kernel faithfully respects our size request,
440 		 * we'll return ERANGE anyway.
441 		 */
442 		EXPECT_CALL(*m_mock, process(
443 			ResultOf([=](auto in) {
444 				return (in.header.opcode == FUSE_LISTXATTR &&
445 					in.header.nodeid == ino &&
446 					in.body.listxattr.size == lie_size);
447 			}, Eq(true)),
448 			_)
449 		).WillRepeatedly(ReturnErrno(ERANGE));
450 
451 		ASSERT_EQ(0, pthread_create(&m_killer_th, NULL, killer,
452 					    &m_mock->m_child_pid))
453 			<< strerror(errno);
454 
455 	}, [] {
456 		/* Child process will block until it gets signaled */
457 		int ns = EXTATTR_NAMESPACE_USER;
458 		char buf[3];
459 		extattr_list_file(FULLPATH, ns, buf, sizeof(buf));
460 		return 0;
461 	}
462 	);
463 
464 	ASSERT_TRUE(WIFSIGNALED(status));
465 }
466 
467 /*
468  * Get the size of the list that it would take to list no extended attributes
469  */
470 TEST_F(Listxattr, size_only_empty)
471 {
472 	uint64_t ino = 42;
473 	int ns = EXTATTR_NAMESPACE_USER;
474 
475 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
476 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) {
477 		out.body.listxattr.size = 0;
478 		SET_OUT_HEADER_LEN(out, listxattr);
479 	}));
480 
481 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
482 		<< strerror(errno);
483 }
484 
485 /*
486  * Get the size of the list that it would take to list some extended
487  * attributes.  Due to the format differences between a FreeBSD and a
488  * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer
489  * and get the whole list, then convert it, just to figure out its size.
490  */
491 TEST_F(Listxattr, size_only_nonempty)
492 {
493 	uint64_t ino = 42;
494 	int ns = EXTATTR_NAMESPACE_USER;
495 	char attrs[9] = "user.foo";
496 
497 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
498 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out)
499 	{
500 		out.body.listxattr.size = sizeof(attrs);
501 		SET_OUT_HEADER_LEN(out, listxattr);
502 	}));
503 
504 	expect_listxattr(ino, sizeof(attrs),
505 		ReturnImmediate([=](auto in __unused, auto& out) {
506 			size_t l = sizeof(attrs);
507 			strlcpy((char*)out.body.bytes, attrs, l);
508 			out.header.len = sizeof(fuse_out_header) + l;
509 		})
510 	);
511 
512 	ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
513 		<< strerror(errno);
514 }
515 
516 /*
517  * The list of extended attributes grows in between the server's two calls to
518  * FUSE_LISTXATTR.
519  */
520 TEST_F(Listxattr, size_only_race_bigger)
521 {
522 	uint64_t ino = 42;
523 	int ns = EXTATTR_NAMESPACE_USER;
524 	char attrs0[9] = "user.foo";
525 	char attrs1[18] = "user.foo\0user.bar";
526 	Sequence seq;
527 
528 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
529 	.WillRepeatedly(Invoke(
530 		ReturnImmediate([=](auto in __unused, auto& out) {
531 		SET_OUT_HEADER_LEN(out, entry);
532 		out.body.entry.attr.mode = S_IFREG | 0644;
533 		out.body.entry.nodeid = ino;
534 		out.body.entry.attr.nlink = 1;
535 		out.body.entry.attr_valid = UINT64_MAX;
536 		out.body.entry.entry_valid = UINT64_MAX;
537 	})));
538 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out)
539 	{
540 		out.body.listxattr.size = sizeof(attrs0);
541 		SET_OUT_HEADER_LEN(out, listxattr);
542 	}), &seq);
543 
544 	/*
545 	 * After the first FUSE_LISTXATTR the list grew, so the second
546 	 * operation returns ERANGE.
547 	 */
548 	expect_listxattr(ino, sizeof(attrs0), ReturnErrno(ERANGE), &seq);
549 
550 	/* And now the kernel retries */
551 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out)
552 	{
553 		out.body.listxattr.size = sizeof(attrs1);
554 		SET_OUT_HEADER_LEN(out, listxattr);
555 	}), &seq);
556 	expect_listxattr(ino, sizeof(attrs1),
557 		ReturnImmediate([&](auto in __unused, auto& out) {
558 			memcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
559 			out.header.len = sizeof(fuse_out_header) +
560 			    sizeof(attrs1);
561 		}), &seq
562 	);
563 
564 	/* Userspace should never know about the retry */
565 	ASSERT_EQ(8, extattr_list_file(FULLPATH, ns, NULL, 0))
566 		<< strerror(errno);
567 }
568 
569 /*
570  * The list of extended attributes shrinks in between the server's two calls to
571  * FUSE_LISTXATTR
572  */
573 TEST_F(Listxattr, size_only_race_smaller)
574 {
575 	uint64_t ino = 42;
576 	int ns = EXTATTR_NAMESPACE_USER;
577 	char attrs0[18] = "user.foo\0user.bar";
578 	char attrs1[9] = "user.foo";
579 
580 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
581 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out)
582 	{
583 		out.body.listxattr.size = sizeof(attrs0);
584 		SET_OUT_HEADER_LEN(out, listxattr);
585 	}));
586 	expect_listxattr(ino, sizeof(attrs0),
587 		ReturnImmediate([&](auto in __unused, auto& out) {
588 			strlcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
589 			out.header.len = sizeof(fuse_out_header) +
590 			    sizeof(attrs1);
591 		})
592 	);
593 
594 	ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
595 		<< strerror(errno);
596 }
597 
598 TEST_F(Listxattr, size_only_really_big)
599 {
600 	uint64_t ino = 42;
601 	int ns = EXTATTR_NAMESPACE_USER;
602 
603 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
604 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) {
605 		out.body.listxattr.size = 16000;
606 		SET_OUT_HEADER_LEN(out, listxattr);
607 	}));
608 
609 	expect_listxattr(ino, 16000,
610 		ReturnImmediate([](auto in __unused, auto& out) {
611 			const char l[16] = "user.foobarbang";
612 			for (int i=0; i < 1000; i++) {
613 				memcpy(&out.body.bytes[16 * i], l, 16);
614 			}
615 			out.header.len = sizeof(fuse_out_header) + 16000;
616 		})
617 	);
618 
619 	ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0))
620 		<< strerror(errno);
621 }
622 
623 /*
624  * List all of the user attributes of a file which has both user and system
625  * attributes
626  */
627 TEST_F(Listxattr, user)
628 {
629 	uint64_t ino = 42;
630 	int ns = EXTATTR_NAMESPACE_USER;
631 	char data[80];
632 	char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'};
633 	char attrs[28] = "user.foo\0system.x\0user.bang";
634 
635 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
636 	expect_listxattr(ino, 0,
637 		ReturnImmediate([&](auto in __unused, auto& out) {
638 			out.body.listxattr.size = sizeof(attrs);
639 			SET_OUT_HEADER_LEN(out, listxattr);
640 		})
641 	);
642 
643 	expect_listxattr(ino, sizeof(attrs),
644 	ReturnImmediate([&](auto in __unused, auto& out) {
645 		memcpy((void*)out.body.bytes, attrs, sizeof(attrs));
646 		out.header.len = sizeof(fuse_out_header) + sizeof(attrs);
647 	}));
648 
649 	ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)),
650 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
651 		<< strerror(errno);
652 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
653 }
654 
655 /*
656  * List all of the system attributes of a file which has both user and system
657  * attributes
658  */
659 TEST_F(Listxattr, system)
660 {
661 	uint64_t ino = 42;
662 	int ns = EXTATTR_NAMESPACE_SYSTEM;
663 	char data[80];
664 	char expected[2] = {1, 'x'};
665 	char attrs[28] = "user.foo\0system.x\0user.bang";
666 
667 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
668 	expect_listxattr(ino, 0,
669 		ReturnImmediate([&](auto in __unused, auto& out) {
670 			out.body.listxattr.size = sizeof(attrs);
671 			SET_OUT_HEADER_LEN(out, listxattr);
672 		})
673 	);
674 
675 	expect_listxattr(ino, sizeof(attrs),
676 	ReturnImmediate([&](auto in __unused, auto& out) {
677 		memcpy((void*)out.body.bytes, attrs, sizeof(attrs));
678 		out.header.len = sizeof(fuse_out_header) + sizeof(attrs);
679 	}));
680 
681 	ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)),
682 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
683 		<< strerror(errno);
684 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
685 }
686 
687 /* Fail to remove a nonexistent attribute */
688 TEST_F(Removexattr, enoattr)
689 {
690 	uint64_t ino = 42;
691 	int ns = EXTATTR_NAMESPACE_USER;
692 
693 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
694 	expect_removexattr(ino, "user.foo", ENOATTR);
695 
696 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
697 	ASSERT_EQ(ENOATTR, errno);
698 }
699 
700 /*
701  * If the filesystem returns ENOSYS, then it will be treated as a permanent
702  * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP
703  * without querying the filesystem daemon
704  */
705 TEST_F(Removexattr, enosys)
706 {
707 	uint64_t ino = 42;
708 	int ns = EXTATTR_NAMESPACE_USER;
709 
710 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
711 	expect_removexattr(ino, "user.foo", ENOSYS);
712 
713 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
714 	EXPECT_EQ(EOPNOTSUPP, errno);
715 
716 	/* Subsequent attempts should not query the filesystem at all */
717 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
718 	EXPECT_EQ(EOPNOTSUPP, errno);
719 }
720 
721 /* Successfully remove a user xattr */
722 TEST_F(Removexattr, user)
723 {
724 	uint64_t ino = 42;
725 	int ns = EXTATTR_NAMESPACE_USER;
726 
727 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
728 	expect_removexattr(ino, "user.foo", 0);
729 
730 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
731 		<< strerror(errno);
732 }
733 
734 /* Successfully remove a system xattr */
735 TEST_F(Removexattr, system)
736 {
737 	uint64_t ino = 42;
738 	int ns = EXTATTR_NAMESPACE_SYSTEM;
739 
740 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
741 	expect_removexattr(ino, "system.foo", 0);
742 
743 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
744 		<< strerror(errno);
745 }
746 
747 /*
748  * If the filesystem returns ENOSYS, then it will be treated as a permanent
749  * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
750  * without querying the filesystem daemon
751  */
752 TEST_F(Setxattr, enosys)
753 {
754 	uint64_t ino = 42;
755 	const char value[] = "whatever";
756 	ssize_t value_len = strlen(value) + 1;
757 	int ns = EXTATTR_NAMESPACE_USER;
758 	ssize_t r;
759 
760 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
761 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS));
762 
763 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
764 		value_len);
765 	ASSERT_EQ(-1, r);
766 	EXPECT_EQ(EOPNOTSUPP, errno);
767 
768 	/* Subsequent attempts should not query the filesystem at all */
769 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
770 		value_len);
771 	ASSERT_EQ(-1, r);
772 	EXPECT_EQ(EOPNOTSUPP, errno);
773 }
774 
775 /*
776  * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem
777  * as currently configured doesn't support extended attributes.
778  */
779 TEST_F(Setxattr, enotsup)
780 {
781 	uint64_t ino = 42;
782 	const char value[] = "whatever";
783 	ssize_t value_len = strlen(value) + 1;
784 	int ns = EXTATTR_NAMESPACE_USER;
785 	ssize_t r;
786 
787 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
788 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
789 
790 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
791 		value_len);
792 	ASSERT_EQ(-1, r);
793 	EXPECT_EQ(ENOTSUP, errno);
794 }
795 
796 /*
797  * Successfully set a user attribute.
798  */
799 TEST_F(Setxattr, user)
800 {
801 	uint64_t ino = 42;
802 	const char value[] = "whatever";
803 	ssize_t value_len = strlen(value) + 1;
804 	int ns = EXTATTR_NAMESPACE_USER;
805 	ssize_t r;
806 
807 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
808 	expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
809 
810 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
811 		value_len);
812 	ASSERT_EQ(value_len, r) << strerror(errno);
813 }
814 
815 /*
816  * Successfully set a system attribute.
817  */
818 TEST_F(Setxattr, system)
819 {
820 	uint64_t ino = 42;
821 	const char value[] = "whatever";
822 	ssize_t value_len = strlen(value) + 1;
823 	int ns = EXTATTR_NAMESPACE_SYSTEM;
824 	ssize_t r;
825 
826 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
827 	expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
828 
829 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
830 		value_len);
831 	ASSERT_EQ(value_len, r) << strerror(errno);
832 }
833 
834 TEST_F(RofsXattr, deleteextattr_erofs)
835 {
836 	uint64_t ino = 42;
837 	int ns = EXTATTR_NAMESPACE_USER;
838 
839 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
840 
841 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
842 	ASSERT_EQ(EROFS, errno);
843 }
844 
845 TEST_F(RofsXattr, setextattr_erofs)
846 {
847 	uint64_t ino = 42;
848 	const char value[] = "whatever";
849 	ssize_t value_len = strlen(value) + 1;
850 	int ns = EXTATTR_NAMESPACE_USER;
851 	ssize_t r;
852 
853 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
854 
855 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
856 		value_len);
857 	ASSERT_EQ(-1, r);
858 	EXPECT_EQ(EROFS, errno);
859 }
860