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