feat(xattr_win): add (set|remove)xattr and improve error handling

This commit is contained in:
Marcus Holland-Moritz 2025-08-23 11:13:37 +02:00
parent 7bcc508dab
commit b7b9f4fd4d
2 changed files with 84 additions and 9 deletions

View File

@ -117,6 +117,31 @@ HANDLE open_file(std::filesystem::path const& path, bool writeable,
return fh; return fh;
} }
std::error_code error_code_from_nt_status(NTSTATUS status) {
switch (status) {
case STATUS_EAS_NOT_SUPPORTED:
return std::make_error_code(std::errc::operation_not_supported);
case STATUS_EA_TOO_LARGE:
return std::make_error_code(std::errc::argument_list_too_long);
case STATUS_NONEXISTENT_EA_ENTRY:
case STATUS_NO_EAS_ON_FILE:
case STATUS_NO_MORE_EAS:
return std::make_error_code(std::errc::no_message_available);
case STATUS_EA_CORRUPT_ERROR:
return std::error_code(ERROR_EA_FILE_CORRUPT, std::system_category());
case STATUS_INVALID_EA_NAME:
return std::error_code(ERROR_INVALID_EA_NAME, std::system_category());
case STATUS_EA_LIST_INCONSISTENT:
return std::error_code(ERROR_EA_LIST_INCONSISTENT, std::system_category());
default:
break;
}
// Last resort...
return std::error_code(::RtlNtStatusToDosError(status),
std::system_category());
}
} // namespace } // namespace
std::string getxattr(std::filesystem::path const& path, std::string const& name, std::string getxattr(std::filesystem::path const& path, std::string const& name,
@ -157,7 +182,7 @@ std::string getxattr(std::filesystem::path const& path, std::string const& name,
getea_len, nullptr, FALSE); getea_len, nullptr, FALSE);
if (res != STATUS_SUCCESS) { if (res != STATUS_SUCCESS) {
ec = std::error_code(::RtlNtStatusToDosError(res), std::system_category()); ec = error_code_from_nt_status(res);
return {}; return {};
} }
@ -171,12 +196,62 @@ std::string getxattr(std::filesystem::path const& path, std::string const& name,
void setxattr(std::filesystem::path const& path, std::string const& name, void setxattr(std::filesystem::path const& path, std::string const& name,
std::string_view value, std::error_code& ec) { std::string_view value, std::error_code& ec) {
// TODO ec.clear();
if (name.size() > std::numeric_limits<UCHAR>::max()) {
ec = std::error_code(ERROR_INVALID_EA_NAME, std::system_category());
return;
}
if (value.size() > std::numeric_limits<USHORT>::max()) {
ec = std::error_code(ERANGE, std::generic_category());
return;
}
auto fh = open_file(path, true, ec);
if (!fh) {
// error code already set
return;
}
scope_exit close_fh{[&] { ::NtClose(fh); }};
ULONG ea_len = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
static_cast<ULONG>(name.size()) + 1 +
static_cast<ULONG>(value.size());
std::vector<CHAR> buf(ea_len, 0);
auto ea = reinterpret_cast<PFILE_FULL_EA_INFORMATION>(buf.data());
ea->NextEntryOffset = 0;
ea->Flags = 0;
ea->EaNameLength = static_cast<UCHAR>(name.size());
ea->EaValueLength = static_cast<USHORT>(value.size());
std::memcpy(ea->EaName, name.data(), name.size());
ea->EaName[name.size()] = '\0';
std::memcpy(ea->EaName + name.size() + 1, value.data(), value.size());
IO_STATUS_BLOCK iosb;
auto res = ::NtSetEaFile(fh, &iosb, ea, ea_len);
if (res != STATUS_SUCCESS) {
ec = error_code_from_nt_status(res);
}
} }
void removexattr(std::filesystem::path const& path, std::string const& name, void removexattr(std::filesystem::path const& path, std::string const& name,
std::error_code& ec) { std::error_code& ec) {
// TODO // Windows EAs, unlike POSIX, do not support setting an empty value.
// Setting an empty value removes the attribute, hence we can implement
// removexattr by setting an empty value. For POSIX compatibility, we
// first check if the attribute exists, and return ENODATA if it doesn't.
getxattr(path, name, ec);
if (!ec) {
setxattr(path, name, {}, ec);
}
} }
std::vector<std::string> std::vector<std::string>
@ -203,9 +278,13 @@ listxattr(std::filesystem::path const& path, std::error_code& ec) {
auto res = ::NtQueryEaFile(fh, &iosb, ea, ea_buf.size(), FALSE, nullptr, 0, auto res = ::NtQueryEaFile(fh, &iosb, ea, ea_buf.size(), FALSE, nullptr, 0,
nullptr, restart); nullptr, restart);
if (res == STATUS_NO_EAS_ON_FILE || res == STATUS_NO_MORE_EAS) {
// no EAs found, return empty list
break;
}
if (res != STATUS_SUCCESS && res != STATUS_BUFFER_OVERFLOW) { if (res != STATUS_SUCCESS && res != STATUS_BUFFER_OVERFLOW) {
ec = ec = error_code_from_nt_status(res);
std::error_code(::RtlNtStatusToDosError(res), std::system_category());
return {}; return {};
} }

View File

@ -30,9 +30,6 @@
#include <dwarfs/xattr.h> #include <dwarfs/xattr.h>
TEST(xattr_test, portable_xattr) { TEST(xattr_test, portable_xattr) {
#ifdef _WIN32
GTEST_SKIP() << "Extended attributes are not fully implemented on Windows";
#else
dwarfs::temporary_directory td("dwarfs"); dwarfs::temporary_directory td("dwarfs");
auto const path = td.path() / "testfile"; auto const path = td.path() / "testfile";
auto const non_existant_path = td.path() / "non_existant_testfile"; auto const non_existant_path = td.path() / "non_existant_testfile";
@ -83,5 +80,4 @@ TEST(xattr_test, portable_xattr) {
EXPECT_EQ(dwarfs::getxattr(path, xattr_name, ec), std::string{}); EXPECT_EQ(dwarfs::getxattr(path, xattr_name, ec), std::string{});
EXPECT_TRUE(ec); EXPECT_TRUE(ec);
#endif
} }