diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f9a6f73..6e661ca7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -963,6 +963,10 @@ foreach(tgt dwarfs dwarfs_compression dwarfs_categorizer endif() target_compile_definitions(${tgt} PRIVATE DWARFS_COVERAGE_ENABLED=1) endif() + + if(WIN32) + target_link_libraries(${tgt} ntdll.lib) + endif() endforeach() # not sure why exactly, copied from fsst/CMakeLists.txt diff --git a/src/dwarfs/xattr.cpp b/src/dwarfs/xattr.cpp index f02a0450..18f6d7c0 100644 --- a/src/dwarfs/xattr.cpp +++ b/src/dwarfs/xattr.cpp @@ -20,24 +20,115 @@ */ #ifdef _WIN32 -#include + +#include + +#include +#include +#include +#include + +#include + #else + #include + #endif +#include #include +#include + #include "dwarfs/xattr.h" #ifdef _WIN32 +extern "C" { + +typedef struct _FILE_FULL_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + CHAR EaName[1]; +} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; + +typedef struct _FILE_GET_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR EaNameLength; + CHAR EaName[1]; +} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION; + +NTSYSAPI NTSTATUS NTAPI RtlDosPathNameToNtPathName_U_WithStatus( + PCWSTR DosFileName, PUNICODE_STRING NtFileName, PWSTR* FilePart, + PVOID RelativeName); + +VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING UnicodeString); + +NTSYSAPI NTSTATUS NTAPI NtQueryEaFile(HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID Buffer, ULONG Length, + BOOLEAN ReturnSingleEntry, PVOID EaList, + ULONG EaListLength, PULONG EaIndex, + BOOLEAN RestartScan); + +NTSYSAPI NTSTATUS NTAPI NtSetEaFile(HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID Buffer, ULONG Length); +} + #endif namespace dwarfs { namespace { -#ifndef _WIN32 +#ifdef _WIN32 + +constexpr size_t kMaxFullEaBufferSize{ + offsetof(FILE_FULL_EA_INFORMATION, EaName) + 256 + 65536}; +constexpr size_t kMaxGetEaBufferSize{offsetof(FILE_GET_EA_INFORMATION, EaName) + + 256}; + +HANDLE open_file(std::filesystem::path const& path, bool writeable, + std::error_code& ec) { + UNICODE_STRING nt_path; + + if (auto r = ::RtlDosPathNameToNtPathName_U_WithStatus( + path.wstring().c_str(), &nt_path, nullptr, nullptr); + r != 0) { + ec = std::error_code(r, std::system_category()); + return nullptr; + } + + SCOPE_EXIT { ::RtlFreeUnicodeString(&nt_path); }; + + HANDLE fh; + IO_STATUS_BLOCK iosb; + OBJECT_ATTRIBUTES attr; + ACCESS_MASK desired_access = FILE_READ_EA; + + if (writeable) { + desired_access |= FILE_WRITE_EA; + } + + InitializeObjectAttributes(&attr, &nt_path, 0, nullptr, nullptr); + + if (auto r = ::NtCreateFile( + &fh, desired_access, &attr, &iosb, nullptr, FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 0, + nullptr, 0); + r != 0) { + ec = std::error_code(::RtlNtStatusToDosError(r), std::system_category()); + return nullptr; + } + + return fh; +} + +#else ssize_t portable_getxattr(const char* path, const char* name, void* value, size_t size) { @@ -81,15 +172,118 @@ constexpr size_t kExtraSize{1024}; #ifdef _WIN32 -// TODO: Implement Windows xattr functions std::string getxattr(std::filesystem::path const& path, std::string const& name, - std::error_code& ec); + std::error_code& ec) { + ec.clear(); + + if (name.size() > std::numeric_limits::max()) { + ec = std::error_code(ERROR_INVALID_EA_NAME, std::system_category()); + return {}; + } + + auto fh = open_file(path, false, ec); + + if (!fh) { + // error code already set + return {}; + } + + SCOPE_EXIT { ::NtClose(fh); }; + + CHAR getea_buf[kMaxGetEaBufferSize]; + ULONG getea_len = + FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) + name.size() + 1; + auto getea = reinterpret_cast(getea_buf); + + getea->NextEntryOffset = 0; + getea->EaNameLength = static_cast(name.size()); + std::memcpy(getea->EaName, name.data(), name.size()); + getea->EaName[name.size()] = '\0'; + + std::vector ea_buf(kMaxFullEaBufferSize); + PFILE_FULL_EA_INFORMATION ea; + IO_STATUS_BLOCK iosb; + + ea = reinterpret_cast(ea_buf.data()); + + auto res = ::NtQueryEaFile(fh, &iosb, ea, ea_buf.size(), FALSE, getea, + getea_len, nullptr, FALSE); + + if (res != STATUS_SUCCESS) { + ec = std::error_code(::RtlNtStatusToDosError(res), std::system_category()); + return {}; + } + + if (ea->EaValueLength == 0) { + ec = std::error_code(ENODATA, std::generic_category()); + return {}; + } + + return {ea->EaName + ea->EaNameLength + 1, ea->EaValueLength}; +} + 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 +} + void removexattr(std::filesystem::path const& path, std::string const& name, - std::error_code& ec); + std::error_code& ec) { + // TODO +} + std::vector -listxattr(std::filesystem::path const& path, std::error_code& ec); +listxattr(std::filesystem::path const& path, std::error_code& ec) { + ec.clear(); + + auto fh = open_file(path, false, ec); + + if (!fh) { + // error code already set + return {}; + } + + SCOPE_EXIT { ::NtClose(fh); }; + + std::vector names; + std::vector ea_buf(kMaxFullEaBufferSize); + BOOLEAN restart = TRUE; + + for (;;) { + IO_STATUS_BLOCK iosb; + + auto ea = reinterpret_cast(ea_buf.data()); + auto res = ::NtQueryEaFile(fh, &iosb, ea, ea_buf.size(), FALSE, nullptr, 0, + nullptr, restart); + + if (res != STATUS_SUCCESS && res != STATUS_BUFFER_OVERFLOW) { + ec = + std::error_code(::RtlNtStatusToDosError(res), std::system_category()); + return {}; + } + + for (;;) { + std::string name(ea->EaName, ea->EaNameLength); + boost::algorithm::to_lower(name); + names.push_back(std::move(name)); + + if (ea->NextEntryOffset == 0) { + break; + } + + ea = reinterpret_cast( + reinterpret_cast(ea) + ea->NextEntryOffset); + } + + if (res == STATUS_SUCCESS) { + break; + } + + restart = FALSE; + } + + return names; +} #else