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