#include "resource_fork.h" #include #if defined(__CYGWIN__) || defined(__MSYS__) #if !defined(_WIN32) #define _WIN32 #endif #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #define XATTR_RESOURCEFORK_NAME "AFP_Resource" #else #include #include #include #include #include "xattr.h" #endif #ifdef __APPLE__ #include #include #ifndef _PATH_RSRCFORKSPEC #define _PATH_RSRCFORKSPEC "/..namedfork/rsrc" #endif #endif #if defined(__linux__) #define XATTR_RESOURCEFORK_NAME "user.com.apple.ResourceFork" #endif #ifndef XATTR_RESOURCEFORK_NAME #define XATTR_RESOURCEFORK_NAME "com.apple.ResourceFork" #endif #if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) #define XATTR_RESOURCE_FORK #include #endif namespace { // ENOATTR is not standard enough, so use ENODATA. #if defined(ENOATTR) && ENOATTR != ENODATA void remap_enoattr(std::error_code &ec) { if (ec.value() == ENOATTR) ec = std::make_error_code(std::errc::no_message_available); } #else void remap_enoattr(std::error_code &ec) {} #endif #if defined(_WIN32) #ifdef _MSC_VER #define AFP_ERROR_FILE_NOT_FOUND ERROR_FILE_NOT_FOUND #define remap_os_error(x) x #else #define AFP_ERROR_FILE_NOT_FOUND ENOENT extern "C" int remap_os_error(unsigned long); #endif BOOL _(BOOL x, std::error_code &ec) { if (!x) ec = std::error_code(remap_os_error(GetLastError()), std::system_category()); return x; } HANDLE _(HANDLE x, std::error_code &ec) { if (x == INVALID_HANDLE_VALUE) ec = std::error_code(remap_os_error(GetLastError()), std::system_category()); return x; } #undef CreateFile #undef DeleteFile template HANDLE CreateFile(const std::string &s, Args... args) { return CreateFileA(s.c_str(), std::forward(args)...); } template HANDLE CreateFile(const std::wstring &s, Args... args) { return CreateFileW(s.c_str(), std::forward(args)...); } template HANDLE CreateFileX(const StringType &s, afp::resource_fork::open_mode mode, std::error_code &ec) { DWORD access = 0; DWORD create = 0; switch (mode) { case afp::resource_fork::read_only: access = GENERIC_READ; create = OPEN_EXISTING; break; case afp::resource_fork::read_write: access = GENERIC_READ | GENERIC_WRITE; create = OPEN_ALWAYS; break; case afp::resource_fork::write_only: access = GENERIC_WRITE; create = OPEN_ALWAYS; break; } HANDLE h = _(CreateFile(s, access, FILE_SHARE_READ, nullptr, create, FILE_ATTRIBUTE_NORMAL, nullptr), ec); return h; } BOOL DeleteFile(const std::string &path) { return DeleteFileA(path.c_str()); } BOOL DeleteFile(const std::wstring &path) { return DeleteFileW(path.c_str()); } template bool DeleteFileX(const StringType &path, std::error_code &ec) { return _(DeleteFile(path), ec); } DWORD GetFileAttributesX(const std::string &path) { return GetFileAttributesA(path.c_str()); } DWORD GetFileAttributesX(const std::wstring &path) { return GetFileAttributesW(path.c_str()); } template bool regular_file(const StringType &path, std::error_code &ec) { // make sure this isn't a directory. DWORD st = GetFileAttributesX(path); if (st == INVALID_FILE_ATTRIBUTES) { ec = std::error_code(remap_os_error(GetLastError()), std::system_category()); return false; } if (st & FILE_ATTRIBUTE_DIRECTORY) { ec = std::make_error_code(std::errc::is_a_directory); return false; } if (st & FILE_ATTRIBUTE_DEVICE) { ec = std::make_error_code(std::errc::invalid_seek); return false; } return true; } #else template T _(T x, std::error_code &ec) { if (x < 0) ec = std::error_code(errno, std::system_category()); return x; } bool regular_file(int fd, std::error_code &ec) { struct stat st; if (_(::fstat(fd, &st), ec) < 0) { return false; } if (S_ISREG(st.st_mode)) return true; if (S_ISDIR(st.st_mode)) { ec = std::make_error_code(std::errc::is_a_directory); } else { ec = std::make_error_code(std::errc::invalid_seek); // ESPIPE. } return false; } /* opens a file read-only and verifies it's a regular file */ int openX(const std::string &path, std::error_code &ec) { int fd = _(::open(path.c_str(), O_RDONLY | O_NONBLOCK), ec); if (fd >= 0 && !regular_file(fd, ec)) { ::close(fd); fd = -1; } return fd; } #endif } namespace afp { resource_fork::resource_fork(resource_fork &&rhs) { std::swap(_fd, rhs._fd); #if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) std::swap(_offset, rhs._offset); std::swap(_mode, rhs._mode); #endif } resource_fork& resource_fork::operator=(resource_fork &&rhs) { if (this != &rhs) { close(); std::swap(_fd, rhs._fd); #if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) std::swap(_offset, rhs._offset); std::swap(_mode, rhs._mode); #endif } return *this; } #ifdef _WIN32 bool resource_fork::open(const std::string &path, open_mode mode, std::error_code &ec) { ec.clear(); close(); if (!regular_file(path, ec)) return false; std::string s(path); s += ":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return false; _fd = CreateFileX(s, mode, ec); CloseHandle(h); if (ec) { if (ec.value() == AFP_ERROR_FILE_NOT_FOUND) ec = std::make_error_code(std::errc::no_message_available); return false; } return true; } bool resource_fork::open(const std::wstring &path, open_mode mode, std::error_code &ec) { ec.clear(); close(); if (!regular_file(path, ec)) return false; std::wstring s(path); s += L":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return false; _fd = CreateFileX(s, mode, ec); CloseHandle(h); if (ec) { if (ec.value() == AFP_ERROR_FILE_NOT_FOUND) ec = std::make_error_code(std::errc::no_message_available); return false; } return true; } size_t resource_fork::write(const std::string &path, const void *buffer, size_t n, std::error_code &ec) { ec.clear(); if (!regular_file(path, ec)) return 0; std::string s(path); s += ":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return 0; HANDLE fd = _(CreateFile(s, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr), ec); if (ec) return 0; DWORD transferred = 0; BOOL ok = _(WriteFile(fd, buffer, n, &transferred, nullptr), ec); CloseHandle(h); CloseHandle(fd); if (ec) return 0; return transferred; } size_t resource_fork::write(const std::wstring &path, const void *buffer, size_t n, std::error_code &ec) { ec.clear(); if (!regular_file(path, ec)) return 0; std::wstring s(path); s += L":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return 0; HANDLE fd = _(CreateFile(s, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr), ec); if (ec) return 0; DWORD transferred = 0; BOOL ok = _(WriteFile(fd, buffer, n, &transferred, nullptr), ec); CloseHandle(h); CloseHandle(fd); if (ec) return 0; return transferred; } bool resource_fork::remove(const std::string &path, std::error_code &ec) { ec.clear(); if (!regular_file(path, ec)) return false; std::string s(path); s += ":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return false; CloseHandle(h); bool ok = DeleteFileX(s, ec); if (ec.value() == ERROR_FILE_NOT_FOUND) { ec = std::make_error_code(std::errc::no_message_available); return true; } return ok; } bool resource_fork::remove(const std::wstring &path, std::error_code &ec) { ec.clear(); if (!regular_file(path, ec)) return false; std::wstring s(path); s += L":" XATTR_RESOURCEFORK_NAME; HANDLE h = CreateFileX(path, read_only, ec); if (ec) return false; CloseHandle(h); bool ok = DeleteFileX(s, ec); if (ec.value() == ERROR_FILE_NOT_FOUND) { ec = std::make_error_code(std::errc::no_message_available); return true; } return ok; } void resource_fork::close() { CloseHandle(_fd); _fd = INVALID_HANDLE_VALUE; } size_t resource_fork::read(void *buffer, size_t n, std::error_code &ec) { ec.clear(); DWORD transferred = 0; BOOL ok = _(ReadFile(_fd, buffer, n, &transferred, nullptr), ec); if (ec) return 0; return transferred; } size_t resource_fork::write(const void *buffer, size_t n, std::error_code &ec) { ec.clear(); DWORD transferred = 0; BOOL ok = _(WriteFile(_fd, buffer, n, &transferred, nullptr), ec); if (ec) return 0; return transferred; } size_t resource_fork::size(std::error_code &ec) { ec.clear(); LARGE_INTEGER ll = { }; BOOL ok = _(GetFileSizeEx(_fd, &ll), ec); if (ec) return 0; return static_cast(ll.QuadPart); } bool resource_fork::truncate(size_t pos, std::error_code &ec) { ec.clear(); LARGE_INTEGER ll; BOOL ok; ll.QuadPart = pos; ok = _(SetFilePointerEx(_fd, ll, nullptr, FILE_BEGIN), ec); if (ec) return false; ok = _(SetEndOfFile(_fd), ec); if (ec) return false; return true; } bool resource_fork::seek(size_t pos, std::error_code &ec) { ec.clear(); LARGE_INTEGER ll; BOOL ok; ll.QuadPart = pos; ok = _(SetFilePointerEx(_fd, ll, nullptr, FILE_BEGIN), ec); if (ec) return false; return true; } #else void resource_fork::close() { ::close(_fd); _fd = -1; } #endif #ifdef __sun__ #define FD_RESOURCE_FORK bool resource_fork::open(const std::string &path, open_mode mode, std::error_code &ec) { ec.clear(); close(); int umode = 0; switch(mode) { case read_only: umode = O_RDONLY; break; case write_only: umode = O_WRONLY | O_CREAT; break; case read_write: umode = O_RDWR | O_CREAT; break; } int fd = openX(path, ec); if (ec) return false; _fd = _(::openat(fd, XATTR_RESOURCEFORK_NAME, umode | O_XATTR, 0666), ec); ::close(fd); if (ec) { if (ec.value() == ENOENT) ec = std::make_error_code(std::errc::no_message_available); // ENODATA. return false; } return true; } bool resource_fork::remove(const std::string &path, std::error_code &ec) { ec.clear(); int fd = openX(path, ec); if (ec) return false; int dirfd = _(::openat(fd, ".", O_RDONLY | O_XATTR), ec); ::close(fd); if (ec) return false; int ok = _(::unlinkat(dirfd, XATTR_RESOURCEFORK_NAME, 0), ec); ::close(dirfd); if (ec.value() == ENOENT) { ec = std::make_error_code(std::errc::no_message_available); // ENODATA. return true; } return ok == 0; } size_t resource_fork::write(const std::string &path, const void *buffer, size_t n, std::error_code &ec) { ec.clear(); int fd = openX(path, ec); if (ec) return 0; int rfd = _(::openat(fd, XATTR_RESOURCEFORK_NAME, O_WRONLY | O_CREAT | O_TRUNC | O_XATTR, 0666), ec); ::close(fd); if (ec) { if (ec.value() == ENOENT) ec = std::make_error_code(std::errc::no_message_available); // ENODATA. return 0; } auto rv = _(::write(rfd, buffer, n), ec); ::close(rfd); if (rv < 0) return 0; return rv; } #endif #ifdef __APPLE__ #define FD_RESOURCE_FORK bool resource_fork::open(const std::string &path, open_mode mode, std::error_code &ec) { ec.clear(); close(); std::string s(path); s += _PATH_RSRCFORKSPEC; int fd = openX(path, ec); if (ec) return false; int umode = 0; switch(mode) { case read_only: umode = O_RDONLY; break; case write_only: umode = O_WRONLY | O_CREAT; break; case read_write: umode = O_RDWR | O_CREAT; break; } _fd = _(::open(s.c_str(), umode, 0666), ec); ::close(fd); if (ec) { if (ec.value() == ENOENT) ec = std::make_error_code(std::errc::no_message_available); return false; } return true; } bool resource_fork::remove(const std::string &path, std::error_code &ec) { ec.clear(); std::string s(path); s += _PATH_RSRCFORKSPEC; int fd = openX(path, ec); if (ec) return false; ::close(fd); int ok = _(::unlink(s.c_str()), ec); if (ec.value() == ENOENT) { ec = std::make_error_code(std::errc::no_message_available); return true; } return ok == 0; } size_t resource_fork::write(const std::string &path, const void *buffer, size_t n, std::error_code &ec) { ec.clear(); std::string s(path); s += _PATH_RSRCFORKSPEC; int fd = openX(path, ec); if (ec) return false; ::close(fd); int rfd = _(::open(s.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666), ec); if (rfd < 0) return 0; auto rv = _(::write(rfd, buffer, n), ec); ::close(rfd); if (rv < 0) return 0; return rv; } #endif #ifdef FD_RESOURCE_FORK size_t resource_fork::read(void *buffer, size_t n, std::error_code &ec) { ec.clear(); auto rv = _(::read(_fd, buffer, n), ec); if (ec) return 0; return rv; } size_t resource_fork::write(const void *buffer, size_t n, std::error_code &ec) { ec.clear(); auto rv = _(::write(_fd, buffer, n), ec); if (ec) return 0; return rv; } bool resource_fork::truncate(size_t pos, std::error_code &ec) { ec.clear(); _(::ftruncate(_fd, pos), ec); if (ec) return false; return true; } bool resource_fork::seek(size_t pos, std::error_code &ec) { ec.clear(); _(::lseek(_fd, pos, SEEK_SET), ec); if (ec) return false; return true; } size_t resource_fork::size(std::error_code &ec) { ec.clear(); struct stat st; _(::fstat(_fd, &st), ec); if (ec) return 0; return st.st_size; } #endif #ifdef XATTR_RESOURCE_FORK namespace { std::vector read_rfork(int _fd, std::error_code &ec) { std::vector rv; for(;;) { ssize_t size = 0; ssize_t tsize = 0; rv.clear(); ec.clear(); size = _(::size_xattr(_fd, XATTR_RESOURCEFORK_NAME), ec); if (ec) return rv; if (size == 0) return rv; rv.resize(size); tsize = _(::read_xattr(_fd, XATTR_RESOURCEFORK_NAME, rv.data(), size), ec); if (ec) { if (ec.value() == ERANGE) continue; rv.clear(); return rv; } rv.resize(tsize); return rv; } } } bool resource_fork::open(const std::string &path, open_mode mode, std::error_code &ec) { close(); ec.clear(); _fd = openX(path, ec); if (ec) return false; _mode = mode; _offset = 0; return true; } size_t resource_fork::size(std::error_code &ec) { ec.clear(); auto rv = _(::size_xattr(_fd, XATTR_RESOURCEFORK_NAME), ec); if (ec) { remap_enoattr(ec); return 0; } return rv; } size_t resource_fork::read(void *buffer, size_t n, std::error_code &ec) { ec.clear(); if (_fd < 0 || _mode == write_only) { ec = std::make_error_code(std::errc::bad_file_descriptor); return 0; } if (n == 0) return 0; auto tmp = read_rfork(_fd, ec); if (ec) { remap_enoattr(ec); return 0; } if (_offset >= tmp.size()) return 0; size_t count = std::min(n, tmp.size() - _offset); std::memcpy(buffer, tmp.data() + _offset, count); _offset += count; return count; } size_t resource_fork::write(const void *buffer, size_t n, std::error_code &ec) { ec.clear(); if (_fd < 0 || _mode == read_only) { ec = std::make_error_code(std::errc::bad_file_descriptor); return 0; } if (n == 0) return 0; auto tmp = read_rfork(_fd, ec); if (ec) { remap_enoattr(ec); return 0; } if (_offset > tmp.size()) { tmp.resize(_offset); } tmp.insert(tmp.end(), (const uint8_t *)buffer, (const uint8_t *)buffer + n); auto rv = _(::write_xattr(_fd, XATTR_RESOURCEFORK_NAME, tmp.data(), tmp.size()), ec); if (ec) { remap_enoattr(ec); return 0; } return n; } bool resource_fork::truncate(size_t pos, std::error_code &ec) { ec.clear(); if (_fd < 0 || _mode == read_only) { ec = std::make_error_code(std::errc::bad_file_descriptor); return 0; } // simple case.. if (pos == 0) { auto rv = _(::remove_xattr(_fd, XATTR_RESOURCEFORK_NAME), ec); if (ec) { remap_enoattr(ec); // consider that ok? return false; } return true; } auto tmp = read_rfork(_fd, ec); if (ec) { remap_enoattr(ec); return false; } if (tmp.size() == pos) return true; tmp.resize(pos); auto rv = _(::write_xattr(_fd, XATTR_RESOURCEFORK_NAME, tmp.data(), pos), ec); if (ec) { remap_enoattr(ec); return false; } _offset = pos; return true; } bool resource_fork::seek(size_t pos, std::error_code &ec) { ec.clear(); if (_fd < 0) { ec = std::make_error_code(std::errc::bad_file_descriptor); return 0; } _offset = pos; return true; } bool resource_fork::remove(const std::string &path, std::error_code &ec) { ec.clear(); int fd = openX(path, ec); if (ec) return false; int rv = _(::remove_xattr(fd, XATTR_RESOURCEFORK_NAME), ec); ::close(fd); if (rv < 0) { remap_enoattr(ec); if (ec.value() == ENODATA) return true; } return rv == 0; } size_t resource_fork::write(const std::string &path, const void *buffer, size_t n, std::error_code &ec) { ec.clear(); int fd = openX(path, ec); if (ec) return false; auto rv = _(::write_xattr(fd, XATTR_RESOURCEFORK_NAME, buffer, n), ec); ::close(fd); if (rv < 0) { remap_enoattr(ec); return 0; } return rv; } #endif size_t resource_fork::size(const std::string &path, std::error_code &ec) { resource_fork rf; rf.open(path, read_only, ec); if (ec) return 0; return rf.size(ec); } #ifdef _WIN32 size_t resource_fork::size(const std::wstring &path, std::error_code &ec) { resource_fork rf; rf.open(path, read_only, ec); if (ec) return 0; return rf.size(ec); } #endif }