From f398ac94f07a1a11624ce15b6d6159d6a30767e3 Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Mon, 24 Jul 2017 16:00:39 -0400 Subject: [PATCH] first version (sort of) --- CMakeLists.txt | 12 + include/afp/finder_info.h | 147 +++++++ include/afp/resource_fork.h | 70 ++++ include/afp/xattr.h | 27 ++ src/finder_info.cpp | 743 ++++++++++++++++++++++++++++++++++++ src/resource_fork.cpp | 535 ++++++++++++++++++++++++++ src/xattr.c | 101 +++++ 7 files changed, 1635 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/afp/finder_info.h create mode 100644 include/afp/resource_fork.h create mode 100644 include/afp/xattr.h create mode 100644 src/finder_info.cpp create mode 100644 src/resource_fork.cpp create mode 100644 src/xattr.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8c80eb3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.1) + + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS FALSE) + + +add_library(afp src/finder_info.cpp src/resource_fork.cpp src/xattr.c) + +target_include_directories(afp INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/) +target_include_directories(afp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/afp) diff --git a/include/afp/finder_info.h b/include/afp/finder_info.h new file mode 100644 index 0000000..a67f9aa --- /dev/null +++ b/include/afp/finder_info.h @@ -0,0 +1,147 @@ +#ifndef __afp_finder_info_h__ +#define __afp_finder_info_h__ + +#include +#include + +#include + + +#if defined(_WIN32) +#pragma pack(push, 2) +struct AFP_Info { + uint32_t magic; + uint32_t version; + uint32_t file_id; + uint32_t backup_date; + uint8_t finder_info[32]; + uint16_t prodos_file_type; + uint32_t prodos_aux_type; + uint8_t reserved[6]; +}; +#pragma pack(pop) + +#endif + +namespace afp { + class finder_info { + + public: + + enum open_mode { + read_only = 1, + write_only = 2, + read_write = 3, + }; + + finder_info(); + ~finder_info(); + + finder_info(const finder_info &) = delete; + finder_info(finder_info &&) = delete; + + finder_info& operator=(const finder_info &) = delete; + finder_info& operator=(finder_info &&) = delete; + + + const uint8_t *data() const { + #if defined(_WIN32) + return _afp.finder_info; + #else + return _finder_info; + #endif + } + + uint8_t *data() { + #if defined(_WIN32) + return _afp.finder_info; + #else + return _finder_info; + #endif + } + + + bool read(const std::string &fname, std::error_code &ec) { + return open(fname, read_only, ec); + } + + bool write(const std::string &fname, std::error_code &ec); + + bool open(const std::string &fname, open_mode perm, std::error_code &ec); + bool open(const std::string &fname, std::error_code &ec) { + return open(fname, read_only, ec); + } + + + #if defined(_WIN32) + bool read(const std::wstring &pathName, std::error_code &ec) { + return open(pathName, read_only, ec); + } + + bool write(const std::wstring &pathName, std::error_code &ec); + + bool open(const std::wstring &fname, open_mode perm, std::error_code &ec); + bool open(const std::wstring &fname, std::error_code &ec) { + return open(fname, read_only, ec); + } + + #endif + + + bool write(std::error_code &ec); + + uint32_t creator_type() const; + uint32_t file_type() const; + + uint16_t prodos_file_type() const { + #if defined(_WIN32) + return _afp.prodos_file_type; + #else + return _prodos_file_type; + #endif + } + + uint32_t prodos_aux_type() const { + #if defined(_WIN32) + return _afp.prodos_aux_type; + #else + return _prodos_aux_type; + #endif + } + + void set_prodos_file_type(uint16_t); + void set_prodos_file_type(uint16_t, uint32_t); + + void set_file_type(uint32_t); + void set_creator_type(uint32_t); + + bool is_text() const; + bool is_binary() const; + + private: + + void close(); + + #if defined(_WIN32) + bool write(void *handle, std::error_code &ec); + bool read(void *handle, std::error_code &ec); + #else + bool write(int fd, std::error_code &ec); + bool read(int fd, std::error_code &ec); + #endif + + #if defined(_WIN32) + void *_fd = (void *)-1; + AFP_Info _afp; + #else + int _fd = -1; + + uint16_t _prodos_file_type = 0; + uint32_t _prodos_aux_type = 0; + uint8_t _finder_info[32] = {}; + #endif + }; + +} + +#endif diff --git a/include/afp/resource_fork.h b/include/afp/resource_fork.h new file mode 100644 index 0000000..759bb64 --- /dev/null +++ b/include/afp/resource_fork.h @@ -0,0 +1,70 @@ +#ifndef __afp_resource_fork_h__ +#define __afp_resource_fork_h__ + +#include +#include + +namespace afp { + + class resource_fork { + + public: + enum open_mode { + read_only = 1, + write_only = 2, + read_write = 3, + }; + + resource_fork() = default; + resource_fork(const resource_fork &) = delete; + resource_fork(resource_fork &&rhs); + + resource_fork& operator=(const resource_fork &rhs) = delete; + resource_fork& operator=(resource_fork &&rhs); + + + ~resource_fork() { close(); } + + static size_t size(const std::string &path, std::error_code &ec); +#ifdef _WIN32 + static size_t size(const std::wstring &path, std::error_code &ec); +#endif + + bool open(const std::string &s, open_mode mode, std::error_code &ec); + + bool open(const std::string &s, std::error_code &ec) { + return open(s, read_only, ec); + } + +#ifdef _WIN32 + bool open(const std::wstring &s, open_mode mode, std::error_code &ec); + bool open(const std::wstring &s, std::error_code &ec) { + return open(s, read_only, ec); + } +#endif + + void close(); + + size_t read(void *buffer, size_t n, std::error_code &); + size_t write(const void *buffer, size_t n, std::error_code &); + bool truncate(size_t pos, std::error_code &ec); + bool seek(size_t pos, std::error_code &ec); + size_t size(std::error_code &ec); + + + private: + #ifdef _WIN32 + void *_fd = (void *)-1; + #else + int _fd = -1; + #endif + + #if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) + size_t _offset = 0; + open_mode _mode = read_only; + #endif + }; + +} + +#endif diff --git a/include/afp/xattr.h b/include/afp/xattr.h new file mode 100644 index 0000000..28bc498 --- /dev/null +++ b/include/afp/xattr.h @@ -0,0 +1,27 @@ +#ifndef xattr_h +#define xattr_h + +#include +#include + +#include + +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +ssize_t size_xattr(int fd, const char *xattr); +ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size); +ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size); +int remove_xattr(int fd, const char *xattr); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/finder_info.cpp b/src/finder_info.cpp new file mode 100644 index 0000000..bc0905c --- /dev/null +++ b/src/finder_info.cpp @@ -0,0 +1,743 @@ +#include "finder_info.h" + +#include +#include +#include +#include + +#include +#include + +#if defined(__APPLE__) +#include +#endif + +#if defined(__linux__) +#include +#define XATTR_FINDERINFO_NAME "user.com.apple.FinderInfo" +#define XATTR_RESOURCEFORK_NAME "user.com.apple.ResourceFork" +#endif + +#if defined(__FreeBSD__) +#include +#include +#endif + +#if defined(_AIX) +#include +#endif + + +#if defined (_WIN32) +#include +#define XATTR_FINDERINFO_NAME "AFP_AfpInfo" +#endif + +#ifndef XATTR_FINDERINFO_NAME +#define XATTR_FINDERINFO_NAME "com.apple.FinderInfo" +#endif + + +#if defined(_WIN32) +#define _prodos_file_type _afp.prodos_file_type +#define _prodos_aux_type _afp.prodos_aux_type +#define _finder_info _afp.finder_info +#endif + +namespace { + + /* + + tech note PT515 + ProDOS -> Macintosh conversion + + ProDOS Macintosh + Filetype Auxtype Creator Filetype + $00 $0000 'pdos' 'BINA' + $B0 (SRC) (any) 'pdos' 'TEXT' + $04 (TXT) $0000 'pdos' 'TEXT' + $FF (SYS) (any) 'pdos' 'PSYS' + $B3 (S16) (any) 'pdos' 'PS16' + $uv $wxyz 'pdos' 'p' $uv $wx $yz + + Programmer's Reference for System 6.0: + + ProDOS Macintosh + File Type Auxiliary Type Creator Type File Type + $00 $0000 'pdos' 'BINA' + $04 (TXT) $0000 'pdos' 'TEXT' + $FF (SYS) (any) 'pdos' 'PSYS' + $B3 (S16) $DByz 'pdos' 'p' $B3 $DB $yz + $B3 (S16) (any) 'pdos' 'PS16' + $D7 $0000 'pdos' 'MIDI' + $D8 $0000 'pdos' 'AIFF' + $D8 $0001 'pdos' 'AIFC' + $E0 $0005 'dCpy' 'dImg' + $FF (SYS) (any) 'pdos' 'PSYS' + $uv $wxyz 'pdos' 'p' $uv $wx $yz + + + mpw standard: + $uv (any) "pdos" printf("%02x ",$uv) + + */ + + + + int hex(uint8_t c) + { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c + 10 - 'a'; + if (c >= 'A' && c <= 'F') return c + 10 - 'A'; + return 0; + } + + + bool finder_info_to_filetype(const uint8_t *buffer, uint16_t *file_type, uint32_t *aux_type) { + + if (!memcmp("pdos", buffer + 4, 4)) + { + if (buffer[0] == 'p') { + *file_type = buffer[1]; + *aux_type = (buffer[2] << 8) | buffer[3]; + return true; + } + if (!memcmp("PSYS", buffer, 4)) { + *file_type = 0xff; + *aux_type = 0x0000; + return true; + } + if (!memcmp("PS16", buffer, 4)) { + *file_type = 0xb3; + *aux_type = 0x0000; + return true; + } + + // old mpw method for encoding. + if (!isxdigit(buffer[0]) && isxdigit(buffer[1]) && buffer[2] == ' ' && buffer[3] == ' ') + { + *file_type = (hex(buffer[0]) << 8) | hex(buffer[1]); + *aux_type = 0; + return true; + } + } + if (!memcmp("TEXT", buffer, 4)) { + *file_type = 0x04; + *aux_type = 0x0000; + return true; + } + if (!memcmp("BINA", buffer, 4)) { + *file_type = 0x00; + *aux_type = 0x0000; + return true; + } + if (!memcmp("dImgdCpy", buffer, 8)) { + *file_type = 0xe0; + *aux_type = 0x0005; + return true; + } + + if (!memcmp("MIDI", buffer, 4)) { + *file_type = 0xd7; + *aux_type = 0x0000; + return true; + } + + if (!memcmp("AIFF", buffer, 4)) { + *file_type = 0xd8; + *aux_type = 0x0000; + return true; + } + + if (!memcmp("AIFC", buffer, 4)) { + *file_type = 0xd8; + *aux_type = 0x0001; + return true; + } + + return false; + } + + bool file_type_to_finder_info(uint8_t *buffer, uint16_t file_type, uint32_t aux_type) { + if (file_type > 0xff || aux_type > 0xffff) return false; + + if (!file_type && aux_type == 0x0000) { + memcpy(buffer, "BINApdos", 8); + return true; + } + + if (file_type == 0x04 && aux_type == 0x0000) { + memcpy(buffer, "TEXTpdos", 8); + return true; + } + + if (file_type == 0xff && aux_type == 0x0000) { + memcpy(buffer, "PSYSpdos", 8); + return true; + } + + if (file_type == 0xb3 && aux_type == 0x0000) { + memcpy(buffer, "PS16pdos", 8); + return true; + } + + if (file_type == 0xd7 && aux_type == 0x0000) { + memcpy(buffer, "MIDIpdos", 8); + return true; + } + if (file_type == 0xd8 && aux_type == 0x0000) { + memcpy(buffer, "AIFFpdos", 8); + return true; + } + if (file_type == 0xd8 && aux_type == 0x0001) { + memcpy(buffer, "AIFCpdos", 8); + return true; + } + if (file_type == 0xe0 && aux_type == 0x0005) { + memcpy(buffer, "dImgdCpy", 8); + return true; + } + + + memcpy(buffer, "p pdos", 8); + buffer[1] = (file_type) & 0xff; + buffer[2] = (aux_type >> 8) & 0xff; + buffer[3] = (aux_type) & 0xff; + return true; + } + + + template + T _(const T t, std::error_code &ec) { + if (t < 0) ec = std::error_code(errno, std::generic_category()); + return t; + } + + /* + * extended attributes functions. + */ + #if defined(__APPLE__) + ssize_t size_xattr(int fd, const char *xattr) { + return fgetxattr(fd, xattr, NULL, 0, 0, 0); + } + + ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetxattr(fd, xattr, buffer, size, 0, 0); + } + + ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetxattr(fd, xattr, buffer, size, 0, 0) < 0) return -1; + return size; + } + + int remove_xattr(int fd, const char *xattr) { + return fremovexattr(fd, xattr, 0); + } + + #elif defined(__linux__) + ssize_t size_xattr(int fd, const char *xattr) { + return fgetxattr(fd, xattr, NULL, 0); + } + + ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetxattr(fd, xattr, buffer, size); + } + + ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetxattr(fd, xattr, buffer, size, 0) < 0) return -1; + return size; + } + + int remove_xattr(int fd, const char *xattr) { + return fremovexattr(fd, xattr); + } + + #elif defined(__FreeBSD__) + ssize_t size_xattr(int fd, const char *xattr) { + return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, xattr, NULL, 0); + } + + ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, xattr, buffer, size); + } + + ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + return extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, xattr, buffer, size); + } + + int remove_xattr(int fd, const char *xattr) { + return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, xattr); + } + + #elif defined(_AIX) + ssize_t size_xattr(int fd, const char *xattr) { + /* + struct stat64x st; + if (fstatea(fd, xattr, &st) < 0) return -1; + return st.st_size; + */ + return fgetea(fd, xattr, NULL, 0); + } + + ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetea(fd, xattr, buffer, size); + } + + ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetea(fd, xattr, buffer, size, 0) < 0) return -1; + return size; + } + + int remove_xattr(int fd, const char *xattr) { + return fremoveea(fd, xattr); + } + + #endif + + + void set_or_throw_error(std::error_code *ec, int error, const std::string &what) { + if (ec) *ec = std::error_code(error, std::system_category()); + else throw std::system_error(error, std::system_category(), what); + } + + + + +#if defined(_WIN32) + + /* + * allocating a new string could reset GetLastError() to 0. + */ + void set_or_throw_error(std::error_code *ec, const char *what) { + auto e = GetLastError(); + set_or_throw_error(ec, e, what); + } + + void set_or_throw_error(std::error_code *ec, const std::string &what) { + auto e = GetLastError(); + set_or_throw_error(ec, e, what); + } + + template + HANDLE CreateFileX(const std::string &s, Args... args) { + return CreateFileA(s.c_str(), std::forward(args)...); + } + + template + HANDLE CreateFileX(const std::wstring &s, Args... args) { + return CreateFileW(s.c_str(), std::forward(args)...); + } + + void fi_close(void *fd) { + CloseHandle(fd); + } + + void *fi_open(const std::string &path, int perm, std::error_code &ec) { + + ec.clear(); + + std::string s(path); + s.append(":" XATTR_FINDERINFO_NAME); + + HANDLE fh; + bool ro = perm == 1; + + fh = CreateFileX(s, + ro ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + nullptr, + ro ? OPEN_EXISTING : OPEN_ALWAYS, + ro ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (fh == INVALID_HANDLE_VALUE) + set_or_throw_error(&ec, "CreateFile"); + + return fh; + } + + void *fi_open(const std::wstring &path, int perm, std::error_code &ec) { + + ec.clear(); + + std::wstring s(path); + s.append(L":" XATTR_FINDERINFO_NAME); + + HANDLE fh; + bool ro = perm == 1; + + fh = CreateFileX(s, + ro ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + nullptr, + ro ? OPEN_EXISTING : OPEN_ALWAYS, + ro ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (fh == INVALID_HANDLE_VALUE) + set_or_throw_error(&ec, "CreateFile"); + + return fh; + } + + int fi_write(void *handle, const void *data, int length, std::error_code &ec) { + + ec.clear(); + DWORD rv = 0; + BOOL ok; + + LARGE_INTEGER zero = { 0 }; + + ok = SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN); + if (!ok) { + set_or_throw_error(&ec, "SetFilePointerEx"); + return 0; + } + ok = WriteFile(handle, data, length, &rv, nullptr); + if (!ok) { + set_or_throw_error(&ec, "WriteFile"); + return 0; + } + return rv; + } + + int fi_read(void *handle, void *data, int length, std::error_code &ec) { + + ec.clear(); + DWORD rv = 0; + BOOL ok; + LARGE_INTEGER zero = { 0 }; + + ok = SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN); + if (!ok) { + set_or_throw_error(&ec, "SetFilePointerEx"); + return 0; + } + ok = ReadFile(handle, data, length, &rv, nullptr); + if (!ok) { + set_or_throw_error(&ec, "ReadFile"); + return 0; + } + return rv; + } + +#else + + void fi_close(int fd) { + close(fd); + } + + int fi_open(const std::string &path, int perm, std::error_code &ec) { + + #if defined(__sun__) + if (perm == 1) return attropen(path.c_str(), XATTR_FINDERINFO_NAME, O_RDONLY); + else return attropen(path.c_str(), XATTR_FINDERINFO_NAME, O_RDWR | O_CREAT, 0666); + #else + // linux needs to open as read/write to write it? + //return open(path.c_str(), read_only ? O_RDONLY : O_RDWR); + return open(path.c_str(), O_RDONLY); + #endif + } + + + int fi_write(int fd, const void *data, int length, std::error_code &ec) { + #if defined(__sun__) + lseek(fd, 0, SEEK_SET); + return write(fd, data, length); + #else + return write_xattr(fd, XATTR_FINDERINFO_NAME, data, length); + #endif + } + + int fi_read(int fd, void *data, int length, std::error_code &ec) { + #if defined(__sun__) + lseek(fd, 0, SEEK_SET); + return read(fd, data, length); + #else + return read_xattr(fd, XATTR_FINDERINFO_NAME, data, length); + #endif + } + +#endif + + +#if defined(_WIN32) +void afp_init(struct AFP_Info *info) { + //static_assert(sizeof(AFP_Info) == 60, "Incorrect AFP_Info size"); + memset(info, 0, sizeof(*info)); + info->magic = 0x00504641; + info->version = 0x00010000; + info->backup_date = 0x80000000; +} + +int afp_verify(struct AFP_Info *info) { + if (!info) return 0; + + if (info->magic != 0x00504641) return 0; + if (info->version != 0x00010000) return 0; + + return 1; +} + + +int afp_to_filetype(struct AFP_Info *info, uint16_t *file_type, uint32_t *aux_type) { + // check for prodos ftype/auxtype... + if (info->prodos_file_type || info->prodos_aux_type) { + *file_type = info->prodos_file_type; + *aux_type = info->prodos_aux_type; + return 0; + } + int ok = finder_info_to_filetype(info->finder_info, file_type, aux_type); + if (ok == 0) { + info->prodos_file_type = *file_type; + info->prodos_aux_type = *aux_type; + } + return 0; +} + +enum { + trust_prodos, + trust_hfs +}; + +void afp_synchronize(struct AFP_Info *info, int trust) { + // if ftype/auxtype is inconsistent between prodos and finder info, use + // prodos as source of truth. + uint16_t f = 0; + uint32_t a = 0; + if (finder_info_to_filetype(info->finder_info, &f, &a)) { + if (f == info->prodos_file_type && a == info->prodos_aux_type) return; + } + + if (trust == trust_prodos) + file_type_to_finder_info(info->finder_info, info->prodos_file_type, info->prodos_aux_type); + else { + info->prodos_file_type = f; + info->prodos_aux_type = a; + } +} + + + +#endif + +} + +namespace afp { + +finder_info::finder_info() { +#if defined(_WIN32) + afp_init(&_afp); +#else + memset(&_finder_info, 0, sizeof(_finder_info)); +#endif +} + +void finder_info::close() { +#if _WIN32 + if (_fd != INVALID_HANDLE_VALUE) CloseHandle(_fd); + _fd = INVALID_HANDLE_VALUE; +#else + if (_fd >= 0) close(_fd); + _fd = -1; +#endif + +} +finder_info::~finder_info() { + close(); +} + +bool finder_info::open(const std::string &name, open_mode perm, std::error_code &ec) { + + ec.clear(); + + close(); + + if (perm < 1 || perm > 3) { + ec = std::make_error_code(std::errc::invalid_argument); + return false; + } + + auto fd = fi_open(name, perm, ec); + if (ec) return false; + + // win32 should read even if write-only. + bool ok = read(_fd, ec); + + if (perm == read_only) { + fi_close(fd); + return ok; + } + + // write mode, so it's ok if it doesn't exist. + if (!ok) ec.clear(); + _fd = fd; + return true; +} +#if _WIN32 + +bool finder_info::open(const std::wstring &name, open_mode perm, std::error_code &ec) { + + ec.clear(); + + close(); + + if (perm < 1 || perm > 3) { + ec = std::make_error_code(std::errc::invalid_argument); + return false; + } + + auto fd = fi_open(name, perm, ec); + if (ec) return false; + + // win32 should read even if write-only. + bool ok = read(_fd, ec); + + if (perm == read_only) { + fi_close(fd); + return ok; + } + + // write mode, so it's ok if it doesn't exist. + if (!ok) ec.clear(); + _fd = fd; + return true; +} + +bool finder_info::read(void *fd, std::error_code &ec) { + + int ok = fi_read(fd, &_afp, sizeof(_afp), ec); + if (ec) { + afp_init(&_afp); + return false; + } + if (ok < sizeof(_afp) || !afp_verify(&_afp)) { + ec = std::make_error_code(std::errc::illegal_byte_sequence); // close enough! + afp_init(&_afp); + return false; + } + if (!_afp.prodos_file_type && !_afp.prodos_aux_type) + afp_synchronize(&_afp, trust_hfs); + + return true; +} + +bool finder_info::write(void *fd, std::error_code &ec) { + return fi_write(fd, &_afp, sizeof(_afp), ec) == sizeof(_afp); +} + +#else + +bool finder_info::read(int fd, std::error_code &ec) { + + int ok = fi_read(fd, &_finder_info, sizeof(_finder_info), ec); + if (ok < 0) { + memset(&_finder_info, 0, sizeof(_finder_info)); + return false; + } + finder_info_to_filetype(_finder_info, &_prodos_file_type, &_prodos_aux_type); + return true; +} + +bool finder_info::write(int fd, std::error_code &ec) { + return fi_write(fd, &_finder_info, sizeof(_finder_info), ec) == sizeof(_finder_info); +} + +#endif + +bool finder_info::write(std::error_code &ec) { + ec.clear(); + return write(_fd, ec); +} + + +bool finder_info::write(const std::string &name, std::error_code &ec) { + ec.clear(); + auto fd = fi_open(name, write_only, ec); + + if (ec) + return false; + + bool ok = write(fd, ec); + fi_close(fd); + return ok; +} + +#ifdef _WIN32 +bool finder_info::write(const std::wstring &name, std::error_code &ec) { + ec.clear(); + auto fd = fi_open(name, write_only, ec); + + if (ec) + return false; + + bool ok = write(fd, ec); + fi_close(fd); + return ok; +} + +#endif + +void finder_info::set_prodos_file_type(uint16_t ftype, uint32_t atype) { + _prodos_file_type = ftype; + _prodos_aux_type = atype; + file_type_to_finder_info(_finder_info, ftype, atype); +} + + +void finder_info::set_prodos_file_type(uint16_t ftype) { + set_prodos_file_type(ftype, _prodos_aux_type); +} + +bool finder_info::is_text() const { + if (memcmp(_finder_info, "TEXT", 4) == 0) return true; + if (_prodos_file_type == 0x04) return true; + if (_prodos_file_type == 0xb0) return true; + + return false; +} + +bool finder_info::is_binary() const { + if (is_text()) return false; + if (_prodos_file_type || _prodos_aux_type) return true; + + if (memcmp(_finder_info, "\x00\x00\x00\x00\x00\x00\x00\x00", 8) == 0) return false; + return true; +} + + + +uint32_t finder_info::file_type() const { + uint32_t rv = 0; + for (unsigned i = 0; i < 4; ++i) { + rv <<= 8; + rv |= _finder_info[i]; + } + return rv; +} + +uint32_t finder_info::creator_type() const { + uint32_t rv = 0; + for (unsigned i = 4; i < 8; ++i) { + rv <<= 8; + rv |= _finder_info[i]; + } + return rv; +} + +void finder_info::set_file_type(uint32_t x) { + _finder_info[0] = x >> 24; + _finder_info[1] = x >> 16; + _finder_info[2] = x >> 8; + _finder_info[3] = x >> 0; +} + + +void finder_info::set_creator_type(uint32_t x) { + _finder_info[4] = x >> 24; + _finder_info[5] = x >> 16; + _finder_info[6] = x >> 8; + _finder_info[7] = x >> 0; +} + +} diff --git a/src/resource_fork.cpp b/src/resource_fork.cpp new file mode 100644 index 0000000..6e0dadf --- /dev/null +++ b/src/resource_fork.cpp @@ -0,0 +1,535 @@ +#include "resource_fork.h" +#include "xattr.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define XATTR_RESOURCEFORK_NAME "AFP_ResourceFork" +#else +#include +#include +#include +#endif + +#ifdef __APPLE_ +#include +#include +#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 { + + void set_or_throw_error(std::error_code *ec, int error, const std::string &what) { + if (ec) *ec = std::error_code(error, std::system_category()); + else throw std::system_error(error, std::system_category(), what); + } + + +#ifdef _WIN32 + + /* + * allocating a new string could reset GetLastError() to 0. + */ + void set_or_throw_error(std::error_code *ec, const char *what) { + auto e = GetLastError(); + set_or_throw_error(ec, e, what); + } + + void set_or_throw_error(std::error_code *ec, const std::string &what) { + auto e = GetLastError(); + set_or_throw_error(ec, e, what); + } + + template + HANDLE CreateFileX(const std::string &s, Args... args) { + return CreateFileA(s.c_str(), std::forward(args)...); + } + + template + HANDLE CreateFileX(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 = CreateFileX(s, access, FILE_SHARE_READ, nullptr, create, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (h == INVALID_HANDLE_VALUE) { + set_or_throw_error(&ec, "CreateFile"); + } + return h; + } +#else + void set_or_throw_error(std::error_code *ec, const char *what) { + auto e = errno + set_or_throw_error(ec, e, what); + } + + void set_or_throw_error(std::error_code *ec, const std::string &what) { + auto e = errno + set_or_throw_error(ec, e, what); + } + +#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) { + 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(); + + std::string s(path); + s += ":" XATTR_RESOURCEFORK_NAME; + + _fd = CreateFileX(s, mode, ec); + if (ec) return false; + return true; + } + + bool resource_fork::open(const std::wstring &path, open_mode mode, std::error_code &ec) { + ec.clear(); + close(); + + std::wstring s(path); + s += L":" XATTR_RESOURCEFORK_NAME; + + _fd = CreateFileX(s, mode, ec); + if (ec) return false; + return true; + } + + + 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); + if (!ok) { + set_or_throw_error(&ec, "ReadFile"); + 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); + if (!ok) { + set_or_throw_error(&ec, "WriteFile"); + return 0; + } + return transferred; + } + + size_t resource_fork::size(std::error_code &ec) { + ec.clear(); + LARGE_INTEGER ll = { }; + BOOL ok = GetFileSizeEx(_fd, &ll); + if (!ok) { + set_or_throw_error(&ec, "GetFileSizeEx"); + return 0; + } + return 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); + if (!ok) { + set_or_throw_error(&ec, "SetFilePointerEx"); + return false; + } + + ok = SetEndOfFile(_fd); + if (!ok) { + set_or_throw_error(&ec, "SetEndOfFile"); + 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); + if (!ok) { + set_or_throw_error(&ec, "SetFilePointerEx"); + return false; + } + return true; + } +#else + 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; + } + _fd = attropen(path.c_str(), XATTR_RESOURCEFORK_NAME, umode, 0666); + if (_fd < 0) { + set_or_throw_error(ec, "attropen"); + return false; + } + return true; + } + +#endif + + +#ifdef __APPLE__ + #define FD_RESOURCE_FORK + bool open(const std::string &path, open_mode mode, std::error_code &ec) { + ec.clear(); + close(); + + std::string s(path); + s += _PATH_RSRCFORKSPEC; + + 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); + if (_fd < 0) { + set_or_throw_error(ec, "open"); + return false; + } + return true; + } + +#endif + +#ifdef FD_RESOURCE_FORK + size_t resource_fork::read(void *buffer, size_t n, std::error_code &) { + ec.clear(); + auto rv = read(_fd, buffer, n); + if (rv < 0) { + set_or_throw_error(ec, "read"); + return 0; + } + return rv; + } + + size_t resource_fork::write(const void *buffer, size_t n, std::error_code &) { + ec.clear(); + auto rv = write(_fd, buffer, n); + if (rv < 0) { + set_or_throw_error(ec, "write"); + return 0; + } + return rv; + } + + bool resource_fork::truncate(size_t pos, std::error_code &ec) { + ec.clear(); + if (ftruncate(_fd, pos) < 0) { + set_or_throw_error(ec, "ftruncate"); + return false; + } + return true; + } + + bool resource_fork::seek(size_t pos, std::error_code &ec) { + ec.clear(); + if (lseek(_fd, pos, SEEK_SET) < 0) { + set_or_throw_error(ec, "lseek"); + return false; + } + return true; + } + + size_t resource_fork::size(std::error_code &ec) { + ec.clear(); + struct stat st; + if (fstat(_fd, &st) < 0) { + set_or_throw_error(ec, "fstat"); + 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; + + ec.clear(); + + for(;;) { + ssize_t size = 0; + ssize_t tsize = 0; + + for(;;) { + rv.clear(); + size = size_xattr(_fd, XATTR_RESOURCEFORK_NAME); + if (size < 0) { + if (errno == EINTR) continue; + if (errno == ENOATTR) { + return rv; + } + set_or_throw_error(ec, "size_xattr"); + return rv; + } + break; + } + + if (size == 0) return rv; + rv.resize(size); + + for(;;) { + tsize = read_xattr(_fd, XATTR_RESOURCEFORK_NAME, tmp.data(), size); + if (tsize < 0) { + if (errno == EINTR) continue; + if (errno == ERANGE) break; + if (errno == ENOATTR) { + rv.clear(); + return rv; + } + set_or_throw_error(ec, "read_xattr"); + rv.clear(); + return rv; + } + rv.resize(tsize); + return rv; + } + } + } + } + bool resource_fork::open(const std::string &s, open_mode mode, std::error_code &ec) { + close(); + ec.clear(); + _fd = open(s.c_str(), O_RDONLY); + if (_fd < 0) { + set_or_throw_error(ec, "open"); + 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); + if (rv < 0) { + set_or_throw_error(ec, "size_xattr"); + return 0; + } + return rv; + } + + size_t resource_fork::read(void *buffer, size_t n, std::error_code &ec) { + ec.clear(); + if (_fd < 0) { + set_or_throw_error(ec, EBADF, "read"); + return 0; + } + if (_mode == write_only) { + set_or_throw_error(ec, EPERM, "read"); + return 0; + } + + if (n == 0) return 0; + + auto tmp = read_rfork(_fd, ec); + if (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) { + set_or_throw_error(ec, EBADF, "write"); + return 0; + } + if (_mode == read_only) { + set_or_throw_error(ec, EPERM, "write"); + return 0; + } + + if (n == 0) return 0; + + auto tmp = read_rfork(_fd, ec); + if (ec) return ec; + + if (_offset > tmp.size()) { + tmp.resize(_offset); + } + tmp.append((const uint8_t *)buffer, (const uint8_t *buffer) + n); + + auto rv = write_xattr(_fd, XATTR_RESOURCEFORK_NAME, tmp.data(), tmp.size()); + if (rv < 0) { + set_or_throw_error(ec, "write_xattr"); + return 0; + } + return n; + } + + bool resource_fork::truncate(size_t pos, std::error_code &ec) { + + ec.clear(); + if (_fd < 0) { + set_or_throw_error(ec, EBADF, "resource_fork::truncate"); + return 0; + } + if (_mode == read_only) { + set_or_throw_error(ec, EPERM, "resource_fork::truncate"); + return 0; + } + + // simple case.. + if (pos == 0) { + auto rv = remove_xattr(_fd, XATTR_RESOURCEFORK_NAME); + if (rv < 0) { + set_or_throw_error(ec, "remove_xattr"); + return false; + } + return true; + } + auto tmp = read_rfork(_fd, ec); + if (ec) return false; + if (tmp.size() == pos) return true; + tmp.resize(pos); + auto rv = write_xattr(_fd, XATTR_RESOURCEFORK_NAME, tmp.data(), pos); + if (rv < 0) { + set_or_throw_error(ec, "write_xattr"); + return false; + } + _offset = pos; + return true; + } + bool resource_fork::seek(size_t pos, std::error_code &ec) { + ec.clear(); + if (_fd < 0) { + set_or_throw_error(ec, EBADF, "truncate"); + return 0; + } + + _offset = pos; + return true; + } + + +#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 + +} diff --git a/src/xattr.c b/src/xattr.c new file mode 100644 index 0000000..078ac88 --- /dev/null +++ b/src/xattr.c @@ -0,0 +1,101 @@ + +#include "xattr.h" + +#if defined(__APPLE__) +#include +#endif + +#if defined(__linux__) +#include +#endif + +#if defined(__FreeBSD__) +#include +#include +#endif + +#if defined(_AIX) +#include +#endif + + +/* + * extended attributes functions. + */ +#if defined(__APPLE__) +ssize_t size_xattr(int fd, const char *xattr) { + return fgetxattr(fd, xattr, NULL, 0, 0, 0); +} + +ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetxattr(fd, xattr, buffer, size, 0, 0); +} + +ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetxattr(fd, xattr, buffer, size, 0, 0) < 0) return -1; + return size; +} + +int remove_xattr(int fd, const char *xattr) { + return fremovexattr(fd, xattr, 0); +} + +#elif defined(__linux__) +ssize_t size_xattr(int fd, const char *xattr) { + return fgetxattr(fd, xattr, NULL, 0); +} + +ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetxattr(fd, xattr, buffer, size); +} + +ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetxattr(fd, xattr, buffer, size, 0) < 0) return -1; + return size; +} + +int remove_xattr(int fd, const char *xattr) { + return fremovexattr(fd, xattr); +} + +#elif defined(__FreeBSD__) +ssize_t size_xattr(int fd, const char *xattr) { + return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, xattr, NULL, 0); +} + +ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, xattr, buffer, size); +} + +ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + return extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, xattr, buffer, size); +} + +int remove_xattr(int fd, const char *xattr) { + return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, xattr); +} + +#elif defined(_AIX) +ssize_t size_xattr(int fd, const char *xattr) { + /* + struct stat64x st; + if (fstatea(fd, xattr, &st) < 0) return -1; + return st.st_size; + */ + return fgetea(fd, xattr, NULL, 0); +} + +ssize_t read_xattr(int fd, const char *xattr, void *buffer, size_t size) { + return fgetea(fd, xattr, buffer, size); +} + +ssize_t write_xattr(int fd, const char *xattr, const void *buffer, size_t size) { + if (fsetea(fd, xattr, buffer, size, 0) < 0) return -1; + return size; +} + +int remove_xattr(int fd, const char *xattr) { + return fremoveea(fd, xattr); +} + +#endif