xref: /freebsd/tests/sys/fs/fusefs/xattr.cc (revision 666f8543bb0a894ec0614b7c1d9c8637a995a512)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers extern "C" {
329821f1d3SAlan Somers #include <sys/types.h>
339821f1d3SAlan Somers #include <sys/extattr.h>
349821f1d3SAlan Somers #include <string.h>
359821f1d3SAlan Somers }
369821f1d3SAlan Somers 
379821f1d3SAlan Somers #include "mockfs.hh"
389821f1d3SAlan Somers #include "utils.hh"
399821f1d3SAlan Somers 
409821f1d3SAlan Somers using namespace testing;
419821f1d3SAlan Somers 
429821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
439821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
449821f1d3SAlan Somers 
459821f1d3SAlan Somers /* For testing filesystems without posix locking support */
469821f1d3SAlan Somers class Xattr: public FuseTest {
479821f1d3SAlan Somers public:
489821f1d3SAlan Somers void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r)
499821f1d3SAlan Somers {
509821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
519821f1d3SAlan Somers 		ResultOf([=](auto in) {
529821f1d3SAlan Somers 			const char *a = (const char*)in->body.bytes +
539821f1d3SAlan Somers 				sizeof(fuse_getxattr_in);
549821f1d3SAlan Somers 			return (in->header.opcode == FUSE_GETXATTR &&
559821f1d3SAlan Somers 				in->header.nodeid == ino &&
569821f1d3SAlan Somers 				0 == strcmp(attr, a));
579821f1d3SAlan Somers 		}, Eq(true)),
589821f1d3SAlan Somers 		_)
599821f1d3SAlan Somers 	).WillOnce(Invoke(r));
609821f1d3SAlan Somers }
619821f1d3SAlan Somers 
629821f1d3SAlan Somers void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r)
639821f1d3SAlan Somers {
649821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
659821f1d3SAlan Somers 		ResultOf([=](auto in) {
669821f1d3SAlan Somers 			return (in->header.opcode == FUSE_LISTXATTR &&
679821f1d3SAlan Somers 				in->header.nodeid == ino &&
689821f1d3SAlan Somers 				in->body.listxattr.size == size);
699821f1d3SAlan Somers 		}, Eq(true)),
709821f1d3SAlan Somers 		_)
719821f1d3SAlan Somers 	).WillOnce(Invoke(r))
729821f1d3SAlan Somers 	.RetiresOnSaturation();
739821f1d3SAlan Somers }
749821f1d3SAlan Somers 
759821f1d3SAlan Somers void expect_removexattr(uint64_t ino, const char *attr, int error)
769821f1d3SAlan Somers {
779821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
789821f1d3SAlan Somers 		ResultOf([=](auto in) {
799821f1d3SAlan Somers 			const char *a = (const char*)in->body.bytes;
809821f1d3SAlan Somers 			return (in->header.opcode == FUSE_REMOVEXATTR &&
819821f1d3SAlan Somers 				in->header.nodeid == ino &&
829821f1d3SAlan Somers 				0 == strcmp(attr, a));
839821f1d3SAlan Somers 		}, Eq(true)),
849821f1d3SAlan Somers 		_)
859821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
869821f1d3SAlan Somers }
879821f1d3SAlan Somers 
889821f1d3SAlan Somers void expect_setxattr(uint64_t ino, const char *attr, const char *value,
899821f1d3SAlan Somers 	ProcessMockerT r)
909821f1d3SAlan Somers {
919821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
929821f1d3SAlan Somers 		ResultOf([=](auto in) {
939821f1d3SAlan Somers 			const char *a = (const char*)in->body.bytes +
949821f1d3SAlan Somers 				sizeof(fuse_setxattr_in);
959821f1d3SAlan Somers 			const char *v = a + strlen(a) + 1;
969821f1d3SAlan Somers 			return (in->header.opcode == FUSE_SETXATTR &&
979821f1d3SAlan Somers 				in->header.nodeid == ino &&
989821f1d3SAlan Somers 				0 == strcmp(attr, a) &&
999821f1d3SAlan Somers 				0 == strcmp(value, v));
1009821f1d3SAlan Somers 		}, Eq(true)),
1019821f1d3SAlan Somers 		_)
1029821f1d3SAlan Somers 	).WillOnce(Invoke(r));
1039821f1d3SAlan Somers }
1049821f1d3SAlan Somers 
1059821f1d3SAlan Somers };
1069821f1d3SAlan Somers 
1079821f1d3SAlan Somers class Getxattr: public Xattr {};
1089821f1d3SAlan Somers class Listxattr: public Xattr {};
1099821f1d3SAlan Somers class Removexattr: public Xattr {};
1109821f1d3SAlan Somers class Setxattr: public Xattr {};
111*666f8543SAlan Somers class RofsXattr: public Xattr {
112*666f8543SAlan Somers public:
113*666f8543SAlan Somers virtual void SetUp() {
114*666f8543SAlan Somers 	m_ro = true;
115*666f8543SAlan Somers 	Xattr::SetUp();
116*666f8543SAlan Somers }
117*666f8543SAlan Somers };
1189821f1d3SAlan Somers 
1199821f1d3SAlan Somers /*
1209821f1d3SAlan Somers  * If the extended attribute does not exist on this file, the daemon should
1219821f1d3SAlan Somers  * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the
1229821f1d3SAlan Somers  * correct errror code)
1239821f1d3SAlan Somers  */
1249821f1d3SAlan Somers TEST_F(Getxattr, enoattr)
1259821f1d3SAlan Somers {
1269821f1d3SAlan Somers 	char data[80];
1279821f1d3SAlan Somers 	uint64_t ino = 42;
1289821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1299821f1d3SAlan Somers 	ssize_t r;
1309821f1d3SAlan Somers 
1319821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
1329821f1d3SAlan Somers 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR));
1339821f1d3SAlan Somers 
1349821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
1359821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
1369821f1d3SAlan Somers 	ASSERT_EQ(ENOATTR, errno);
1379821f1d3SAlan Somers }
1389821f1d3SAlan Somers 
1399821f1d3SAlan Somers /*
1409821f1d3SAlan Somers  * If the filesystem returns ENOSYS, then it will be treated as a permanent
1419821f1d3SAlan Somers  * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP
1429821f1d3SAlan Somers  * without querying the filesystem daemon
1439821f1d3SAlan Somers  */
1449821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */
1459821f1d3SAlan Somers TEST_F(Getxattr, DISABLED_enosys)
1469821f1d3SAlan Somers {
1479821f1d3SAlan Somers 	char data[80];
1489821f1d3SAlan Somers 	uint64_t ino = 42;
1499821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1509821f1d3SAlan Somers 	ssize_t r;
1519821f1d3SAlan Somers 
1529821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
1539821f1d3SAlan Somers 	expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS));
1549821f1d3SAlan Somers 
1559821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
1569821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
1579821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
1589821f1d3SAlan Somers 
1599821f1d3SAlan Somers 	/* Subsequent attempts should not query the filesystem at all */
1609821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
1619821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
1629821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
1639821f1d3SAlan Somers }
1649821f1d3SAlan Somers 
1659821f1d3SAlan Somers /*
1669821f1d3SAlan Somers  * On FreeBSD, if the user passes an insufficiently large buffer then the
1679821f1d3SAlan Somers  * filesystem is supposed to copy as much of the attribute's value as will fit.
1689821f1d3SAlan Somers  *
1699821f1d3SAlan Somers  * On Linux, however, the filesystem is supposed to return ERANGE.
1709821f1d3SAlan Somers  *
1719821f1d3SAlan Somers  * libfuse specifies the Linux behavior.  However, that's probably an error.
1729821f1d3SAlan Somers  * It would probably be correct for the filesystem to use platform-dependent
1739821f1d3SAlan Somers  * behavior.
1749821f1d3SAlan Somers  *
1759821f1d3SAlan Somers  * This test case covers a filesystem that uses the Linux behavior
1769821f1d3SAlan Somers  */
1779821f1d3SAlan Somers TEST_F(Getxattr, erange)
1789821f1d3SAlan Somers {
1799821f1d3SAlan Somers 	char data[10];
1809821f1d3SAlan Somers 	uint64_t ino = 42;
1819821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1829821f1d3SAlan Somers 	ssize_t r;
1839821f1d3SAlan Somers 
1849821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
1859821f1d3SAlan Somers 	expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE));
1869821f1d3SAlan Somers 
1879821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
1889821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
1899821f1d3SAlan Somers 	ASSERT_EQ(ERANGE, errno);
1909821f1d3SAlan Somers }
1919821f1d3SAlan Somers 
1929821f1d3SAlan Somers /*
1939821f1d3SAlan Somers  * If the user passes a 0-length buffer, then the daemon should just return the
1949821f1d3SAlan Somers  * size of the attribute
1959821f1d3SAlan Somers  */
1969821f1d3SAlan Somers TEST_F(Getxattr, size_only)
1979821f1d3SAlan Somers {
1989821f1d3SAlan Somers 	uint64_t ino = 42;
1999821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
2009821f1d3SAlan Somers 
2019821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
2029821f1d3SAlan Somers 	expect_getxattr(ino, "user.foo",
2039821f1d3SAlan Somers 		ReturnImmediate([](auto in __unused, auto out) {
2049821f1d3SAlan Somers 			SET_OUT_HEADER_LEN(out, getxattr);
2059821f1d3SAlan Somers 			out->body.getxattr.size = 99;
2069821f1d3SAlan Somers 		})
2079821f1d3SAlan Somers 	);
2089821f1d3SAlan Somers 
2099821f1d3SAlan Somers 	ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0))
2109821f1d3SAlan Somers 		<< strerror(errno);;
2119821f1d3SAlan Somers }
2129821f1d3SAlan Somers 
2139821f1d3SAlan Somers /*
2149821f1d3SAlan Somers  * Successfully get an attribute from the system namespace
2159821f1d3SAlan Somers  */
2169821f1d3SAlan Somers TEST_F(Getxattr, system)
2179821f1d3SAlan Somers {
2189821f1d3SAlan Somers 	uint64_t ino = 42;
2199821f1d3SAlan Somers 	char data[80];
2209821f1d3SAlan Somers 	const char value[] = "whatever";
2219821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
2229821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
2239821f1d3SAlan Somers 	ssize_t r;
2249821f1d3SAlan Somers 
2259821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
2269821f1d3SAlan Somers 	expect_getxattr(ino, "system.foo",
2279821f1d3SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
2289821f1d3SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
2299821f1d3SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
2309821f1d3SAlan Somers 		})
2319821f1d3SAlan Somers 	);
2329821f1d3SAlan Somers 
2339821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
2349821f1d3SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
2359821f1d3SAlan Somers 	EXPECT_STREQ(value, data);
2369821f1d3SAlan Somers }
2379821f1d3SAlan Somers 
2389821f1d3SAlan Somers /*
2399821f1d3SAlan Somers  * Successfully get an attribute from the user namespace
2409821f1d3SAlan Somers  */
2419821f1d3SAlan Somers TEST_F(Getxattr, user)
2429821f1d3SAlan Somers {
2439821f1d3SAlan Somers 	uint64_t ino = 42;
2449821f1d3SAlan Somers 	char data[80];
2459821f1d3SAlan Somers 	const char value[] = "whatever";
2469821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
2479821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
2489821f1d3SAlan Somers 	ssize_t r;
2499821f1d3SAlan Somers 
2509821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
2519821f1d3SAlan Somers 	expect_getxattr(ino, "user.foo",
2529821f1d3SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
2539821f1d3SAlan Somers 			memcpy((void*)out->body.bytes, value, value_len);
2549821f1d3SAlan Somers 			out->header.len = sizeof(out->header) + value_len;
2559821f1d3SAlan Somers 		})
2569821f1d3SAlan Somers 	);
2579821f1d3SAlan Somers 
2589821f1d3SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
2599821f1d3SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
2609821f1d3SAlan Somers 	EXPECT_STREQ(value, data);
2619821f1d3SAlan Somers }
2629821f1d3SAlan Somers 
2639821f1d3SAlan Somers /*
2649821f1d3SAlan Somers  * If the filesystem returns ENOSYS, then it will be treated as a permanent
2659821f1d3SAlan Somers  * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP
2669821f1d3SAlan Somers  * without querying the filesystem daemon
2679821f1d3SAlan Somers  */
2689821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */
2699821f1d3SAlan Somers TEST_F(Listxattr, DISABLED_enosys)
2709821f1d3SAlan Somers {
2719821f1d3SAlan Somers 	uint64_t ino = 42;
2729821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
2739821f1d3SAlan Somers 
2749821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
2759821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnErrno(ENOSYS));
2769821f1d3SAlan Somers 
2779821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
2789821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
2799821f1d3SAlan Somers 
2809821f1d3SAlan Somers 	/* Subsequent attempts should not query the filesystem at all */
2819821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
2829821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
2839821f1d3SAlan Somers }
2849821f1d3SAlan Somers 
2859821f1d3SAlan Somers /*
2869821f1d3SAlan Somers  * Listing extended attributes failed because they aren't configured on this
2879821f1d3SAlan Somers  * filesystem
2889821f1d3SAlan Somers  */
2899821f1d3SAlan Somers TEST_F(Listxattr, enotsup)
2909821f1d3SAlan Somers {
2919821f1d3SAlan Somers 	uint64_t ino = 42;
2929821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
2939821f1d3SAlan Somers 
2949821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
2959821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnErrno(ENOTSUP));
2969821f1d3SAlan Somers 
2979821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
2989821f1d3SAlan Somers 	ASSERT_EQ(ENOTSUP, errno);
2999821f1d3SAlan Somers }
3009821f1d3SAlan Somers 
3019821f1d3SAlan Somers /*
3029821f1d3SAlan Somers  * On FreeBSD, if the user passes an insufficiently large buffer then the
3039821f1d3SAlan Somers  * filesystem is supposed to copy as much of the attribute's value as will fit.
3049821f1d3SAlan Somers  *
3059821f1d3SAlan Somers  * On Linux, however, the filesystem is supposed to return ERANGE.
3069821f1d3SAlan Somers  *
3079821f1d3SAlan Somers  * libfuse specifies the Linux behavior.  However, that's probably an error.
3089821f1d3SAlan Somers  * It would probably be correct for the filesystem to use platform-dependent
3099821f1d3SAlan Somers  * behavior.
3109821f1d3SAlan Somers  *
3119821f1d3SAlan Somers  * This test case covers a filesystem that uses the Linux behavior
3129821f1d3SAlan Somers  */
3139821f1d3SAlan Somers TEST_F(Listxattr, erange)
3149821f1d3SAlan Somers {
3159821f1d3SAlan Somers 	uint64_t ino = 42;
3169821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
3179821f1d3SAlan Somers 
3189821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
3199821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnErrno(ERANGE));
3209821f1d3SAlan Somers 
3219821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
3229821f1d3SAlan Somers 	ASSERT_EQ(ERANGE, errno);
3239821f1d3SAlan Somers }
3249821f1d3SAlan Somers 
3259821f1d3SAlan Somers /*
3269821f1d3SAlan Somers  * Get the size of the list that it would take to list no extended attributes
3279821f1d3SAlan Somers  */
3289821f1d3SAlan Somers TEST_F(Listxattr, size_only_empty)
3299821f1d3SAlan Somers {
3309821f1d3SAlan Somers 	uint64_t ino = 42;
3319821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
3329821f1d3SAlan Somers 
3339821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
3349821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
3359821f1d3SAlan Somers 		out->body.listxattr.size = 0;
3369821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
3379821f1d3SAlan Somers 	}));
3389821f1d3SAlan Somers 
3399821f1d3SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
3409821f1d3SAlan Somers 		<< strerror(errno);
3419821f1d3SAlan Somers }
3429821f1d3SAlan Somers 
3439821f1d3SAlan Somers /*
3449821f1d3SAlan Somers  * Get the size of the list that it would take to list some extended
3459821f1d3SAlan Somers  * attributes.  Due to the format differences between a FreeBSD and a
3469821f1d3SAlan Somers  * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer
3479821f1d3SAlan Somers  * and get the whole list, then convert it, just to figure out its size.
3489821f1d3SAlan Somers  */
3499821f1d3SAlan Somers TEST_F(Listxattr, size_only_nonempty)
3509821f1d3SAlan Somers {
3519821f1d3SAlan Somers 	uint64_t ino = 42;
3529821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
3539821f1d3SAlan Somers 
3549821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
3559821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
3569821f1d3SAlan Somers 		out->body.listxattr.size = 45;
3579821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
3589821f1d3SAlan Somers 	}));
3599821f1d3SAlan Somers 
3609821f1d3SAlan Somers 	// TODO: fix the expected size after fixing the size calculation bug in
3619821f1d3SAlan Somers 	// fuse_vnop_listextattr.  It should be exactly 45.
3629821f1d3SAlan Somers 	expect_listxattr(ino, 53,
3639821f1d3SAlan Somers 		ReturnImmediate([](auto in __unused, auto out) {
3649821f1d3SAlan Somers 			const char l[] = "user.foo";
3659821f1d3SAlan Somers 			strlcpy((char*)out->body.bytes, l,
3669821f1d3SAlan Somers 				sizeof(out->body.bytes));
3679821f1d3SAlan Somers 			out->header.len = sizeof(fuse_out_header) + sizeof(l);
3689821f1d3SAlan Somers 		})
3699821f1d3SAlan Somers 	);
3709821f1d3SAlan Somers 
3719821f1d3SAlan Somers 	ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0))
3729821f1d3SAlan Somers 		<< strerror(errno);
3739821f1d3SAlan Somers }
3749821f1d3SAlan Somers 
3759821f1d3SAlan Somers TEST_F(Listxattr, size_only_really_big)
3769821f1d3SAlan Somers {
3779821f1d3SAlan Somers 	uint64_t ino = 42;
3789821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
3799821f1d3SAlan Somers 
3809821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
3819821f1d3SAlan Somers 	expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) {
3829821f1d3SAlan Somers 		out->body.listxattr.size = 16000;
3839821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
3849821f1d3SAlan Somers 	}));
3859821f1d3SAlan Somers 
3869821f1d3SAlan Somers 	// TODO: fix the expected size after fixing the size calculation bug in
3879821f1d3SAlan Somers 	// fuse_vnop_listextattr.  It should be exactly 16000.
3889821f1d3SAlan Somers 	expect_listxattr(ino, 16008,
3899821f1d3SAlan Somers 		ReturnImmediate([](auto in __unused, auto out) {
3909821f1d3SAlan Somers 			const char l[16] = "user.foobarbang";
3919821f1d3SAlan Somers 			for (int i=0; i < 1000; i++) {
3929821f1d3SAlan Somers 				memcpy(&out->body.bytes[16 * i], l, 16);
3939821f1d3SAlan Somers 			}
3949821f1d3SAlan Somers 			out->header.len = sizeof(fuse_out_header) + 16000;
3959821f1d3SAlan Somers 		})
3969821f1d3SAlan Somers 	);
3979821f1d3SAlan Somers 
3989821f1d3SAlan Somers 	ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0))
3999821f1d3SAlan Somers 		<< strerror(errno);
4009821f1d3SAlan Somers }
4019821f1d3SAlan Somers 
4029821f1d3SAlan Somers /*
4039821f1d3SAlan Somers  * List all of the user attributes of a file which has both user and system
4049821f1d3SAlan Somers  * attributes
4059821f1d3SAlan Somers  */
4069821f1d3SAlan Somers TEST_F(Listxattr, user)
4079821f1d3SAlan Somers {
4089821f1d3SAlan Somers 	uint64_t ino = 42;
4099821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
4109821f1d3SAlan Somers 	char data[80];
4119821f1d3SAlan Somers 	char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'};
4129821f1d3SAlan Somers 	char attrs[28] = "user.foo\0system.x\0user.bang";
4139821f1d3SAlan Somers 
4149821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
4159821f1d3SAlan Somers 	expect_listxattr(ino, 0,
4169821f1d3SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
4179821f1d3SAlan Somers 			out->body.listxattr.size = sizeof(attrs);
4189821f1d3SAlan Somers 			SET_OUT_HEADER_LEN(out, listxattr);
4199821f1d3SAlan Somers 		})
4209821f1d3SAlan Somers 	);
4219821f1d3SAlan Somers 
4229821f1d3SAlan Somers 	// TODO: fix the expected size after fixing the size calculation bug in
4239821f1d3SAlan Somers 	// fuse_vnop_listextattr.
4249821f1d3SAlan Somers 	expect_listxattr(ino, sizeof(attrs) + 8,
4259821f1d3SAlan Somers 	ReturnImmediate([&](auto in __unused, auto out) {
4269821f1d3SAlan Somers 		memcpy((void*)out->body.bytes, attrs, sizeof(attrs));
4279821f1d3SAlan Somers 		out->header.len = sizeof(fuse_out_header) + sizeof(attrs);
4289821f1d3SAlan Somers 	}));
4299821f1d3SAlan Somers 
4309821f1d3SAlan Somers 	ASSERT_EQ((ssize_t)sizeof(expected),
4319821f1d3SAlan Somers 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
4329821f1d3SAlan Somers 		<< strerror(errno);
4339821f1d3SAlan Somers 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
4349821f1d3SAlan Somers }
4359821f1d3SAlan Somers 
4369821f1d3SAlan Somers /*
4379821f1d3SAlan Somers  * List all of the system attributes of a file which has both user and system
4389821f1d3SAlan Somers  * attributes
4399821f1d3SAlan Somers  */
4409821f1d3SAlan Somers TEST_F(Listxattr, system)
4419821f1d3SAlan Somers {
4429821f1d3SAlan Somers 	uint64_t ino = 42;
4439821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
4449821f1d3SAlan Somers 	char data[80];
4459821f1d3SAlan Somers 	char expected[2] = {1, 'x'};
4469821f1d3SAlan Somers 	char attrs[28] = "user.foo\0system.x\0user.bang";
4479821f1d3SAlan Somers 
4489821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
4499821f1d3SAlan Somers 	expect_listxattr(ino, 0,
4509821f1d3SAlan Somers 		ReturnImmediate([&](auto in __unused, auto out) {
4519821f1d3SAlan Somers 			out->body.listxattr.size = sizeof(attrs);
4529821f1d3SAlan Somers 			SET_OUT_HEADER_LEN(out, listxattr);
4539821f1d3SAlan Somers 		})
4549821f1d3SAlan Somers 	);
4559821f1d3SAlan Somers 
4569821f1d3SAlan Somers 	// TODO: fix the expected size after fixing the size calculation bug in
4579821f1d3SAlan Somers 	// fuse_vnop_listextattr.
4589821f1d3SAlan Somers 	expect_listxattr(ino, sizeof(attrs) + 8,
4599821f1d3SAlan Somers 	ReturnImmediate([&](auto in __unused, auto out) {
4609821f1d3SAlan Somers 		memcpy((void*)out->body.bytes, attrs, sizeof(attrs));
4619821f1d3SAlan Somers 		out->header.len = sizeof(fuse_out_header) + sizeof(attrs);
4629821f1d3SAlan Somers 	}));
4639821f1d3SAlan Somers 
4649821f1d3SAlan Somers 	ASSERT_EQ((ssize_t)sizeof(expected),
4659821f1d3SAlan Somers 		extattr_list_file(FULLPATH, ns, data, sizeof(data)))
4669821f1d3SAlan Somers 		<< strerror(errno);
4679821f1d3SAlan Somers 	ASSERT_EQ(0, memcmp(expected, data, sizeof(expected)));
4689821f1d3SAlan Somers }
4699821f1d3SAlan Somers 
4709821f1d3SAlan Somers /* Fail to remove a nonexistent attribute */
4719821f1d3SAlan Somers TEST_F(Removexattr, enoattr)
4729821f1d3SAlan Somers {
4739821f1d3SAlan Somers 	uint64_t ino = 42;
4749821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
4759821f1d3SAlan Somers 
4769821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
4779821f1d3SAlan Somers 	expect_removexattr(ino, "user.foo", ENOATTR);
4789821f1d3SAlan Somers 
4799821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
4809821f1d3SAlan Somers 	ASSERT_EQ(ENOATTR, errno);
4819821f1d3SAlan Somers }
4829821f1d3SAlan Somers 
4839821f1d3SAlan Somers /*
4849821f1d3SAlan Somers  * If the filesystem returns ENOSYS, then it will be treated as a permanent
4859821f1d3SAlan Somers  * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP
4869821f1d3SAlan Somers  * without querying the filesystem daemon
4879821f1d3SAlan Somers  */
4889821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */
4899821f1d3SAlan Somers TEST_F(Removexattr, DISABLED_enosys)
4909821f1d3SAlan Somers {
4919821f1d3SAlan Somers 	uint64_t ino = 42;
4929821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
4939821f1d3SAlan Somers 
4949821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
4959821f1d3SAlan Somers 	expect_removexattr(ino, "user.foo", ENOSYS);
4969821f1d3SAlan Somers 
4979821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
4989821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
4999821f1d3SAlan Somers 
5009821f1d3SAlan Somers 	/* Subsequent attempts should not query the filesystem at all */
5019821f1d3SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
5029821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
5039821f1d3SAlan Somers }
5049821f1d3SAlan Somers 
5059821f1d3SAlan Somers /* Successfully remove a user xattr */
5069821f1d3SAlan Somers TEST_F(Removexattr, user)
5079821f1d3SAlan Somers {
5089821f1d3SAlan Somers 	uint64_t ino = 42;
5099821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
5109821f1d3SAlan Somers 
5119821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
5129821f1d3SAlan Somers 	expect_removexattr(ino, "user.foo", 0);
5139821f1d3SAlan Somers 
5149821f1d3SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
5159821f1d3SAlan Somers 		<< strerror(errno);
5169821f1d3SAlan Somers }
5179821f1d3SAlan Somers 
5189821f1d3SAlan Somers /* Successfully remove a system xattr */
5199821f1d3SAlan Somers TEST_F(Removexattr, system)
5209821f1d3SAlan Somers {
5219821f1d3SAlan Somers 	uint64_t ino = 42;
5229821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
5239821f1d3SAlan Somers 
5249821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
5259821f1d3SAlan Somers 	expect_removexattr(ino, "system.foo", 0);
5269821f1d3SAlan Somers 
5279821f1d3SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
5289821f1d3SAlan Somers 		<< strerror(errno);
5299821f1d3SAlan Somers }
5309821f1d3SAlan Somers 
5319821f1d3SAlan Somers /*
5329821f1d3SAlan Somers  * If the filesystem returns ENOSYS, then it will be treated as a permanent
5339821f1d3SAlan Somers  * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
5349821f1d3SAlan Somers  * without querying the filesystem daemon
5359821f1d3SAlan Somers  */
5369821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */
5379821f1d3SAlan Somers TEST_F(Setxattr, DISABLED_enosys)
5389821f1d3SAlan Somers {
5399821f1d3SAlan Somers 	uint64_t ino = 42;
5409821f1d3SAlan Somers 	const char value[] = "whatever";
5419821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
5429821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
5439821f1d3SAlan Somers 	ssize_t r;
5449821f1d3SAlan Somers 
5459821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
5469821f1d3SAlan Somers 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS));
5479821f1d3SAlan Somers 
5489821f1d3SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
5499821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
5509821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
5519821f1d3SAlan Somers 
5529821f1d3SAlan Somers 	/* Subsequent attempts should not query the filesystem at all */
5539821f1d3SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
5549821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
5559821f1d3SAlan Somers 	EXPECT_EQ(EOPNOTSUPP, errno);
5569821f1d3SAlan Somers }
5579821f1d3SAlan Somers 
5589821f1d3SAlan Somers /*
5599821f1d3SAlan Somers  * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem
5609821f1d3SAlan Somers  * as currently configured doesn't support extended attributes.
5619821f1d3SAlan Somers  */
5629821f1d3SAlan Somers TEST_F(Setxattr, enotsup)
5639821f1d3SAlan Somers {
5649821f1d3SAlan Somers 	uint64_t ino = 42;
5659821f1d3SAlan Somers 	const char value[] = "whatever";
5669821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
5679821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
5689821f1d3SAlan Somers 	ssize_t r;
5699821f1d3SAlan Somers 
5709821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
5719821f1d3SAlan Somers 	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
5729821f1d3SAlan Somers 
5739821f1d3SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
5749821f1d3SAlan Somers 	ASSERT_EQ(-1, r);
5759821f1d3SAlan Somers 	EXPECT_EQ(ENOTSUP, errno);
5769821f1d3SAlan Somers }
5779821f1d3SAlan Somers 
5789821f1d3SAlan Somers /*
5799821f1d3SAlan Somers  * Successfully set a user attribute.
5809821f1d3SAlan Somers  */
5819821f1d3SAlan Somers TEST_F(Setxattr, user)
5829821f1d3SAlan Somers {
5839821f1d3SAlan Somers 	uint64_t ino = 42;
5849821f1d3SAlan Somers 	const char value[] = "whatever";
5859821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
5869821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
5879821f1d3SAlan Somers 	ssize_t r;
5889821f1d3SAlan Somers 
5899821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
5909821f1d3SAlan Somers 	expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
5919821f1d3SAlan Somers 
5929821f1d3SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
5939821f1d3SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
5949821f1d3SAlan Somers }
5959821f1d3SAlan Somers 
5969821f1d3SAlan Somers /*
5979821f1d3SAlan Somers  * Successfully set a system attribute.
5989821f1d3SAlan Somers  */
5999821f1d3SAlan Somers TEST_F(Setxattr, system)
6009821f1d3SAlan Somers {
6019821f1d3SAlan Somers 	uint64_t ino = 42;
6029821f1d3SAlan Somers 	const char value[] = "whatever";
6039821f1d3SAlan Somers 	ssize_t value_len = strlen(value) + 1;
6049821f1d3SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
6059821f1d3SAlan Somers 	ssize_t r;
6069821f1d3SAlan Somers 
6079821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
6089821f1d3SAlan Somers 	expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
6099821f1d3SAlan Somers 
6109821f1d3SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
6119821f1d3SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
6129821f1d3SAlan Somers }
613ff4fbdf5SAlan Somers 
614*666f8543SAlan Somers TEST_F(RofsXattr, deleteextattr_erofs)
615*666f8543SAlan Somers {
616*666f8543SAlan Somers 	uint64_t ino = 42;
617*666f8543SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
618*666f8543SAlan Somers 
619*666f8543SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
620*666f8543SAlan Somers 
621*666f8543SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
622*666f8543SAlan Somers 	ASSERT_EQ(EROFS, errno);
623*666f8543SAlan Somers }
624*666f8543SAlan Somers 
625*666f8543SAlan Somers TEST_F(RofsXattr, setextattr_erofs)
626*666f8543SAlan Somers {
627*666f8543SAlan Somers 	uint64_t ino = 42;
628*666f8543SAlan Somers 	const char value[] = "whatever";
629*666f8543SAlan Somers 	ssize_t value_len = strlen(value) + 1;
630*666f8543SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
631*666f8543SAlan Somers 	ssize_t r;
632*666f8543SAlan Somers 
633*666f8543SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
634*666f8543SAlan Somers 
635*666f8543SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
636*666f8543SAlan Somers 	ASSERT_EQ(-1, r);
637*666f8543SAlan Somers 	EXPECT_EQ(EROFS, errno);
638*666f8543SAlan Somers }
639*666f8543SAlan Somers 
640ff4fbdf5SAlan Somers // TODO: EROFS tests
641