diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e661ca7..b2f993fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -515,6 +515,12 @@ list(APPEND LIBDWARFS_SRC src/dwarfs/xattr.cpp ) +if(WIN32) + list(APPEND LIBDWARFS_SRC src/dwarfs/xattr_win.cpp) +else() + list(APPEND LIBDWARFS_SRC src/dwarfs/xattr_posix.cpp) +endif() + if(WITH_MAN_OPTION) include(${CMAKE_SOURCE_DIR}/cmake/render_manpage.cmake) diff --git a/src/dwarfs/xattr.cpp b/src/dwarfs/xattr.cpp index 18f6d7c0..f9709449 100644 --- a/src/dwarfs/xattr.cpp +++ b/src/dwarfs/xattr.cpp @@ -19,369 +19,10 @@ * along with dwarfs. If not, see . */ -#ifdef _WIN32 - -#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 { - -#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) { -#ifdef __APPLE__ - return ::getxattr(path, name, value, size, 0, 0); -#else - return ::getxattr(path, name, value, size); -#endif -} - -int portable_setxattr(const char* path, const char* name, const void* value, - size_t size, int flags) { -#ifdef __APPLE__ - return ::setxattr(path, name, value, size, 0, flags); -#else - return ::setxattr(path, name, value, size, flags); -#endif -} - -int portable_removexattr(const char* path, const char* name) { -#ifdef __APPLE__ - return ::removexattr(path, name, 0); -#else - return ::removexattr(path, name); -#endif -} - -ssize_t portable_listxattr(const char* path, char* list, size_t size) { -#ifdef __APPLE__ - return ::listxattr(path, list, size, 0); -#else - return ::listxattr(path, list, size); -#endif -} - -#endif - -constexpr size_t kExtraSize{1024}; - -} // namespace - -#ifdef _WIN32 - -std::string getxattr(std::filesystem::path const& path, std::string const& name, - 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) { - // TODO -} - -void removexattr(std::filesystem::path const& path, std::string const& name, - std::error_code& ec) { - // TODO -} - -std::vector -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 - -std::string getxattr(std::filesystem::path const& path, std::string const& name, - std::error_code& ec) { - ec.clear(); - - auto cpath = path.c_str(); - auto cname = name.c_str(); - - for (;;) { - ssize_t size = portable_getxattr(cpath, cname, nullptr, 0); - - if (size < 0) { - break; - } - - std::string value; - value.resize(size + kExtraSize); - - size = portable_getxattr(cpath, cname, value.data(), value.size()); - - if (size >= 0) { - value.resize(size); - return value; - } - - if (errno != ERANGE) { - break; - } - } - - ec = std::error_code(errno, std::generic_category()); - - return {}; -} - -void setxattr(std::filesystem::path const& path, std::string const& name, - std::string_view value, std::error_code& ec) { - ec.clear(); - - if (portable_setxattr(path.c_str(), name.c_str(), value.data(), value.size(), - 0) < 0) { - ec = std::error_code(errno, std::generic_category()); - } -} - -void removexattr(std::filesystem::path const& path, std::string const& name, - std::error_code& ec) { - ec.clear(); - - if (portable_removexattr(path.c_str(), name.c_str()) < 0) { - ec = std::error_code(errno, std::generic_category()); - } -} - -std::vector -listxattr(std::filesystem::path const& path, std::error_code& ec) { - ec.clear(); - - auto cpath = path.c_str(); - - for (;;) { - ssize_t size = portable_listxattr(cpath, nullptr, 0); - - if (size < 0) { - break; - } - - std::string list; - list.resize(size + kExtraSize); - - size = portable_listxattr(cpath, list.data(), list.size()); - - if (size >= 0) { - std::vector names; - - if (size > 0) { - // drop the last '\0' - list.resize(size - 1); - folly::split('\0', list, names); - } - - return names; - } - - if (errno != ERANGE) { - break; - } - } - - ec = std::error_code(errno, std::generic_category()); - - return {}; -} - -#endif - std::string getxattr(std::filesystem::path const& path, std::string const& name) { std::error_code ec; diff --git a/src/dwarfs/xattr_posix.cpp b/src/dwarfs/xattr_posix.cpp new file mode 100644 index 00000000..296fe7e3 --- /dev/null +++ b/src/dwarfs/xattr_posix.cpp @@ -0,0 +1,163 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#include + +#include + +#include "dwarfs/xattr.h" + +namespace dwarfs { + +namespace { + +ssize_t portable_getxattr(const char* path, const char* name, void* value, + size_t size) { +#ifdef __APPLE__ + return ::getxattr(path, name, value, size, 0, 0); +#else + return ::getxattr(path, name, value, size); +#endif +} + +int portable_setxattr(const char* path, const char* name, const void* value, + size_t size, int flags) { +#ifdef __APPLE__ + return ::setxattr(path, name, value, size, 0, flags); +#else + return ::setxattr(path, name, value, size, flags); +#endif +} + +int portable_removexattr(const char* path, const char* name) { +#ifdef __APPLE__ + return ::removexattr(path, name, 0); +#else + return ::removexattr(path, name); +#endif +} + +ssize_t portable_listxattr(const char* path, char* list, size_t size) { +#ifdef __APPLE__ + return ::listxattr(path, list, size, 0); +#else + return ::listxattr(path, list, size); +#endif +} + +constexpr size_t kExtraSize{1024}; + +} // namespace + +std::string getxattr(std::filesystem::path const& path, std::string const& name, + std::error_code& ec) { + ec.clear(); + + auto cpath = path.c_str(); + auto cname = name.c_str(); + + for (;;) { + ssize_t size = portable_getxattr(cpath, cname, nullptr, 0); + + if (size < 0) { + break; + } + + std::string value; + value.resize(size + kExtraSize); + + size = portable_getxattr(cpath, cname, value.data(), value.size()); + + if (size >= 0) { + value.resize(size); + return value; + } + + if (errno != ERANGE) { + break; + } + } + + ec = std::error_code(errno, std::generic_category()); + + return {}; +} + +void setxattr(std::filesystem::path const& path, std::string const& name, + std::string_view value, std::error_code& ec) { + ec.clear(); + + if (portable_setxattr(path.c_str(), name.c_str(), value.data(), value.size(), + 0) < 0) { + ec = std::error_code(errno, std::generic_category()); + } +} + +void removexattr(std::filesystem::path const& path, std::string const& name, + std::error_code& ec) { + ec.clear(); + + if (portable_removexattr(path.c_str(), name.c_str()) < 0) { + ec = std::error_code(errno, std::generic_category()); + } +} + +std::vector +listxattr(std::filesystem::path const& path, std::error_code& ec) { + ec.clear(); + + auto cpath = path.c_str(); + + for (;;) { + ssize_t size = portable_listxattr(cpath, nullptr, 0); + + if (size < 0) { + break; + } + + std::string list; + list.resize(size + kExtraSize); + + size = portable_listxattr(cpath, list.data(), list.size()); + + if (size >= 0) { + std::vector names; + + if (size > 0) { + // drop the last '\0' + list.resize(size - 1); + folly::split('\0', list, names); + } + + return names; + } + + if (errno != ERANGE) { + break; + } + } + + ec = std::error_code(errno, std::generic_category()); + + return {}; +} + +} // namespace dwarfs diff --git a/src/dwarfs/xattr_win.cpp b/src/dwarfs/xattr_win.cpp new file mode 100644 index 00000000..2f2926dc --- /dev/null +++ b/src/dwarfs/xattr_win.cpp @@ -0,0 +1,229 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#include + +#include +#include +#include +#include + +#include + +#include + +#include "dwarfs/xattr.h" + +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); +} + +namespace dwarfs { + +namespace { + +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; +} + +} // namespace + +std::string getxattr(std::filesystem::path const& path, std::string const& name, + 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) { + // TODO +} + +void removexattr(std::filesystem::path const& path, std::string const& name, + std::error_code& ec) { + // TODO +} + +std::vector +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; +} + +} // namespace dwarfs