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