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 {};
113 * If the extended attribute does not exist on this file, the daemon should
114 * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the
115 * correct errror code)
117 TEST_F(Getxattr, enoattr)
121 int ns = EXTATTR_NAMESPACE_USER;
124 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
125 expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
127 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
129 ASSERT_EQ(ENOATTR, errno);
133 * On FreeBSD, if the user passes an insufficiently large buffer then the
134 * filesystem is supposed to copy as much of the attribute's value as will fit.
136 * On Linux, however, the filesystem is supposed to return ERANGE.
138 * libfuse specifies the Linux behavior. However, that's probably an error.
139 * It would probably be correct for the filesystem to use platform-dependent
142 * This test case covers a filesystem that uses the Linux behavior
144 TEST_F(Getxattr, erange)
148 int ns = EXTATTR_NAMESPACE_USER;
151 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
152 expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE));
154 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
156 ASSERT_EQ(ERANGE, errno);
160 * If the user passes a 0-length buffer, then the daemon should just return the
161 * size of the attribute
163 TEST_F(Getxattr, size_only)
166 int ns = EXTATTR_NAMESPACE_USER;
168 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
169 expect_getxattr(ino, "user.foo",
170 ReturnImmediate([](auto in __unused, auto out) {
171 SET_OUT_HEADER_LEN(out, getxattr);
172 out->body.getxattr.size = 99;
176 ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0))
181 * Successfully get an attribute from the system namespace
183 TEST_F(Getxattr, system)
187 const char value[] = "whatever";
188 ssize_t value_len = strlen(value) + 1;
189 int ns = EXTATTR_NAMESPACE_SYSTEM;
192 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
193 expect_getxattr(ino, "system.foo",
194 ReturnImmediate([&](auto in __unused, auto out) {
195 memcpy((void*)out->body.bytes, value, value_len);
196 out->header.len = sizeof(out->header) + value_len;
200 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
201 ASSERT_EQ(value_len, r) << strerror(errno);
202 EXPECT_STREQ(value, data);
206 * Successfully get an attribute from the user namespace
208 TEST_F(Getxattr, user)
212 const char value[] = "whatever";
213 ssize_t value_len = strlen(value) + 1;
214 int ns = EXTATTR_NAMESPACE_USER;
217 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
218 expect_getxattr(ino, "user.foo",
219 ReturnImmediate([&](auto in __unused, auto out) {
220 memcpy((void*)out->body.bytes, value, value_len);
221 out->header.len = sizeof(out->header) + value_len;
225 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
226 ASSERT_EQ(value_len, r) << strerror(errno);
227 EXPECT_STREQ(value, data);
231 * Listing extended attributes failed because they aren't configured on this
234 TEST_F(Listxattr, enotsup)
237 int ns = EXTATTR_NAMESPACE_USER;
239 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
240 expect_listxattr(ino, 0, ReturnErrno(ENOTSUP));
242 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
243 ASSERT_EQ(ENOTSUP, errno);
247 * On FreeBSD, if the user passes an insufficiently large buffer then the
248 * filesystem is supposed to copy as much of the attribute's value as will fit.
250 * On Linux, however, the filesystem is supposed to return ERANGE.
252 * libfuse specifies the Linux behavior. However, that's probably an error.
253 * It would probably be correct for the filesystem to use platform-dependent
256 * This test case covers a filesystem that uses the Linux behavior
258 TEST_F(Listxattr, erange)
261 int ns = EXTATTR_NAMESPACE_USER;
263 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
264 expect_listxattr(ino, 0, ReturnErrno(ERANGE));
266 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
267 ASSERT_EQ(ERANGE, errno);
271 * Get the size of the list that it would take to list no extended attributes
273 TEST_F(Listxattr, size_only_empty)
276 int ns = EXTATTR_NAMESPACE_USER;
278 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
279 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
280 out->body.listxattr.size = 0;
281 SET_OUT_HEADER_LEN(out, listxattr);
284 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
289 * Get the size of the list that it would take to list some extended
290 * attributes. Due to the format differences between a FreeBSD and a
291 * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer
292 * and get the whole list, then convert it, just to figure out its size.
294 TEST_F(Listxattr, size_only_nonempty)
297 int ns = EXTATTR_NAMESPACE_USER;
299 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
300 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
301 out->body.listxattr.size = 45;
302 SET_OUT_HEADER_LEN(out, listxattr);
305 // TODO: fix the expected size after fixing the size calculation bug in
306 // fuse_vnop_listextattr. It should be exactly 45.
307 expect_listxattr(ino, 53,
308 ReturnImmediate([](auto in __unused, auto out) {
309 const char l[] = "user.foo";
310 strlcpy((char*)out->body.bytes, l,
311 sizeof(out->body.bytes));
312 out->header.len = sizeof(fuse_out_header) + sizeof(l);
316 ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
320 TEST_F(Listxattr, size_only_really_big)
323 int ns = EXTATTR_NAMESPACE_USER;
325 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
326 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
327 out->body.listxattr.size = 16000;
328 SET_OUT_HEADER_LEN(out, listxattr);
331 // TODO: fix the expected size after fixing the size calculation bug in
332 // fuse_vnop_listextattr. It should be exactly 16000.
333 expect_listxattr(ino, 16008,
334 ReturnImmediate([](auto in __unused, auto out) {
335 const char l[16] = "user.foobarbang";
336 for (int i=0; i < 1000; i++) {
337 memcpy(&out->body.bytes[16 * i], l, 16);
339 out->header.len = sizeof(fuse_out_header) + 16000;
343 ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0))
348 * List all of the user attributes of a file which has both user and system
351 TEST_F(Listxattr, user)
354 int ns = EXTATTR_NAMESPACE_USER;
356 char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'};
357 char attrs[28] = "user.foo\0system.x\0user.bang";
359 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
360 expect_listxattr(ino, 0,
361 ReturnImmediate([&](auto in __unused, auto out) {
362 out->body.listxattr.size = sizeof(attrs);
363 SET_OUT_HEADER_LEN(out, listxattr);
367 // TODO: fix the expected size after fixing the size calculation bug in
368 // fuse_vnop_listextattr.
369 expect_listxattr(ino, sizeof(attrs) + 8,
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);
375 ASSERT_EQ((ssize_t)sizeof(expected),
376 extattr_list_file(FULLPATH, ns, data, sizeof(data)))
378 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
382 * List all of the system attributes of a file which has both user and system
385 TEST_F(Listxattr, system)
388 int ns = EXTATTR_NAMESPACE_SYSTEM;
390 char expected[2] = {1, 'x'};
391 char attrs[28] = "user.foo\0system.x\0user.bang";
393 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
394 expect_listxattr(ino, 0,
395 ReturnImmediate([&](auto in __unused, auto out) {
396 out->body.listxattr.size = sizeof(attrs);
397 SET_OUT_HEADER_LEN(out, listxattr);
401 // TODO: fix the expected size after fixing the size calculation bug in
402 // fuse_vnop_listextattr.
403 expect_listxattr(ino, sizeof(attrs) + 8,
404 ReturnImmediate([&](auto in __unused, auto out) {
405 memcpy((void*)out->body.bytes, attrs, sizeof(attrs));
406 out->header.len = sizeof(fuse_out_header) + sizeof(attrs);
409 ASSERT_EQ((ssize_t)sizeof(expected),
410 extattr_list_file(FULLPATH, ns, data, sizeof(data)))
412 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
415 /* Fail to remove a nonexistent attribute */
416 TEST_F(Removexattr, enoattr)
419 int ns = EXTATTR_NAMESPACE_USER;
421 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
422 expect_removexattr(ino, "user.foo", ENOATTR);
424 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
425 ASSERT_EQ(ENOATTR, errno);
428 /* Successfully remove a user xattr */
429 TEST_F(Removexattr, user)
432 int ns = EXTATTR_NAMESPACE_USER;
434 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
435 expect_removexattr(ino, "user.foo", 0);
437 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
441 /* Successfully remove a system xattr */
442 TEST_F(Removexattr, system)
445 int ns = EXTATTR_NAMESPACE_SYSTEM;
447 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
448 expect_removexattr(ino, "system.foo", 0);
450 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
455 * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem
456 * as currently configured doesn't support extended attributes.
458 TEST_F(Setxattr, enotsup)
461 const char value[] = "whatever";
462 ssize_t value_len = strlen(value) + 1;
463 int ns = EXTATTR_NAMESPACE_USER;
466 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
467 expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
469 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
471 EXPECT_EQ(ENOTSUP, errno);
475 * Successfully set a user attribute.
477 TEST_F(Setxattr, user)
480 const char value[] = "whatever";
481 ssize_t value_len = strlen(value) + 1;
482 int ns = EXTATTR_NAMESPACE_USER;
485 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
486 expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
488 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
489 ASSERT_EQ(value_len, r) << strerror(errno);
493 * Successfully set a system attribute.
495 TEST_F(Setxattr, system)
498 const char value[] = "whatever";
499 ssize_t value_len = strlen(value) + 1;
500 int ns = EXTATTR_NAMESPACE_SYSTEM;
503 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1);
504 expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
506 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
507 ASSERT_EQ(value_len, r) << strerror(errno);