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