/* 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 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 free_nt_path{[&] { ::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 close_fh{[&] { ::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(), TRUE, 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 close_fh{[&] { ::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