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