commit 7f24d8f588334ffe7efb593c51d6ae2f42c5d913 Author: Kelvin Sherlock Date: Wed Aug 3 13:09:58 2016 -0400 initial version. diff --git a/applefile.h b/applefile.h new file mode 100644 index 0000000..e5ffc3e --- /dev/null +++ b/applefile.h @@ -0,0 +1,360 @@ +#ifndef __applefile_h__ +#define __applefile_h__ + +#include + +/* applefile.h - Data structures used by AppleSingle/AppleDouble + * file format + * + * Written by Lee Jones, 22-Oct-1993 + * + * For definitive information, see "AppleSingle/AppleDouble + * Formats for Foreign Files Developer's Note"; Apple Computer + * Inc.; (c) 1990. + * + * Other details were added from: + * Inside Macintosh [old version], volumes II to VI, + * Apple include files supplied with Think C 5.0.1, + * Microsoft MS-DOS Programmer's Reference, version 5, and + * Microsoft C 6.00a's dos.h include file. + * + * I don't have ProDOS or AFP Server documentation so related + * entries may be a bit skimpy. + * + * Edit history: + * + * when who why + * --------- --- ------------------------------------------ + * 22-Oct-93 LMJ Pull together from Inside Macintosh, + * Developer's Note, etc + * 26-Oct-93 LMJ Finish writing first version and list + * references + * 06-Feb-94 EEF Very minor cleanup + */ + +#pragma pack(push, 2) + + +/* REMINDER: the Motorola 680x0 is a big-endian architecture! */ + +typedef uint32_t OSType; /* 32 bit field */ + +/* In the QuickDraw coordinate plane, each coordinate is + * -32767..32767. Each point is at the intersection of a + * horizontal grid line and a vertical grid line. Horizontal + * coordinates increase from left to right. Vertical + * coordinates increase from top to bottom. This is the way + * both a TV screen and page of English text are scanned: + * from top left to bottom right. + */ + +struct Point /* spot in QuickDraw 2-D grid */ +{ + uint16_t v; /* vertical coordinate */ + uint16_t h; /* horizontal coordinate */ +}; /* Point */ + +typedef struct Point Point; + +/* See older Inside Macintosh, Volume II page 84 or Volume IV + * page 104. + */ + +struct FInfo /* Finder information */ +{ + OSType fdType; /* File type, 4 ASCII chars */ + OSType fdCreator; /* File's creator, 4 ASCII chars */ + uint16_t fdFlags; /* Finder flag bits */ + Point fdLocation; /* file's location in folder */ + uint16_t fdFldr; /* file 's folder (aka window) */ +}; /* FInfo */ + +typedef struct FInfo FInfo; + +/* + * Masks for finder flag bits (field fdFlags in struct + * FInfo). + */ + +#define F_fOnDesk 0x0001 /* file is on desktop (HFS only) */ +#define F_maskColor 0x000E /* color coding (3 bits) */ +/* 0x0010 reserved (System 7) */ +#define F_fSwitchLaunch 0x0020 /* reserved (System 7) */ +#define F_fShared 0x0040 /* appl available to multiple users */ +#define F_fNoINITs 0x0080 /* file contains no INIT resources */ +#define F_fBeenInited 0x0100 /* Finder has loaded bundle res. */ +/* 0x0200 reserved (System 7) */ +#define F_fCustomIcom 0x0400 /* file contains custom icon */ +#define F_fStationary 0x0800 /* file is a stationary pad */ +#define F_fNameLocked 0x1000 /* file can't be renamed by Finder */ +#define F_fHasBundle 0x2000 /* file has a bundle */ +#define F_fInvisible 0x4000 /* file's icon is invisible */ +#define F_fAlias 0x8000 /* file is an alias file (System 7) */ + +/* See older Inside Macintosh, Volume IV, page 105. + */ + +struct FXInfo /* Extended finder information */ + +{ + uint16_t fdIconID; /* icon ID number */ + uint16_t fdUnused[3]; /* spare */ + int8_t fdScript; /* scrip flag and code */ + int8_t fdXFlags; /* reserved */ + uint16_t fdComment; /* comment ID number */ + uint32_t fdPutAway; /* home directory ID */ +}; /* FXInfo */ + +typedef struct FXInfo FXInfo; + +/* Pieces used by AppleSingle & AppleDouble (defined later). */ + +#define APPLESINGLE_MAGIC 0x00051600 +#define APPLEDOUBLE_MAGIC 0x00051607 +#define APPLESINGLE_CIGAM 0x00160500 +#define APPLEDOUBLE_CIGAM 0x07160500 + +struct ASHeader /* header portion of AppleSingle */ +{ + /* AppleSingle = 0x00051600; AppleDouble = 0x00051607 */ + uint32_t magicNum; /* internal file type tag */ + uint32_t versionNum; /* format version: 2 = 0x00020000 */ + uint8_t filler[16]; /* filler, currently all bits 0 */ + uint16_t numEntries; /* number of entries which follow */ +}; /* ASHeader */ + +typedef struct ASHeader ASHeader; + +struct ASEntry /* one AppleSingle entry descriptor */ +{ + uint32_t entryID; /* entry type: see list, 0 invalid */ + uint32_t entryOffset; /* offset, in octets, from beginning */ + /* of file to this entry's data */ + uint32_t entryLength; /* length of data in octets */ +}; /* ASEntry */ + +typedef struct ASEntry ASEntry; + +/* Apple reserves the range of entry IDs from 1 to 0x7FFFFFFF. + * Entry ID 0 is invalid. The rest of the range is available + * for applications to define their own entry types. "Apple does + * not arbitrate the use of the rest of the range." + */ + +#define AS_DATA 1 /* data fork */ +#define AS_RESOURCE 2 /* resource fork */ +#define AS_REALNAME 3 /* File's name on home file system */ +#define AS_COMMENT 4 /* standard Mac comment */ +#define AS_ICONBW 5 /* Mac black & white icon */ +#define AS_ICONCOLOR 6 /* Mac color icon */ +/* 7 not used */ +#define AS_FILEDATES 8 /* file dates; create, modify, etc */ +#define AS_FINDERINFO 9 /* Mac Finder info & extended info */ +#define AS_MACINFO 10 /* Mac file info, attributes, etc */ +#define AS_PRODOSINFO 11 /* Pro-DOS file info, attrib., etc */ +#define AS_MSDOSINFO 12 /* MS-DOS file info, attributes, etc */ +#define AS_AFPNAME 13 /* Short name on AFP server */ +#define AS_AFPINFO 14 /* AFP file info, attrib., etc */ + +#define AS_AFPDIRID 15 /* AFP directory ID */ + +/* matrix of entry types and their usage: + * + * Macintosh Pro-DOS MS-DOS AFP server + * --------- ------- ------ ---------- + * 1 AS_DATA xxx xxx xxx xxx + * 2 AS_RESOURCE xxx xxx + * 3 AS_REALNAME xxx xxx xxx xxx + * + * 4 AS_COMMENT xxx + * 5 AS_ICONBW xxx + * 6 AS_ICONCOLOR xxx + * + * 8 AS_FILEDATES xxx xxx xxx xxx + * 9 AS_FINDERINFO xxx + * 10 AS_MACINFO xxx + * + * 11 AS_PRODOSINFO xxx + * 12 AS_MSDOSINFO xxx + * + * 13 AS_AFPNAME xxx + * 14 AS_AFPINFO xxx + * 15 AS_AFPDIRID xxx + */ + +/* entry ID 1, data fork of file - arbitrary length octet string */ + +/* entry ID 2, resource fork - arbitrary length opaque octet string; + * as created and managed by Mac O.S. resoure manager + */ + +/* entry ID 3, file's name as created on home file system - arbitrary + * length octet string; usually short, printable ASCII + */ + +/* entry ID 4, standard Macintosh comment - arbitrary length octet + * string; printable ASCII, claimed 200 chars or less + */ + +/* This is probably a simple duplicate of the 128 octet bitmap + * stored as the 'ICON' resource or the icon element from an 'ICN#' + * resource. + */ + +/* entry ID 5, standard Mac black and white icon */ +struct ASIconBW +{ + uint32_t bitrow[32]; /* 32 rows of 32 1-bit pixels */ +}; /* ASIconBW */ + +typedef struct ASIconBW ASIconBW; + +/* entry ID 6, "standard" Macintosh color icon - several competing + * color icons are defined. Given the copyright dates + * of the Inside Macintosh volumes, the 'cicn' resource predominated + * when the AppleSingle Developer's Note was written (most probable + * candidate). See Inside Macintosh, Volume V, pages 64 & 80-81 for + * a description of 'cicn' resources. + * + * With System 7, Apple introduced icon families. They consist of: + * large (32x32) B&W icon, 1-bit/pixel, type 'ICN#', + * small (16x16) B&W icon, 1-bit/pixel, type 'ics#', + * large (32x32) color icon, 4-bits/pixel, type 'icl4', + * small (16x16) color icon, 4-bits/pixel, type 'ics4', + * large (32x32) color icon, 8-bits/pixel, type 'icl8', and + * small (16x16) color icon, 8-bits/pixel, type 'ics8'. + * If entry ID 6 is one of these, take your pick. See Inside + * Macintosh, Volume VI, pages 2-18 to 2-22 and 9-9 to 9-13, for + * descriptions. + */ + +/* entry ID 7, not used */ + +/* Times are stored as a "signed number of seconds before of after + * 12:00 a.m. (midnight), January 1, 2000 Greenwich Mean Time (GMT). + * Applications must convert to their native date and time + * conventions." Any unknown entries are set to 0x80000000 + * (earliest reasonable time). + */ + +/* entry ID 8, file dates info */ +struct ASFileDates +{ + uint32_t create; /* file creation date/time */ + uint32_t modify; /* last modification date/time */ + uint32_t backup; /* last backup date/time */ + uint32_t access; /* last access date/time */ +}; /* ASFileDates */ + +typedef struct ASFileDates ASFileDates; + +/* See older Inside Macintosh, Volume II, page 115 for + * PBGetFileInfo(), and Volume IV, page 155, for PBGetCatInfo(). + */ + +/* entry ID 9, Macintosh Finder info & extended info */ +struct ASFinderInfo +{ + FInfo ioFlFndrInfo; /* PBGetFileInfo() or PBGetCatInfo() */ + FXInfo ioFlXFndrInfo; /* PBGetCatInfo() (HFS only) */ +}; /* ASFinderInfo */ + +typedef struct ASFinderInfo ASFinderInfo; + +/* entry ID 10, Macintosh file information */ +struct ASMacInfo +{ + uint8_t filler[3]; /* filler, currently all bits 0 */ + uint8_t ioFlAttrib; /* PBGetFileInfo() or PBGetCatInfo() */ +}; /* ASMacInfo */ + +typedef struct ASMacInfo ASMacInfo; + +#define AS_PROTECTED 0x0002 /* protected bit */ +#define AS_LOCKED 0x0001 /* locked bit */ + +/* NOTE: ProDOS-16 and GS/OS use entire fields. ProDOS-8 uses low + * order half of each item (low byte in access & filetype, low word + * in auxtype); remainder of each field should be zero filled. + */ + +/* entry ID 11, ProDOS file information */ +struct ASProdosInfo +{ + uint16_t access; /* access word */ + uint16_t filetype; /* file type of original file */ + uint32_t auxtype; /* auxiliary type of the orig file */ +}; /* ASProDosInfo */ + +typedef struct ASProdosInfo ASProdosInfo; + +/* MS-DOS file attributes occupy 1 octet; since the Developer Note + * is unspecific, I've placed them in the low order portion of the + * field (based on example of other ASMacInfo & ASProdosInfo). + */ + +/* entry ID 12, MS-DOS file information */ +struct ASMsdosInfo +{ + uint8_t filler; /* filler, currently all bits 0 */ + uint8_t attr; /* _dos_getfileattr(), MS-DOS */ + /* interrupt 21h function 4300h */ +}; /* ASMsdosInfo */ + +typedef struct ASMsdosInfo ASMsdosInfo; + +#define AS_DOS_NORMAL 0x00 /* normal file (all bits clear) */ +#define AS_DOS_READONLY 0x01 /* file is read-only */ +#define AS_DOS_HIDDEN 0x02 /* hidden file (not shown by DIR) */ +#define AS_DOS_SYSTEM 0x04 /* system file (not shown by DIR) */ +#define AS_DOS_VOLID 0x08 /* volume label (only in root dir) */ +#define AS_DOS_SUBDIR 0x10 /* file is a subdirectory */ +#define AS_DOS_ARCHIVE 0x20 /* new or modified (needs backup) */ + +/* entry ID 13, short file name on AFP server - arbitrary length + * octet string; usualy printable ASCII starting with + * '!' (0x21) + */ + +/* entry ID 12, AFP server file information */ +struct ASAfpInfo +{ + uint8_t filler[3]; /* filler, currently all bits 0 */ + uint8_t attr; /* file attributes */ +}; /* ASAfpInfo */ + +typedef struct ASAfpInfo ASAfpInfo; + +#define AS_AFP_Invisible 0x01 /* file is invisible */ +#define AS_AFP_MultiUser 0x02 /* simultaneous access allowed */ +#define AS_AFP_System 0x04 /* system file */ +#define AS_AFP_BackupNeeded 0x40 /* new or modified (needs backup) */ + +/* entry ID 15, AFP server directory ID */ +struct ASAfpDirId +{ + uint32_t dirid; /* file's directory ID on AFP server */ +}; /* ASAfpDirId */ + +typedef struct ASAfpDirId ASAfpDirId; + +/* + * The format of an AppleSingle/AppleDouble header + */ +struct AppleSingle /* format of disk file */ +{ + ASHeader header; /* AppleSingle header part */ + ASEntry entry[1]; /* array of entry descriptors */ +/* uint8_t filedata[]; followed by rest of file */ +}; /* AppleSingle */ + +typedef struct AppleSingle AppleSingle; + +/* + * FINAL REMINDER: the Motorola 680x0 is a big-endian architecture! + */ + +#pragma pack(pop) + +/* End of applefile.h */ +#endif diff --git a/mapped_file.cpp b/mapped_file.cpp new file mode 100644 index 0000000..3d82233 --- /dev/null +++ b/mapped_file.cpp @@ -0,0 +1,369 @@ +#include "mapped_file.h" +#include +#include +#include + +#include "unique_resource.h" + +namespace { + class defer { + public: + typedef std::function FX; + defer() = default; + + defer(FX fx) : _fx(fx) {} + defer(const defer &) = delete; + defer(defer &&) = default; + defer & operator=(const defer &) = delete; + defer & operator=(defer &&) = default; + + void cancel() { _fx = nullptr; } + ~defer() { if (_fx) _fx(); } + private: + FX _fx; + }; + + void throw_error(int error) { + throw std::system_error(error, std::system_category()); + } + + + void throw_error(int error, const std::string &what) { + throw std::system_error(error, std::system_category(), what); + } + + + +} + +#ifdef _WIN32 +#include + +namespace { + + void throw_error() { + throw_error(GetLastError()); + } + + void throw_error(const std::string &what) { + throw_error(GetLastError(), what); + } + +} + +void mapped_file_base::close() { + if (is_open()) { + + UnmapViewOfFile(_data); + CloseHandle(_map_handle); + CloseHandle(_file_handle); + reset(); + } +} + +void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset) { + + + HANDLE fh; + HANDLE mh; + LARGE_INTEGER file_size; + + // length of 0 in CreateFileMapping / MapViewOfFile + // means map the entire file. + + if (is_open()) { + throw std::runtime_error("mapped_file_base::open - file already open"); + } + + fh = CreateFile(p.c_str(), + flags == readonly ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + flags == readonly ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL, + nullptr + ); + if (fh == INVALID_HANDLE_VALUE) { + throw_error(); + } + + auto fh_close = make_unique_resource(fh, CloseHandle); + + GetFileSizeEx(fh, &file_size); + + if (length == -1) + length = file_size.QuadPart; + + DWORD protect = 0; + DWORD access = 0; + switch (flags) { + case readonly: + protect = PAGE_READONLY; + access = FILE_MAP_READ; + break; + case readwrite: + protect = PAGE_READWRITE; + access = FILE_MAP_WRITE; + break; + case priv: + protect = PAGE_WRITECOPY; + access = FILE_MAP_WRITE; + break; + } + + mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0); + if (mh == INVALID_HANDLE_VALUE) { + throw_error(); + } + + auto mh_close = make_unique_resource(mh, CloseHandle); + + + _data = MapViewOfFileEx(mh, + access, + (DWORD)(offset >> 32), + (DWORD)offset, + length, + nullptr); + if (!_data) { + throw_error(); + } + + + _file_handle = fh_close.release(); + _map_handle = mh_close.release(); + _size = length; + _flags = flags; +} + + +void mapped_file_base::create(const path_type& p, size_t length) { + + const size_t offset = 0; + + HANDLE fh; + HANDLE mh; + LARGE_INTEGER file_size; + + const DWORD protect = PAGE_READWRITE; + const DWORD access = FILE_MAP_WRITE; + + + if (is_open()) { + throw std::runtime_error("mapped_file_base::create - file already open"); + } + + + fh = CreateFile(p.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + if (fh == INVALID_HANDLE_VALUE) { + throw_error(); + } + + auto fh_close = make_unique_resource(fh, CloseHandle); + + + file_size.QuadPart = length; + if (!SetFilePointerEx(fh, file_size, nullptr, FILE_BEGIN)); + if (!SetEndOfFile(fh)) throw_error(); + + mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0); + if (mh == INVALID_HANDLE_VALUE) { + throw_error(); + } + + auto mh_close = make_unique_resource(mh, CloseHandle); + + _data = MapViewOfFileEx(mh, + access, + (DWORD)(offset >> 32), + (DWORD)offset, + length, + nullptr); + + if (!_data) { + throw_error(); + } + + _file_handle = fh_close.release(); + _map_handle = mh_close.release(); + + _size = length; + _flags = readwrite; +} + + + +#else + +#include +#include +#include +#include +#include + +namespace { + + void throw_error() { + throw_error(errno); + } + + void throw_error(const std::string &what) { + throw_error(errno, what); + } + +} + +void mapped_file_base::close() { + if (is_open()) { + ::munmap(_data, _size); + ::close(_fd); + reset(); + } +} + + +void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset) { + + int fd; + struct stat st; + + int oflags = 0; + + if (is_open()) { + throw std::runtime_error("mapped_file_base::open - file already open"); + } + + switch (flags) { + case readonly: + oflags = O_RDONLY; + break; + default: + oflags = O_RDWR; + break; + } + + fd = ::open(p.c_str(), oflags); + if (fd < 0) { + throw_error(errno); + } + + //defer([fd](){::close(fd); }); + auto close_fd = make_unique_resource(fd, ::close); + + if (::fstat(fd, &st) < 0) { + throw_error(errno); + } + + + if (length == -1) length = st.st_size; + + _data = ::mmap(0, length, + flags == readonly ? PROT_READ : PROT_READ | PROT_WRITE, + flags == priv ? MAP_PRIVATE : MAP_SHARED, + fd, offset); + + if (_data == MAP_FAILED) { + _data = nullptr; + throw_error(errno); + } + + _fd = close_fd.release(); + _size = length; + _flags = flags; +} + +void mapped_file_base::create(const path_type& p, size_t length) { + + int fd; + const size_t offset = 0; + + if (is_open()) { + throw std::runtime_error("mapped_file_base::create - file already open"); + } + + + + fd = ::open(p.c_str(), O_RDWR | O_CREAT | O_TRUNC); + if (fd < 0) { + throw_error(errno); + } + + //defer([fd](){::close(fd); }); + + auto close_fd = make_unique_resource(fd, ::close); + + + if (::ftruncate(fd, length) < 0) { + throw_error(errno); + } + + + _data = ::mmap(0, length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, offset); + + if (_data == MAP_FAILED) { + _data = nullptr; + throw_error(errno); + } + + _fd = close_fd.release(); + _size = length; + _flags = readwrite; +} + + + +#endif + + +void mapped_file_base::reset() { + _data = nullptr; + _size = 0; + _flags = readonly; +#ifdef _WIN32 + _file_handle = nullptr; + _map_handle = nullptr; +#else + _fd = -1; +#endif +} + +void mapped_file_base::swap(mapped_file_base &rhs) +{ + if (std::addressof(rhs) != this) { + std::swap(_data, rhs._data); + std::swap(_size, rhs._size); + std::swap(_flags, rhs._flags); +#ifdef _WIN32 + std::swap(_file_handle, rhs._file_handle); + std::swap(_map_handle, rhs._map_handle); +#else + std::swap(_fd, rhs._fd); +#endif + } +} + +mapped_file::mapped_file(mapped_file &&rhs) : mapped_file() { + swap(rhs); + //rhs.reset(); +} + +mapped_file& mapped_file::operator=(mapped_file &&rhs) { + if (std::addressof(rhs) == this) return *this; + + swap(rhs); + rhs.close(); + //rhs.reset(); + return *this; +} + + diff --git a/mapped_file.h b/mapped_file.h new file mode 100644 index 0000000..9d4452f --- /dev/null +++ b/mapped_file.h @@ -0,0 +1,146 @@ +#ifndef __mapped_file_h__ +#define __mapped_file_h__ + +#ifdef HAVE_TSFS +#include +#else +#include +#endif + +#include + + +class mapped_file_base { +public: + +#ifdef HAVE_TSFS + typedef std::filesystem::path path_type ; +#else + typedef std::string path_type ; +#endif + + enum mapmode { readonly, readwrite, priv }; + + void close(); + + bool is_open() const { + return _data != nullptr; + } + + size_t size() const { + return _size; + } + + operator bool() const { return is_open(); } + bool operator !() const { return !is_open(); } + + ~mapped_file_base() { close(); } + +protected: + + void swap(mapped_file_base &rhs); + + void open(const path_type& p, mapmode flags, size_t length, size_t offset); + void create(const path_type &p, size_t new_size); // always creates readwrite. + void reset(); + + + size_t _size = 0; + void *_data = nullptr; + mapmode _flags = readonly; + +#ifdef _WIN32 + void *_file_handle = nullptr; + void *_map_handle = nullptr; +#else + int _fd = -1; +#endif +}; + + + +class mapped_file : public mapped_file_base { + + typedef mapped_file_base base; + +public: + + typedef unsigned char value_type; + + typedef value_type *iterator; + typedef const value_type *const_iterator; + + typedef value_type &reference ; + typedef const value_type &const_reference; + + + mapped_file() = default; + mapped_file(const path_type& p, mapmode flags = readonly, size_t length = -1, size_t offset = 0) { + open(p, flags, length, offset); + } + + mapped_file(mapped_file &&); + mapped_file(const mapped_file &) = delete; + + mapped_file &operator=(mapped_file &&); + mapped_file &operator=(const mapped_file &) = delete; + + + void open(const path_type& p, mapmode flags, size_t length = -1, size_t offset = 0) { + base::open(p, flags, length, offset); + } + + + const value_type *data() const { + return (const value_type *)_data; + } + + value_type *data() { + return _flags == readonly ? (value_type *)nullptr : (value_type *)_data; + } + + const_iterator cbegin() const { + return data(); + } + + const_iterator cend() const { + return data() + size(); + } + + const_iterator begin() const { + return cbegin(); + } + + const_iterator end() const { + return cend(); + } + + + + + iterator begin() { + return _flags == readonly ? (iterator)nullptr : (iterator)_data; + } + + iterator end() { + return _flags == readonly ? (iterator)nullptr : (iterator)_data + size(); + } + + mapmode flags() const { + return _flags; + } + + void swap(mapped_file &rhs) { + base::swap(rhs); + } + +}; + +namespace std { + template + void swap(mapped_file &a, mapped_file &b) { + a.swap(b); + } +} + +#endif diff --git a/unfork.cpp b/unfork.cpp new file mode 100644 index 0000000..b733331 --- /dev/null +++ b/unfork.cpp @@ -0,0 +1,192 @@ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "applefile.h" +#include "mapped_file.h" + + +void damaged_file() { + throw std::runtime_error("File is damaged."); +} + +void throw_errno() { + throw std::system_error(errno, std::system_category()); +} + + +void throw_errno(const std::string &what) { + throw std::system_error(errno, std::system_category(), what); +} + + +class defer { +public: + typedef std::function FX; + defer() = default; + + defer(FX fx) : _fx(fx) {} + defer(const defer &) = delete; + defer(defer &&) = default; + defer & operator=(const defer &) = delete; + defer & operator=(defer &&) = default; + + void cancel() { _fx = nullptr; } + ~defer() { if (_fx) _fx(); } +private: + FX _fx; +}; + +void unfork(const char *in, const char *out) { + + + static_assert(sizeof(ASHeader) == 26, "ASHeader size is wrong."); + static_assert(sizeof(ASEntry) == 12, "ASEntry size is wrong."); + + + mapped_file mf(in, mapped_file::priv); + + if (mf.size() < sizeof(ASHeader)) damaged_file(); + + ASHeader *header = (ASHeader *)mf.data(); + header->magicNum = ntohl(header->magicNum); + header->versionNum = ntohl(header->versionNum); + header->numEntries = ntohs(header->numEntries); + + + + if (header->magicNum != 0x00051600 || header->versionNum != 0x00020000 || header->numEntries == 0) + throw std::runtime_error("Not an AppleSingle File"); + + if (header->numEntries * sizeof(ASEntry) + sizeof(ASHeader) > mf.size()) damaged_file(); + + ASEntry *begin = (ASEntry *)(mf.data() + sizeof(ASHeader)); + ASEntry *end = &begin[header->numEntries]; + + std::for_each(begin, end, [](ASEntry &e){ + e.entryID = ntohl(e.entryID); + e.entryOffset = ntohl(e.entryOffset); + e.entryLength = ntohl(e.entryLength); + }); + + + // check for truncation.... + + for (auto iter = begin; iter != end; ++iter) { + const auto &e = *iter; + if (!e.entryLength) continue; + if (e.entryOffset > mf.size()) damaged_file(); + if (e.entryOffset + e.entryLength > mf.size()) damaged_file(); + } + + std::string outname = out ? out : ""; + // if no name, pull it from the name record. + if (!out) { + auto iter = std::find_if(begin, end, [](const ASEntry &e) { return e.entryID == AS_REALNAME; }); + if (iter != end) { + outname.assign((const char *)mf.data() + iter->entryOffset, iter->entryLength); + } + } + + if (outname.empty()) throw std::runtime_error("No filename"); + + int fd = open(outname.c_str(), O_CREAT | O_TRUNC | O_WRONLY); + if (fd < 1) throw_errno(); + defer close_fd([fd](){ close(fd); }); + + for (auto iter = begin; iter != end; ++iter) { + const auto &e = *iter; + + if (e.entryLength == 0) continue; + switch(e.entryID) { + + case AS_DATA: { + ssize_t ok = write(fd, mf.data() + e.entryOffset, e.entryLength); + if (ok < 0) throw_errno(); + //if (ok != e.entryLength) return -1; + break; + } + case AS_RESOURCE: { + int fd = fopenattr(fd, "com.apple.ResourceFork", O_CREAT | O_TRUNC | O_WRONLY); + if (fd < 0) throw_errno("com.apple.ResourceFork"); + defer close_fd([fd](){ close(fd); }); + + ssize_t ok = write(fd, mf.data() + e.entryOffset, e.entryLength); + if (ok < 0) throw_errno("com.apple.ResourceFork"); + //if (ok != e.entryLength) return -1; + break; + } + case AS_FINDERINFO: { + if (e.entryLength != 32) { + fputs("Warning: Invalid Finder Info size.\n", stderr); + break; + } + int fd = fopenattr(fd, "com.apple.FinderInfo", O_CREAT | O_TRUNC | O_WRONLY); + if (fd < 0) throw_errno("com.apple.ResourceFork"); + defer close_fd([fd](){ close(fd); }); + + ssize_t ok = write(fd, mf.data() + e.entryOffset, e.entryLength); + if (ok < 0) throw_errno("com.apple.FinderInfo"); + //if (ok != e.entryLength) return -1; + break; + } + } + + } + + close(fd); +} + +void usage() { + fputs("Usage: applesingle [-o file] file...\n", stderr); + exit(EX_USAGE); +} +int main(int argc, char **argv) { + + + int c; + char *_o; + + while ((c = getopt(argc, argv, "o:")) != -1) { + + switch(c) { + case 'o': + _o = optarg; + break; + case ':': + case '?': + default: + usage(); + break; + } + + } + + argv += optind; + argc -= optind; + + int rv = 0; + for (int i = 0; i < argc; ++i) { + + try { unfork(argv[i], _o); } + catch (std::exception &ex) { + fprintf(stderr, "%s : %s\n", argv[i], ex.what()); + rv = 1; + } + _o = nullptr; + } + + return rv; +} \ No newline at end of file