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