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