| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// Unit tests for getxattr, setxattr, and, listxattr. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "hdr/sys_stat_macros.h" |
| #include "hdr/sys_xattr_macros.h" |
| #include "hdr/types/size_t.h" |
| #include "hdr/types/ssize_t.h" |
| #include "src/__support/CPP/scope.h" |
| #include "src/__support/CPP/string_view.h" |
| #include "src/__support/libc_errno.h" |
| #include "src/fcntl/creat.h" |
| #include "src/sys/xattr/getxattr.h" |
| #include "src/sys/xattr/listxattr.h" |
| #include "src/sys/xattr/setxattr.h" |
| #include "src/unistd/close.h" |
| #include "src/unistd/symlink.h" |
| #include "src/unistd/unlink.h" |
| #include "test/UnitTest/ErrnoCheckingTest.h" |
| #include "test/UnitTest/ErrnoSetterMatcher.h" |
| #include "test/UnitTest/Test.h" |
| |
| namespace { |
| |
| using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher; |
| using LlvmLibcXattrTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest; |
| using LIBC_NAMESPACE::cpp::scope_exit; |
| using LIBC_NAMESPACE::cpp::string_view; |
| |
| int recreate_test_file(const char *path) { |
| LIBC_NAMESPACE::unlink(path); |
| LIBC_NAMESPACE::libc_errno = 0; |
| return LIBC_NAMESPACE::creat(path, S_IRWXU); |
| } |
| |
| int recreate_test_symlink(const char *target, const char *linkpath) { |
| LIBC_NAMESPACE::unlink(linkpath); |
| LIBC_NAMESPACE::libc_errno = 0; |
| return LIBC_NAMESPACE::symlink(target, linkpath); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, GetAndListWithNoAttributeSet) { |
| const LIBC_NAMESPACE::CString TEST_FILE_NAME = |
| libc_make_test_file_path("testdata/xattr_unset_testfile.txt"); |
| |
| int fd = recreate_test_file(TEST_FILE_NAME); |
| ASSERT_ERRNO_SUCCESS(); |
| ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0)); |
| scope_exit cleanup([&] { |
| ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE_NAME), Succeeds(0)); |
| }); |
| |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, "user.missing_test_attr", |
| buffer, BUFFER_SIZE), |
| Fails<ssize_t>(ENODATA)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::listxattr(TEST_FILE_NAME, nullptr, 0), |
| Succeeds<ssize_t>(0)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::listxattr(TEST_FILE_NAME, buffer, BUFFER_SIZE), |
| Succeeds<ssize_t>(0)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, SetGetAndListWithUserExtendedAttribute) { |
| const LIBC_NAMESPACE::CString TEST_FILE_NAME = |
| libc_make_test_file_path("testdata/xattr_testfile.txt"); |
| constexpr const char *TEST_SYMLINK_TARGET = "xattr_testfile.txt"; |
| const LIBC_NAMESPACE::CString TEST_SYMLINK_NAME = |
| libc_make_test_file_path("testdata/xattr_testsymlink.txt"); |
| |
| int fd = recreate_test_file(TEST_FILE_NAME); |
| ASSERT_ERRNO_SUCCESS(); |
| scope_exit unlink_file([&] { |
| ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE_NAME), Succeeds(0)); |
| }); |
| ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0)); |
| |
| ASSERT_THAT(recreate_test_symlink(TEST_SYMLINK_TARGET, TEST_SYMLINK_NAME), |
| Succeeds(0)); |
| scope_exit unlink_symlink([&] { |
| ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_SYMLINK_NAME), Succeeds(0)); |
| }); |
| |
| string_view XATTR_NAME = "user.test_attr"; |
| string_view XATTR_VALUE = "test_value"; |
| ASSERT_THAT(LIBC_NAMESPACE::setxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| XATTR_VALUE.data(), XATTR_VALUE.size(), |
| /* flags = */ 0), |
| Succeeds(0)); |
| size_t xattr_name_null_terminated_len = XATTR_NAME.size() + 1; |
| |
| // Get and list using the test file path. |
| EXPECT_THAT( |
| LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, XATTR_NAME.data(), nullptr, 0), |
| Succeeds<ssize_t>(XATTR_VALUE.size())); |
| EXPECT_THAT(LIBC_NAMESPACE::listxattr(TEST_FILE_NAME, nullptr, 0), |
| Succeeds<ssize_t>(xattr_name_null_terminated_len)); |
| { |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, XATTR_NAME.size()); |
| |
| char buffer[BUFFER_SIZE] = {}; |
| ASSERT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Succeeds(XATTR_VALUE.size())); |
| string_view result_str(buffer, XATTR_VALUE.size()); |
| EXPECT_EQ(result_str, XATTR_VALUE); |
| } |
| { |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, xattr_name_null_terminated_len); |
| |
| char buffer[BUFFER_SIZE] = {}; |
| ASSERT_THAT(LIBC_NAMESPACE::listxattr(TEST_FILE_NAME, buffer, BUFFER_SIZE), |
| Succeeds(xattr_name_null_terminated_len)); |
| string_view result_str(buffer, xattr_name_null_terminated_len - 1); |
| EXPECT_EQ(result_str, XATTR_NAME); |
| } |
| |
| // Get and list using the symlink path to verify the correct syscall is used |
| // internally; getxattr or listxattr, and not lgetxattr or llistxattr. |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_SYMLINK_NAME, XATTR_NAME.data(), |
| nullptr, 0), |
| Succeeds<ssize_t>(XATTR_VALUE.size())); |
| EXPECT_THAT(LIBC_NAMESPACE::listxattr(TEST_SYMLINK_NAME, nullptr, 0), |
| Succeeds<ssize_t>(xattr_name_null_terminated_len)); |
| { |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, XATTR_NAME.size()); |
| |
| char buffer[BUFFER_SIZE] = {}; |
| ASSERT_THAT(LIBC_NAMESPACE::getxattr(TEST_SYMLINK_NAME, XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Succeeds(XATTR_VALUE.size())); |
| string_view result_str(buffer, XATTR_VALUE.size()); |
| EXPECT_EQ(result_str, XATTR_VALUE); |
| } |
| { |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, xattr_name_null_terminated_len); |
| |
| char buffer[BUFFER_SIZE] = {}; |
| ASSERT_THAT( |
| LIBC_NAMESPACE::listxattr(TEST_SYMLINK_NAME, buffer, BUFFER_SIZE), |
| Succeeds(xattr_name_null_terminated_len)); |
| string_view result_str(buffer, xattr_name_null_terminated_len - 1); |
| EXPECT_EQ(result_str, XATTR_NAME); |
| } |
| |
| // Set another attribute through the symlink to verify the correct syscall is |
| // used internally; setxattr instead of lsetxattr. |
| { |
| string_view NEW_XATTR_NAME = "user.test_attr_through_symlink"; |
| string_view NEW_XATTR_VALUE = "test_value_through_symlink"; |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, NEW_XATTR_VALUE.size()); |
| char buffer[BUFFER_SIZE] = {}; |
| |
| ASSERT_THAT( |
| LIBC_NAMESPACE::setxattr(TEST_SYMLINK_NAME, NEW_XATTR_NAME.data(), |
| NEW_XATTR_VALUE.data(), NEW_XATTR_VALUE.size(), |
| /* flags = */ 0), |
| Succeeds(0)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, NEW_XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Succeeds<ssize_t>(NEW_XATTR_VALUE.size())); |
| ASSERT_EQ(string_view(buffer, NEW_XATTR_VALUE.size()), NEW_XATTR_VALUE); |
| } |
| |
| // Get and list with insufficient buffer size, and get with missing attribute |
| // to check failing errno. |
| { |
| constexpr size_t BUFFER_SIZE = 8; |
| ASSERT_LT(BUFFER_SIZE, xattr_name_null_terminated_len); |
| ASSERT_LT(BUFFER_SIZE, XATTR_VALUE.size()); |
| char buffer[BUFFER_SIZE] = {}; |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Fails<ssize_t>(ERANGE)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::listxattr(TEST_FILE_NAME, buffer, BUFFER_SIZE), |
| Fails<ssize_t>(ERANGE)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, |
| "user.missing_test_attr", buffer, |
| BUFFER_SIZE), |
| Fails<ssize_t>(ENODATA)); |
| } |
| } |
| |
| TEST_F(LlvmLibcXattrTest, SetAttributeWithNonzeroFlags) { |
| const LIBC_NAMESPACE::CString TEST_FILE_NAME = |
| libc_make_test_file_path("testdata/xattr_nonzero_flags_testfile.txt"); |
| int fd = recreate_test_file(TEST_FILE_NAME); |
| ASSERT_ERRNO_SUCCESS(); |
| scope_exit cleanup([&] { |
| ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE_NAME), Succeeds(0)); |
| }); |
| ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0)); |
| |
| string_view XATTR_NAME = "user.test_attr"; |
| string_view XATTR_VALUE = "test_value"; |
| constexpr size_t BUFFER_SIZE = 32; |
| ASSERT_GE(BUFFER_SIZE, XATTR_VALUE.size()); |
| char buffer[BUFFER_SIZE] = {}; |
| |
| // Try replace-only before attribute exists. |
| ASSERT_THAT(LIBC_NAMESPACE::setxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| XATTR_VALUE.data(), XATTR_VALUE.size(), |
| XATTR_REPLACE), |
| Fails(ENODATA)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::setxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| XATTR_VALUE.data(), XATTR_VALUE.size(), |
| /* flags = */ XATTR_CREATE), |
| Succeeds(0)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Succeeds<ssize_t>(XATTR_VALUE.size())); |
| ASSERT_EQ(string_view(buffer, XATTR_VALUE.size()), XATTR_VALUE); |
| |
| // Replace-only now that the attribute does exist. |
| string_view NEW_XATTR_VALUE = "new_test_value"; |
| ASSERT_THAT(LIBC_NAMESPACE::setxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| NEW_XATTR_VALUE.data(), |
| NEW_XATTR_VALUE.size(), XATTR_REPLACE), |
| Succeeds(0)); |
| |
| EXPECT_THAT(LIBC_NAMESPACE::getxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| buffer, BUFFER_SIZE), |
| Succeeds<ssize_t>(NEW_XATTR_VALUE.size())); |
| ASSERT_EQ(string_view(buffer, NEW_XATTR_VALUE.size()), NEW_XATTR_VALUE); |
| |
| // Try create-only when the attribute already exists. |
| ASSERT_THAT(LIBC_NAMESPACE::setxattr(TEST_FILE_NAME, XATTR_NAME.data(), |
| XATTR_VALUE.data(), XATTR_VALUE.size(), |
| XATTR_CREATE), |
| Fails(EEXIST)); |
| } |
| |
| #if defined(LIBC_ADD_NULL_CHECKS) |
| |
| TEST_F(LlvmLibcXattrTest, SetCrashesOnNullPath) { |
| EXPECT_DEATH( |
| [] { |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| LIBC_NAMESPACE::setxattr(nullptr, "user.attr", buffer, BUFFER_SIZE, |
| /* flags = */ 0); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, SetCrashesOnNullAttributeName) { |
| EXPECT_DEATH( |
| [] { |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| LIBC_NAMESPACE::setxattr("testdata/file.txt", nullptr, buffer, |
| BUFFER_SIZE, /* flags = */ 0); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, SetCrashesOnNullBufferNonZeroSize) { |
| EXPECT_DEATH( |
| [] { |
| LIBC_NAMESPACE::setxattr("testdata/file.txt", "user.attr", nullptr, 32, |
| /* flags = */ 0); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, GetCrashesOnNullPath) { |
| EXPECT_DEATH( |
| [] { |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| LIBC_NAMESPACE::getxattr(nullptr, "user.attr", buffer, BUFFER_SIZE); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, GetCrashesOnNullAttributeName) { |
| EXPECT_DEATH( |
| [] { |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| LIBC_NAMESPACE::getxattr("testdata/file.txt", nullptr, buffer, |
| BUFFER_SIZE); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, GetCrashesOnNullBufferNonZeroSize) { |
| EXPECT_DEATH( |
| [] { |
| LIBC_NAMESPACE::getxattr("testdata/file.txt", "user.attr", nullptr, 32); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST(LlvmLibcXattrTest, ListCrashesOnNullPath) { |
| EXPECT_DEATH( |
| [] { |
| constexpr size_t BUFFER_SIZE = 32; |
| char buffer[BUFFER_SIZE] = {}; |
| LIBC_NAMESPACE::listxattr(nullptr, buffer, BUFFER_SIZE); |
| }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| TEST_F(LlvmLibcXattrTest, ListCrashesOnNullBufferNonZeroSize) { |
| EXPECT_DEATH( |
| [] { LIBC_NAMESPACE::listxattr("testdata/file.txt", nullptr, 32); }, |
| WITH_SIGNAL(-1)); |
| } |
| |
| #endif // LIBC_ADD_NULL_CHECKS |
| |
| } // namespace |