Date: Thu, 14 Mar 2019 23:05:59 +0000 (UTC) From: Alan Somers <asomers@FreeBSD.org> To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r345167 - projects/fuse2/tests/sys/fs/fuse Message-ID: <201903142305.x2EN5x9F080983@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: asomers Date: Thu Mar 14 23:05:59 2019 New Revision: 345167 URL: https://svnweb.freebsd.org/changeset/base/345167 Log: fuse(4): add tests for extended attributes Sponsored by: The FreeBSD Foundation Added: projects/fuse2/tests/sys/fs/fuse/xattr.cc (contents, props changed) Modified: projects/fuse2/tests/sys/fs/fuse/Makefile projects/fuse2/tests/sys/fs/fuse/mockfs.cc projects/fuse2/tests/sys/fs/fuse/mockfs.hh Modified: projects/fuse2/tests/sys/fs/fuse/Makefile ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile Thu Mar 14 22:52:16 2019 (r345166) +++ projects/fuse2/tests/sys/fs/fuse/Makefile Thu Mar 14 23:05:59 2019 (r345167) @@ -33,6 +33,7 @@ ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink ATF_TESTS_CXX+= unlink ATF_TESTS_CXX+= write +ATF_TESTS_CXX+= xattr SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -163,6 +164,11 @@ SRCS.write+= getmntopts.c SRCS.write+= mockfs.cc SRCS.write+= write.cc SRCS.write+= utils.cc + +SRCS.xattr+= getmntopts.c +SRCS.xattr+= mockfs.cc +SRCS.xattr+= xattr.cc +SRCS.xattr+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.cc Thu Mar 14 22:52:16 2019 (r345166) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc Thu Mar 14 23:05:59 2019 (r345167) @@ -208,6 +208,18 @@ void debug_fuseop(const mockfs_buf_in *in) if (in->body.setattr.valid & FATTR_FH) printf(" fh=%zu", in->body.setattr.fh); break; + case FUSE_SETXATTR: + /* + * In theory neither the xattr name and value need be + * ASCII, but in this test suite they always are. + */ + { + const char *attr = (const char*)in->body.bytes + + sizeof(fuse_setxattr_in); + const char *v = attr + strlen(attr) + 1; + printf(" %s=%s", attr, v); + } + break; case FUSE_WRITE: printf(" offset=%lu size=%u flags=%u", in->body.write.offset, in->body.write.size, Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.hh ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.hh Thu Mar 14 22:52:16 2019 (r345166) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh Thu Mar 14 23:05:59 2019 (r345167) @@ -86,8 +86,10 @@ union fuse_payloads_in { fuse_forget_in forget; fuse_interrupt_in interrupt; fuse_lk_in getlk; + fuse_getxattr_in getxattr; fuse_init_in init; fuse_link_in link; + fuse_listxattr_in listxattr; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; @@ -100,6 +102,7 @@ union fuse_payloads_in { fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; + fuse_setxattr_in setxattr; fuse_lk_in setlk; char unlink[0]; fuse_write_in write; @@ -117,7 +120,9 @@ union fuse_payloads_out { uint8_t bytes[0x20000]; fuse_entry_out entry; fuse_lk_out getlk; + fuse_getxattr_out getxattr; fuse_init_out init; + fuse_listxattr_out listxattr; fuse_open_out open; fuse_lk_out setlk; fuse_statfs_out statfs; Added: projects/fuse2/tests/sys/fs/fuse/xattr.cc ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/fuse2/tests/sys/fs/fuse/xattr.cc Thu Mar 14 23:05:59 2019 (r345167) @@ -0,0 +1,509 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include <sys/types.h> +#include <sys/extattr.h> +#include <string.h> +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +const char FULLPATH[] = "mountpoint/some_file.txt"; +const char RELPATH[] = "some_file.txt"; + +/* For testing filesystems without posix locking support */ +class Xattr: public FuseTest { +public: +void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes + + sizeof(fuse_getxattr_in); + return (in->header.opcode == FUSE_GETXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LISTXATTR && + in->header.nodeid == ino && + in->body.listxattr.size == size); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)) + .RetiresOnSaturation(); +} + +void expect_removexattr(uint64_t ino, const char *attr, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes; + return (in->header.opcode == FUSE_REMOVEXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + +void expect_setxattr(uint64_t ino, const char *attr, const char *value, + ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes + + sizeof(fuse_setxattr_in); + const char *v = a + strlen(a) + 1; + return (in->header.opcode == FUSE_SETXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a) && + 0 == strcmp(value, v)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +}; + +class Getxattr: public Xattr {}; +class Listxattr: public Xattr {}; +class Removexattr: public Xattr {}; +class Setxattr: public Xattr {}; + +/* + * If the extended attribute does not exist on this file, the daemon should + * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the + * correct errror code) + */ +TEST_F(Getxattr, enoattr) +{ + char data[80]; + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ENOATTR, errno); +} + +/* + * On FreeBSD, if the user passes an insufficiently large buffer then the + * filesystem is supposed to copy as much of the attribute's value as will fit. + * + * On Linux, however, the filesystem is supposed to return ERANGE. + * + * libfuse specifies the Linux behavior. However, that's probably an error. + * It would probably be correct for the filesystem to use platform-dependent + * behavior. + * + * This test case covers a filesystem that uses the Linux behavior + */ +TEST_F(Getxattr, erange) +{ + char data[10]; + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ERANGE, errno); +} + +/* + * If the user passes a 0-length buffer, then the daemon should just return the + * size of the attribute + */ +TEST_F(Getxattr, size_only) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getxattr); + out->body.getxattr.size = 99; + })); + + ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0)) + << strerror(errno);; +} + +/* + * Successfully get an attribute from the system namespace + */ +TEST_F(Getxattr, system) +{ + uint64_t ino = 42; + char data[80]; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_SYSTEM; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "system.foo", + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, value, value_len); + out->header.len = sizeof(out->header) + value_len; + }) + ); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(value_len, r) << strerror(errno); + EXPECT_STREQ(value, data); +} + +/* + * Successfully get an attribute from the user namespace + */ +TEST_F(Getxattr, user) +{ + uint64_t ino = 42; + char data[80]; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, value, value_len); + out->header.len = sizeof(out->header) + value_len; + }) + ); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(value_len, r) << strerror(errno); + EXPECT_STREQ(value, data); +} + +/* + * Listing extended attributes failed because they aren't configured on this + * filesystem + */ +TEST_F(Listxattr, enotsup) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnErrno(ENOTSUP)); + + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + ASSERT_EQ(ENOTSUP, errno); +} + +/* + * On FreeBSD, if the user passes an insufficiently large buffer then the + * filesystem is supposed to copy as much of the attribute's value as will fit. + * + * On Linux, however, the filesystem is supposed to return ERANGE. + * + * libfuse specifies the Linux behavior. However, that's probably an error. + * It would probably be correct for the filesystem to use platform-dependent + * behavior. + * + * This test case covers a filesystem that uses the Linux behavior + */ +TEST_F(Listxattr, erange) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnErrno(ERANGE)); + + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + ASSERT_EQ(ERANGE, errno); +} + +/* + * Get the size of the list that it would take to list no extended attributes + */ +TEST_F(Listxattr, size_only_empty) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 0; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +/* + * Get the size of the list that it would take to list some extended + * attributes. Due to the format differences between a FreeBSD and a + * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer + * and get the whole list, then convert it, just to figure out its size. + */ +TEST_F(Listxattr, size_only_nonempty) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 45; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. It should be exactly 45. + expect_listxattr(ino, 53, ReturnImmediate([](auto in, auto out) { + const char l[] = "user.foo"; + out->header.unique = in->header.unique; + strlcpy((char*)out->body.bytes, l, sizeof(out->body.bytes)); + out->header.len = sizeof(fuse_out_header) + sizeof(l); + })); + + ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +TEST_F(Listxattr, size_only_really_big) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 16000; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. It should be exactly 16000. + expect_listxattr(ino, 16008, ReturnImmediate([](auto in, auto out) { + const char l[16] = "user.foobarbang"; + out->header.unique = in->header.unique; + for (int i=0; i < 1000; i++) { + memcpy(&out->body.bytes[16 * i], l, 16); + } + out->header.len = sizeof(fuse_out_header) + 16000; + })); + + ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +/* + * List all of the user attributes of a file which has both user and system + * attributes + */ +TEST_F(Listxattr, user) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + char data[80]; + char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'}; + char attrs[28] = "user.foo\0system.x\0user.bang"; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. + expect_listxattr(ino, sizeof(attrs) + 8, + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); + out->header.len = sizeof(fuse_out_header) + sizeof(attrs); + })); + + ASSERT_EQ((ssize_t)sizeof(expected), + extattr_list_file(FULLPATH, ns, data, sizeof(data))) + << strerror(errno); + ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); +} + +/* + * List all of the system attributes of a file which has both user and system + * attributes + */ +TEST_F(Listxattr, system) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_SYSTEM; + char data[80]; + char expected[2] = {1, 'x'}; + char attrs[28] = "user.foo\0system.x\0user.bang"; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. + expect_listxattr(ino, sizeof(attrs) + 8, + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); + out->header.len = sizeof(fuse_out_header) + sizeof(attrs); + })); + + ASSERT_EQ((ssize_t)sizeof(expected), + extattr_list_file(FULLPATH, ns, data, sizeof(data))) + << strerror(errno); + ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); +} + +/* Fail to remove a nonexistent attribute */ +TEST_F(Removexattr, enoattr) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "user.foo", ENOATTR); + + ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); + ASSERT_EQ(ENOATTR, errno); +} + +/* Successfully remove a user xattr */ +TEST_F(Removexattr, user) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "user.foo", 0); + + ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) + << strerror(errno); +} + +/* Successfully remove a system xattr */ +TEST_F(Removexattr, system) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_SYSTEM; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "system.foo", 0); + + ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) + << strerror(errno); +} + +/* + * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem + * as currently configured doesn't support extended attributes. + */ +TEST_F(Setxattr, enotsup) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(-1, r); + EXPECT_EQ(ENOTSUP, errno); +} + +/* + * Successfully set a user attribute. + */ +TEST_F(Setxattr, user) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "user.foo", value, ReturnErrno(0)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(value_len, r) << strerror(errno); +} + +/* + * Successfully set a system attribute. + */ +TEST_F(Setxattr, system) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_SYSTEM; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "system.foo", value, ReturnErrno(0)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(value_len, r) << strerror(errno); +}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201903142305.x2EN5x9F080983>