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