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