2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/types.h>
33 #include <sys/extattr.h>
40 using namespace testing;
42 const char FULLPATH[] = "mountpoint/some_file.txt";
43 const char RELPATH[] = "some_file.txt";
45 /* For testing filesystems without posix locking support */
46 class Xattr: public FuseTest {
48 void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r)
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));
59 ).WillOnce(Invoke(r));
62 void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r)
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);
72 .RetiresOnSaturation();
75 void expect_removexattr(uint64_t ino, const char *attr, int error)
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));
85 ).WillOnce(Invoke(ReturnErrno(error)));
88 void expect_setxattr(uint64_t ino, const char *attr, const char *value,
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));
102 ).WillOnce(Invoke(r));
107 class Getxattr: public Xattr {};
108 class Listxattr: public Xattr {};
109 class Removexattr: public Xattr {};
110 class Setxattr: public Xattr {};
111 class RofsXattr: public Xattr {
113 virtual void SetUp() {
120 * If the extended attribute does not exist on this file, the daemon should
121 * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the
122 * correct errror code)
124 TEST_F(Getxattr, enoattr)
128 int ns = EXTATTR_NAMESPACE_USER;
131 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
132 expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
134 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
136 ASSERT_EQ(ENOATTR, errno);
140 * If the filesystem returns ENOSYS, then it will be treated as a permanent
141 * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP
142 * without querying the filesystem daemon
144 TEST_F(Getxattr, enosys)
148 int ns = EXTATTR_NAMESPACE_USER;
151 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
152 expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS));
154 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
156 EXPECT_EQ(EOPNOTSUPP, errno);
158 /* Subsequent attempts should not query the filesystem at all */
159 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
161 EXPECT_EQ(EOPNOTSUPP, errno);
165 * On FreeBSD, if the user passes an insufficiently large buffer then the
166 * filesystem is supposed to copy as much of the attribute's value as will fit.
168 * On Linux, however, the filesystem is supposed to return ERANGE.
170 * libfuse specifies the Linux behavior. However, that's probably an error.
171 * It would probably be correct for the filesystem to use platform-dependent
174 * This test case covers a filesystem that uses the Linux behavior
176 TEST_F(Getxattr, erange)
180 int ns = EXTATTR_NAMESPACE_USER;
183 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
184 expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE));
186 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
188 ASSERT_EQ(ERANGE, errno);
192 * If the user passes a 0-length buffer, then the daemon should just return the
193 * size of the attribute
195 TEST_F(Getxattr, size_only)
198 int ns = EXTATTR_NAMESPACE_USER;
200 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
201 expect_getxattr(ino, "user.foo",
202 ReturnImmediate([](auto in __unused, auto out) {
203 SET_OUT_HEADER_LEN(out, getxattr);
204 out->body.getxattr.size = 99;
208 ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0))
213 * Successfully get an attribute from the system namespace
215 TEST_F(Getxattr, system)
219 const char value[] = "whatever";
220 ssize_t value_len = strlen(value) + 1;
221 int ns = EXTATTR_NAMESPACE_SYSTEM;
224 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
225 expect_getxattr(ino, "system.foo",
226 ReturnImmediate([&](auto in __unused, auto out) {
227 memcpy((void*)out->body.bytes, value, value_len);
228 out->header.len = sizeof(out->header) + value_len;
232 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
233 ASSERT_EQ(value_len, r) << strerror(errno);
234 EXPECT_STREQ(value, data);
238 * Successfully get an attribute from the user namespace
240 TEST_F(Getxattr, user)
244 const char value[] = "whatever";
245 ssize_t value_len = strlen(value) + 1;
246 int ns = EXTATTR_NAMESPACE_USER;
249 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
250 expect_getxattr(ino, "user.foo",
251 ReturnImmediate([&](auto in __unused, auto out) {
252 memcpy((void*)out->body.bytes, value, value_len);
253 out->header.len = sizeof(out->header) + value_len;
257 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
258 ASSERT_EQ(value_len, r) << strerror(errno);
259 EXPECT_STREQ(value, data);
263 * If the filesystem returns ENOSYS, then it will be treated as a permanent
264 * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP
265 * without querying the filesystem daemon
267 TEST_F(Listxattr, enosys)
270 int ns = EXTATTR_NAMESPACE_USER;
272 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
273 expect_listxattr(ino, 0, ReturnErrno(ENOSYS));
275 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
276 EXPECT_EQ(EOPNOTSUPP, errno);
278 /* Subsequent attempts should not query the filesystem at all */
279 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
280 EXPECT_EQ(EOPNOTSUPP, errno);
284 * Listing extended attributes failed because they aren't configured on this
287 TEST_F(Listxattr, enotsup)
290 int ns = EXTATTR_NAMESPACE_USER;
292 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
293 expect_listxattr(ino, 0, ReturnErrno(ENOTSUP));
295 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
296 ASSERT_EQ(ENOTSUP, errno);
300 * On FreeBSD, if the user passes an insufficiently large buffer then the
301 * filesystem is supposed to copy as much of the attribute's value as will fit.
303 * On Linux, however, the filesystem is supposed to return ERANGE.
305 * libfuse specifies the Linux behavior. However, that's probably an error.
306 * It would probably be correct for the filesystem to use platform-dependent
309 * This test case covers a filesystem that uses the Linux behavior
311 TEST_F(Listxattr, erange)
314 int ns = EXTATTR_NAMESPACE_USER;
316 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
317 expect_listxattr(ino, 0, ReturnErrno(ERANGE));
319 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
320 ASSERT_EQ(ERANGE, errno);
324 * Get the size of the list that it would take to list no extended attributes
326 TEST_F(Listxattr, size_only_empty)
329 int ns = EXTATTR_NAMESPACE_USER;
331 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
332 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
333 out->body.listxattr.size = 0;
334 SET_OUT_HEADER_LEN(out, listxattr);
337 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
342 * Get the size of the list that it would take to list some extended
343 * attributes. Due to the format differences between a FreeBSD and a
344 * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer
345 * and get the whole list, then convert it, just to figure out its size.
347 TEST_F(Listxattr, size_only_nonempty)
350 int ns = EXTATTR_NAMESPACE_USER;
352 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
353 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
354 out->body.listxattr.size = 45;
355 SET_OUT_HEADER_LEN(out, listxattr);
358 // TODO: fix the expected size after fixing the size calculation bug in
359 // fuse_vnop_listextattr. It should be exactly 45.
360 expect_listxattr(ino, 53,
361 ReturnImmediate([](auto in __unused, auto out) {
362 const char l[] = "user.foo";
363 strlcpy((char*)out->body.bytes, l,
364 sizeof(out->body.bytes));
365 out->header.len = sizeof(fuse_out_header) + sizeof(l);
369 ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
373 TEST_F(Listxattr, size_only_really_big)
376 int ns = EXTATTR_NAMESPACE_USER;
378 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
379 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
380 out->body.listxattr.size = 16000;
381 SET_OUT_HEADER_LEN(out, listxattr);
384 // TODO: fix the expected size after fixing the size calculation bug in
385 // fuse_vnop_listextattr. It should be exactly 16000.
386 expect_listxattr(ino, 16008,
387 ReturnImmediate([](auto in __unused, auto out) {
388 const char l[16] = "user.foobarbang";
389 for (int i=0; i < 1000; i++) {
390 memcpy(&out->body.bytes[16 * i], l, 16);
392 out->header.len = sizeof(fuse_out_header) + 16000;
396 ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0))
401 * List all of the user attributes of a file which has both user and system
404 TEST_F(Listxattr, user)
407 int ns = EXTATTR_NAMESPACE_USER;
409 char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'};
410 char attrs[28] = "user.foo\0system.x\0user.bang";
412 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
413 expect_listxattr(ino, 0,
414 ReturnImmediate([&](auto in __unused, auto out) {
415 out->body.listxattr.size = sizeof(attrs);
416 SET_OUT_HEADER_LEN(out, listxattr);
420 // TODO: fix the expected size after fixing the size calculation bug in
421 // fuse_vnop_listextattr.
422 expect_listxattr(ino, sizeof(attrs) + 8,
423 ReturnImmediate([&](auto in __unused, auto out) {
424 memcpy((void*)out->body.bytes, attrs, sizeof(attrs));
425 out->header.len = sizeof(fuse_out_header) + sizeof(attrs);
428 ASSERT_EQ((ssize_t)sizeof(expected),
429 extattr_list_file(FULLPATH, ns, data, sizeof(data)))
431 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
435 * List all of the system attributes of a file which has both user and system
438 TEST_F(Listxattr, system)
441 int ns = EXTATTR_NAMESPACE_SYSTEM;
443 char expected[2] = {1, 'x'};
444 char attrs[28] = "user.foo\0system.x\0user.bang";
446 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
447 expect_listxattr(ino, 0,
448 ReturnImmediate([&](auto in __unused, auto out) {
449 out->body.listxattr.size = sizeof(attrs);
450 SET_OUT_HEADER_LEN(out, listxattr);
454 // TODO: fix the expected size after fixing the size calculation bug in
455 // fuse_vnop_listextattr.
456 expect_listxattr(ino, sizeof(attrs) + 8,
457 ReturnImmediate([&](auto in __unused, auto out) {
458 memcpy((void*)out->body.bytes, attrs, sizeof(attrs));
459 out->header.len = sizeof(fuse_out_header) + sizeof(attrs);
462 ASSERT_EQ((ssize_t)sizeof(expected),
463 extattr_list_file(FULLPATH, ns, data, sizeof(data)))
465 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
468 /* Fail to remove a nonexistent attribute */
469 TEST_F(Removexattr, enoattr)
472 int ns = EXTATTR_NAMESPACE_USER;
474 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
475 expect_removexattr(ino, "user.foo", ENOATTR);
477 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
478 ASSERT_EQ(ENOATTR, errno);
482 * If the filesystem returns ENOSYS, then it will be treated as a permanent
483 * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP
484 * without querying the filesystem daemon
486 TEST_F(Removexattr, enosys)
489 int ns = EXTATTR_NAMESPACE_USER;
491 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
492 expect_removexattr(ino, "user.foo", ENOSYS);
494 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
495 EXPECT_EQ(EOPNOTSUPP, errno);
497 /* Subsequent attempts should not query the filesystem at all */
498 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
499 EXPECT_EQ(EOPNOTSUPP, errno);
502 /* Successfully remove a user xattr */
503 TEST_F(Removexattr, user)
506 int ns = EXTATTR_NAMESPACE_USER;
508 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
509 expect_removexattr(ino, "user.foo", 0);
511 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
515 /* Successfully remove a system xattr */
516 TEST_F(Removexattr, system)
519 int ns = EXTATTR_NAMESPACE_SYSTEM;
521 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
522 expect_removexattr(ino, "system.foo", 0);
524 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
529 * If the filesystem returns ENOSYS, then it will be treated as a permanent
530 * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
531 * without querying the filesystem daemon
533 TEST_F(Setxattr, enosys)
536 const char value[] = "whatever";
537 ssize_t value_len = strlen(value) + 1;
538 int ns = EXTATTR_NAMESPACE_USER;
541 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
542 expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS));
544 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
546 EXPECT_EQ(EOPNOTSUPP, errno);
548 /* Subsequent attempts should not query the filesystem at all */
549 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
551 EXPECT_EQ(EOPNOTSUPP, errno);
555 * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem
556 * as currently configured doesn't support extended attributes.
558 TEST_F(Setxattr, enotsup)
561 const char value[] = "whatever";
562 ssize_t value_len = strlen(value) + 1;
563 int ns = EXTATTR_NAMESPACE_USER;
566 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
567 expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
569 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
571 EXPECT_EQ(ENOTSUP, errno);
575 * Successfully set a user attribute.
577 TEST_F(Setxattr, user)
580 const char value[] = "whatever";
581 ssize_t value_len = strlen(value) + 1;
582 int ns = EXTATTR_NAMESPACE_USER;
585 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
586 expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
588 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
589 ASSERT_EQ(value_len, r) << strerror(errno);
593 * Successfully set a system attribute.
595 TEST_F(Setxattr, system)
598 const char value[] = "whatever";
599 ssize_t value_len = strlen(value) + 1;
600 int ns = EXTATTR_NAMESPACE_SYSTEM;
603 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
604 expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
606 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
607 ASSERT_EQ(value_len, r) << strerror(errno);
610 TEST_F(RofsXattr, deleteextattr_erofs)
613 int ns = EXTATTR_NAMESPACE_USER;
615 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
617 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
618 ASSERT_EQ(EROFS, errno);
621 TEST_F(RofsXattr, setextattr_erofs)
624 const char value[] = "whatever";
625 ssize_t value_len = strlen(value) + 1;
626 int ns = EXTATTR_NAMESPACE_USER;
629 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
631 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
633 EXPECT_EQ(EROFS, errno);