xref: /freebsd/tests/sys/fs/fusefs/xattr.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
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 
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 <string.h>
37 }
38 
39 #include "mockfs.hh"
40 #include "utils.hh"
41 
42 using namespace testing;
43 
44 const char FULLPATH[] = "mountpoint/some_file.txt";
45 const char RELPATH[] = "some_file.txt";
46 
47 class Xattr: public FuseTest {
48 public:
49 void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r)
50 {
51 	EXPECT_CALL(*m_mock, process(
52 		ResultOf([=](auto in) {
53 			const char *a = (const char*)in.body.bytes +
54 				sizeof(fuse_getxattr_in);
55 			return (in.header.opcode == FUSE_GETXATTR &&
56 				in.header.nodeid == ino &&
57 				0 == strcmp(attr, a));
58 		}, Eq(true)),
59 		_)
60 	).WillOnce(Invoke(r));
61 }
62 
63 void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r)
64 {
65 	EXPECT_CALL(*m_mock, process(
66 		ResultOf([=](auto in) {
67 			return (in.header.opcode == FUSE_LISTXATTR &&
68 				in.header.nodeid == ino &&
69 				in.body.listxattr.size == size);
70 		}, Eq(true)),
71 		_)
72 	).WillOnce(Invoke(r))
73 	.RetiresOnSaturation();
74 }
75 
76 void expect_removexattr(uint64_t ino, const char *attr, int error)
77 {
78 	EXPECT_CALL(*m_mock, process(
79 		ResultOf([=](auto in) {
80 			const char *a = (const char*)in.body.bytes;
81 			return (in.header.opcode == FUSE_REMOVEXATTR &&
82 				in.header.nodeid == ino &&
83 				0 == strcmp(attr, a));
84 		}, Eq(true)),
85 		_)
86 	).WillOnce(Invoke(ReturnErrno(error)));
87 }
88 
89 void expect_setxattr(uint64_t ino, const char *attr, const char *value,
90 	ProcessMockerT r)
91 {
92 	EXPECT_CALL(*m_mock, process(
93 		ResultOf([=](auto in) {
94 			const char *a = (const char*)in.body.bytes +
95 				sizeof(fuse_setxattr_in);
96 			const char *v = a + strlen(a) + 1;
97 			return (in.header.opcode == FUSE_SETXATTR &&
98 				in.header.nodeid == ino &&
99 				0 == strcmp(attr, a) &&
100 				0 == strcmp(value, v));
101 		}, Eq(true)),
102 		_)
103 	).WillOnce(Invoke(r));
104 }
105 
106 };
107 
108 class Getxattr: public Xattr {};
109 class Listxattr: public Xattr {};
110 class Removexattr: public Xattr {};
111 class Setxattr: public Xattr {};
112 class RofsXattr: public Xattr {
113 public:
114 virtual void SetUp() {
115 	m_ro = true;
116 	Xattr::SetUp();
117 }
118 };
119 
120 /*
121  * If the extended attribute does not exist on this file, the daemon should
122  * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the
123  * correct errror code)
124  */
125 TEST_F(Getxattr, enoattr)
126 {
127 	char data[80];
128 	uint64_t ino = 42;
129 	int ns = EXTATTR_NAMESPACE_USER;
130 	ssize_t r;
131 
132 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
133 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
134 
135 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
136 	ASSERT_EQ(-1, r);
137 	ASSERT_EQ(ENOATTR, errno);
138 }
139 
140 /*
141  * If the filesystem returns ENOSYS, then it will be treated as a permanent
142  * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP
143  * without querying the filesystem daemon
144  */
145 TEST_F(Getxattr, enosys)
146 {
147 	char data[80];
148 	uint64_t ino = 42;
149 	int ns = EXTATTR_NAMESPACE_USER;
150 	ssize_t r;
151 
152 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
153 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS));
154 
155 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
156 	ASSERT_EQ(-1, r);
157 	EXPECT_EQ(EOPNOTSUPP, errno);
158 
159 	/* Subsequent attempts should not query the filesystem at all */
160 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
161 	ASSERT_EQ(-1, r);
162 	EXPECT_EQ(EOPNOTSUPP, errno);
163 }
164 
165 /*
166  * On FreeBSD, if the user passes an insufficiently large buffer then the
167  * filesystem is supposed to copy as much of the attribute's value as will fit.
168  *
169  * On Linux, however, the filesystem is supposed to return ERANGE.
170  *
171  * libfuse specifies the Linux behavior.  However, that's probably an error.
172  * It would probably be correct for the filesystem to use platform-dependent
173  * behavior.
174  *
175  * This test case covers a filesystem that uses the Linux behavior
176  */
177 TEST_F(Getxattr, erange)
178 {
179 	char data[10];
180 	uint64_t ino = 42;
181 	int ns = EXTATTR_NAMESPACE_USER;
182 	ssize_t r;
183 
184 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
185 	expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE));
186 
187 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
188 	ASSERT_EQ(-1, r);
189 	ASSERT_EQ(ERANGE, errno);
190 }
191 
192 /*
193  * If the user passes a 0-length buffer, then the daemon should just return the
194  * size of the attribute
195  */
196 TEST_F(Getxattr, size_only)
197 {
198 	uint64_t ino = 42;
199 	int ns = EXTATTR_NAMESPACE_USER;
200 
201 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
202 	expect_getxattr(ino, "user.foo",
203 		ReturnImmediate([](auto in __unused, auto& out) {
204 			SET_OUT_HEADER_LEN(out, getxattr);
205 			out.body.getxattr.size = 99;
206 		})
207 	);
208 
209 	ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0))
210 		<< strerror(errno);;
211 }
212 
213 /*
214  * Successfully get an attribute from the system namespace
215  */
216 TEST_F(Getxattr, system)
217 {
218 	uint64_t ino = 42;
219 	char data[80];
220 	const char value[] = "whatever";
221 	ssize_t value_len = strlen(value) + 1;
222 	int ns = EXTATTR_NAMESPACE_SYSTEM;
223 	ssize_t r;
224 
225 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
226 	expect_getxattr(ino, "system.foo",
227 		ReturnImmediate([&](auto in __unused, auto& out) {
228 			memcpy((void*)out.body.bytes, value, value_len);
229 			out.header.len = sizeof(out.header) + value_len;
230 		})
231 	);
232 
233 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
234 	ASSERT_EQ(value_len, r)  << strerror(errno);
235 	EXPECT_STREQ(value, data);
236 }
237 
238 /*
239  * Successfully get an attribute from the user namespace
240  */
241 TEST_F(Getxattr, user)
242 {
243 	uint64_t ino = 42;
244 	char data[80];
245 	const char value[] = "whatever";
246 	ssize_t value_len = strlen(value) + 1;
247 	int ns = EXTATTR_NAMESPACE_USER;
248 	ssize_t r;
249 
250 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
251 	expect_getxattr(ino, "user.foo",
252 		ReturnImmediate([&](auto in __unused, auto& out) {
253 			memcpy((void*)out.body.bytes, value, value_len);
254 			out.header.len = sizeof(out.header) + value_len;
255 		})
256 	);
257 
258 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
259 	ASSERT_EQ(value_len, r)  << strerror(errno);
260 	EXPECT_STREQ(value, data);
261 }
262 
263 /*
264  * If the filesystem returns ENOSYS, then it will be treated as a permanent
265  * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP
266  * without querying the filesystem daemon
267  */
268 TEST_F(Listxattr, enosys)
269 {
270 	uint64_t ino = 42;
271 	int ns = EXTATTR_NAMESPACE_USER;
272 
273 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
274 	expect_listxattr(ino, 0, ReturnErrno(ENOSYS));
275 
276 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
277 	EXPECT_EQ(EOPNOTSUPP, errno);
278 
279 	/* Subsequent attempts should not query the filesystem at all */
280 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
281 	EXPECT_EQ(EOPNOTSUPP, errno);
282 }
283 
284 /*
285  * Listing extended attributes failed because they aren't configured on this
286  * filesystem
287  */
288 TEST_F(Listxattr, enotsup)
289 {
290 	uint64_t ino = 42;
291 	int ns = EXTATTR_NAMESPACE_USER;
292 
293 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
294 	expect_listxattr(ino, 0, ReturnErrno(ENOTSUP));
295 
296 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
297 	ASSERT_EQ(ENOTSUP, errno);
298 }
299 
300 /*
301  * On FreeBSD, if the user passes an insufficiently large buffer then the
302  * filesystem is supposed to copy as much of the attribute's value as will fit.
303  *
304  * On Linux, however, the filesystem is supposed to return ERANGE.
305  *
306  * libfuse specifies the Linux behavior.  However, that's probably an error.
307  * It would probably be correct for the filesystem to use platform-dependent
308  * behavior.
309  *
310  * This test case covers a filesystem that uses the Linux behavior
311  */
312 TEST_F(Listxattr, erange)
313 {
314 	uint64_t ino = 42;
315 	int ns = EXTATTR_NAMESPACE_USER;
316 
317 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
318 	expect_listxattr(ino, 0, ReturnErrno(ERANGE));
319 
320 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
321 	ASSERT_EQ(ERANGE, errno);
322 }
323 
324 /*
325  * Get the size of the list that it would take to list no extended attributes
326  */
327 TEST_F(Listxattr, size_only_empty)
328 {
329 	uint64_t ino = 42;
330 	int ns = EXTATTR_NAMESPACE_USER;
331 
332 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
333 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) {
334 		out.body.listxattr.size = 0;
335 		SET_OUT_HEADER_LEN(out, listxattr);
336 	}));
337 
338 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
339 		<< strerror(errno);
340 }
341 
342 /*
343  * Get the size of the list that it would take to list some extended
344  * attributes.  Due to the format differences between a FreeBSD and a
345  * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer
346  * and get the whole list, then convert it, just to figure out its size.
347  */
348 TEST_F(Listxattr, size_only_nonempty)
349 {
350 	uint64_t ino = 42;
351 	int ns = EXTATTR_NAMESPACE_USER;
352 
353 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
354 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) {
355 		out.body.listxattr.size = 45;
356 		SET_OUT_HEADER_LEN(out, listxattr);
357 	}));
358 
359 	// TODO: fix the expected size after fixing the size calculation bug in
360 	// fuse_vnop_listextattr.  It should be exactly 45.
361 	expect_listxattr(ino, 53,
362 		ReturnImmediate([](auto in __unused, auto& out) {
363 			const char l[] = "user.foo";
364 			strlcpy((char*)out.body.bytes, l,
365 				sizeof(out.body.bytes));
366 			out.header.len = sizeof(fuse_out_header) + sizeof(l);
367 		})
368 	);
369 
370 	ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
371 		<< strerror(errno);
372 }
373 
374 TEST_F(Listxattr, size_only_really_big)
375 {
376 	uint64_t ino = 42;
377 	int ns = EXTATTR_NAMESPACE_USER;
378 
379 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
380 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) {
381 		out.body.listxattr.size = 16000;
382 		SET_OUT_HEADER_LEN(out, listxattr);
383 	}));
384 
385 	// TODO: fix the expected size after fixing the size calculation bug in
386 	// fuse_vnop_listextattr.  It should be exactly 16000.
387 	expect_listxattr(ino, 16008,
388 		ReturnImmediate([](auto in __unused, auto& out) {
389 			const char l[16] = "user.foobarbang";
390 			for (int i=0; i < 1000; i++) {
391 				memcpy(&out.body.bytes[16 * i], l, 16);
392 			}
393 			out.header.len = sizeof(fuse_out_header) + 16000;
394 		})
395 	);
396 
397 	ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0))
398 		<< strerror(errno);
399 }
400 
401 /*
402  * List all of the user attributes of a file which has both user and system
403  * attributes
404  */
405 TEST_F(Listxattr, user)
406 {
407 	uint64_t ino = 42;
408 	int ns = EXTATTR_NAMESPACE_USER;
409 	char data[80];
410 	char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'};
411 	char attrs[28] = "user.foo\0system.x\0user.bang";
412 
413 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
414 	expect_listxattr(ino, 0,
415 		ReturnImmediate([&](auto in __unused, auto& out) {
416 			out.body.listxattr.size = sizeof(attrs);
417 			SET_OUT_HEADER_LEN(out, listxattr);
418 		})
419 	);
420 
421 	// TODO: fix the expected size after fixing the size calculation bug in
422 	// fuse_vnop_listextattr.
423 	expect_listxattr(ino, sizeof(attrs) + 8,
424 	ReturnImmediate([&](auto in __unused, auto& out) {
425 		memcpy((void*)out.body.bytes, attrs, sizeof(attrs));
426 		out.header.len = sizeof(fuse_out_header) + sizeof(attrs);
427 	}));
428 
429 	ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)),
430 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
431 		<< strerror(errno);
432 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
433 }
434 
435 /*
436  * List all of the system attributes of a file which has both user and system
437  * attributes
438  */
439 TEST_F(Listxattr, system)
440 {
441 	uint64_t ino = 42;
442 	int ns = EXTATTR_NAMESPACE_SYSTEM;
443 	char data[80];
444 	char expected[2] = {1, 'x'};
445 	char attrs[28] = "user.foo\0system.x\0user.bang";
446 
447 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
448 	expect_listxattr(ino, 0,
449 		ReturnImmediate([&](auto in __unused, auto& out) {
450 			out.body.listxattr.size = sizeof(attrs);
451 			SET_OUT_HEADER_LEN(out, listxattr);
452 		})
453 	);
454 
455 	// TODO: fix the expected size after fixing the size calculation bug in
456 	// fuse_vnop_listextattr.
457 	expect_listxattr(ino, sizeof(attrs) + 8,
458 	ReturnImmediate([&](auto in __unused, auto& out) {
459 		memcpy((void*)out.body.bytes, attrs, sizeof(attrs));
460 		out.header.len = sizeof(fuse_out_header) + sizeof(attrs);
461 	}));
462 
463 	ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)),
464 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
465 		<< strerror(errno);
466 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
467 }
468 
469 /* Fail to remove a nonexistent attribute */
470 TEST_F(Removexattr, enoattr)
471 {
472 	uint64_t ino = 42;
473 	int ns = EXTATTR_NAMESPACE_USER;
474 
475 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
476 	expect_removexattr(ino, "user.foo", ENOATTR);
477 
478 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
479 	ASSERT_EQ(ENOATTR, errno);
480 }
481 
482 /*
483  * If the filesystem returns ENOSYS, then it will be treated as a permanent
484  * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP
485  * without querying the filesystem daemon
486  */
487 TEST_F(Removexattr, enosys)
488 {
489 	uint64_t ino = 42;
490 	int ns = EXTATTR_NAMESPACE_USER;
491 
492 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
493 	expect_removexattr(ino, "user.foo", ENOSYS);
494 
495 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
496 	EXPECT_EQ(EOPNOTSUPP, errno);
497 
498 	/* Subsequent attempts should not query the filesystem at all */
499 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
500 	EXPECT_EQ(EOPNOTSUPP, errno);
501 }
502 
503 /* Successfully remove a user xattr */
504 TEST_F(Removexattr, user)
505 {
506 	uint64_t ino = 42;
507 	int ns = EXTATTR_NAMESPACE_USER;
508 
509 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
510 	expect_removexattr(ino, "user.foo", 0);
511 
512 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
513 		<< strerror(errno);
514 }
515 
516 /* Successfully remove a system xattr */
517 TEST_F(Removexattr, system)
518 {
519 	uint64_t ino = 42;
520 	int ns = EXTATTR_NAMESPACE_SYSTEM;
521 
522 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
523 	expect_removexattr(ino, "system.foo", 0);
524 
525 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
526 		<< strerror(errno);
527 }
528 
529 /*
530  * If the filesystem returns ENOSYS, then it will be treated as a permanent
531  * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
532  * without querying the filesystem daemon
533  */
534 TEST_F(Setxattr, enosys)
535 {
536 	uint64_t ino = 42;
537 	const char value[] = "whatever";
538 	ssize_t value_len = strlen(value) + 1;
539 	int ns = EXTATTR_NAMESPACE_USER;
540 	ssize_t r;
541 
542 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
543 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS));
544 
545 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
546 		value_len);
547 	ASSERT_EQ(-1, r);
548 	EXPECT_EQ(EOPNOTSUPP, errno);
549 
550 	/* Subsequent attempts should not query the filesystem at all */
551 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
552 		value_len);
553 	ASSERT_EQ(-1, r);
554 	EXPECT_EQ(EOPNOTSUPP, errno);
555 }
556 
557 /*
558  * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem
559  * as currently configured doesn't support extended attributes.
560  */
561 TEST_F(Setxattr, enotsup)
562 {
563 	uint64_t ino = 42;
564 	const char value[] = "whatever";
565 	ssize_t value_len = strlen(value) + 1;
566 	int ns = EXTATTR_NAMESPACE_USER;
567 	ssize_t r;
568 
569 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
570 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
571 
572 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
573 		value_len);
574 	ASSERT_EQ(-1, r);
575 	EXPECT_EQ(ENOTSUP, errno);
576 }
577 
578 /*
579  * Successfully set a user attribute.
580  */
581 TEST_F(Setxattr, user)
582 {
583 	uint64_t ino = 42;
584 	const char value[] = "whatever";
585 	ssize_t value_len = strlen(value) + 1;
586 	int ns = EXTATTR_NAMESPACE_USER;
587 	ssize_t r;
588 
589 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
590 	expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
591 
592 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
593 		value_len);
594 	ASSERT_EQ(value_len, r) << strerror(errno);
595 }
596 
597 /*
598  * Successfully set a system attribute.
599  */
600 TEST_F(Setxattr, system)
601 {
602 	uint64_t ino = 42;
603 	const char value[] = "whatever";
604 	ssize_t value_len = strlen(value) + 1;
605 	int ns = EXTATTR_NAMESPACE_SYSTEM;
606 	ssize_t r;
607 
608 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
609 	expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
610 
611 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
612 		value_len);
613 	ASSERT_EQ(value_len, r) << strerror(errno);
614 }
615 
616 TEST_F(RofsXattr, deleteextattr_erofs)
617 {
618 	uint64_t ino = 42;
619 	int ns = EXTATTR_NAMESPACE_USER;
620 
621 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
622 
623 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
624 	ASSERT_EQ(EROFS, errno);
625 }
626 
627 TEST_F(RofsXattr, setextattr_erofs)
628 {
629 	uint64_t ino = 42;
630 	const char value[] = "whatever";
631 	ssize_t value_len = strlen(value) + 1;
632 	int ns = EXTATTR_NAMESPACE_USER;
633 	ssize_t r;
634 
635 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
636 
637 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
638 		value_len);
639 	ASSERT_EQ(-1, r);
640 	EXPECT_EQ(EROFS, errno);
641 }
642