diff --git a/Bitmap.cpp b/Bitmap.cpp new file mode 100644 index 0000000..3605d9e --- /dev/null +++ b/Bitmap.cpp @@ -0,0 +1,197 @@ +#include "Bitmap.h" +#include + +#include "auto.h" + + +// returns # of 1-bits set (0-8) +inline static unsigned popCount(uint8_t x) +{ + #ifdef __GNUC__ + return __builtin_popcount(x); + #endif + + // Brian Kernighan / Peter Wegner in CACM 3 (1960), 322. + + unsigned count; + for (count = 0; x; ++count) + { + x &= x - 1; + } + return count; +} + + + +Bitmap::Bitmap(unsigned blocks) +{ + _blocks = _freeBlocks = blocks; + + _bitmapBlocks = (blocks + 4095) / 4096; + _freeIndex = 0; + + unsigned bitmapSize = _bitmapBlocks * 512; + unsigned blockSize = blocks / 8; + + auto_array bitmap(new uint8_t[bitmapSize]); + + // mark overflow in use, everything else free. + + std::memset(bitmap, 0xff, blocks / 8); + std::memset(bitmap + blockSize, 0x00, bitmapSize - blockSize); + + // edge case + unsigned tmp = blocks & 0x07; + + bitmap[blocks / 8] = ~(0xff >> tmp); + + _bitmap = bitmap.release(); + + +} + +Bitmap::Bitmap(BlockDevice *device, unsigned keyPointer, unsigned blocks) +{ + _blocks = blocks; + _freeBlocks = 0; + _freeIndex = 0; + + _bitmapBlocks = (blocks + 4095) / 4096; + + unsigned bitmapSize = _bitmapBlocks * 512; + unsigned blockSize = blocks / 8; + + auto_array bitmap(new uint8_t[bitmapSize]); + + for (unsigned i = 0; i < blockSize; ++i) + { + device->readBlock(keyPointer + i, bitmap + 512 * i); + } + + // make sure all trailing bits are marked in use. + + // edge case + unsigned tmp = blocks & 0x07; + + bitmap[blocks / 8] &= ~(0xff >> tmp); + + std::memset(bitmap + blockSize, 0x00, bitmapSize - blockSize); + + // set _freeBlocks and _freeIndex; + for (unsigned i = 0; i < (blocks + 7) / 8; ++i) + { + _freeBlocks += popCount(bitmap[i]); + } + + if (_freeblocks) + { + for (unsigned i = 0; i < (blocks + 7) / 8; ++i) + { + if (tmp[i]) + { + _freeIndex = i; + break; + } + } + } + + + _bitmap = bitmap.release(); +} + +Bitmap::~Bitmap() +{ + if (_bitmap) delete []_bitmap; +} + + +void Bitmap::freeBlock(unsigned block) +{ + if (block >= _blocks) return; + + unsigned index = block / 8; + unsigned offset = block & 0x07; + unsigned mask = 0x80 >> offset; + + uint8_t tmp = _bitmap[index]; + + if ((tmp & mask) == 0) + { + ++_freeBlocks; + _bitmap[index] = tmp | mask; + } +} + + +int Bitmap::allocBlock(unsigned block) +{ + if (block >= _blocks) return -1; + + + unsigned index = block / 8; + unsigned offset = block & 0x07; + unsigned mask = 0x80 >> offset; + + uint8_t tmp = _bitmap[index]; + + if ((tmp & mask)) + { + --_freeBlocks; + _bitmap[index] = tmp & ~mask; + return block; + } + + return -1; +} + + +int Bitmap::allocBlock() +{ + if (!_freeBlocks) return -1; + + unsigned firstIndex = _firstIndex; + unsigned maxIndex = (_blocks + 7) / 8; + + + for (unsigned index = _firstIndex; index < maxIndex; ++index) + { + uint8_t tmp = _bitmap[index]; + if (!tmp) continue; + + unsigned mask = 0x80; + for (unsigned offset = 0; offset < 8; ++offset) + { + if (tmp & mask) + { + _firstIndex = index; + _bitmap[index] = tmp & ~mask; + --_freeBlocks; + return index * 8 + offset; + } + mask = mask >> 1; + } + } + + for (unsigned index = 0; index < _firstIndex; ++index) + { + uint8_t tmp = _bitmap[index]; + if (!tmp) continue; + + unsigned mask = 0x80; + for (unsigned offset = 0; offset < 8; ++offset) + { + if (tmp & mask) + { + _firstIndex = index; + _bitmap[index] = tmp & ~mask; + --_freeBlocks; + return index * 8 + offset; + } + mask = mask >> 1; + } + } + + + // should never happen... + return -1; +} \ No newline at end of file diff --git a/Bitmap.h b/Bitmap.h new file mode 100644 index 0000000..b4dcf40 --- /dev/null +++ b/Bitmap.h @@ -0,0 +1,44 @@ +#ifndef __BITMAP_H__ +#define __BITMAP_H__ + +#include +class BlockDevice; + +namespace ProFUSE { + +class Bitmap { +public: + + Bitmap(unsigned blocks); + Bitmap(BlockDevice *device, int keyPointer, int blocks); + //todo -- constructor by loading fro, block device... + ~Bitmap(); + + int allocBlock(); + int allocBlock(unsigned block); + + void freeBlock(unsigned block); + + + unsigned freeBlocks() const { return _freeBlocks; } + unsigned blocks() const { return _blocks; } + unsigned bitmapBlocks() const { return _bitmapBlocks; } + unsigned bitmapSize() const { return _bitmapBlocks * 512; } + const void *bitmap() const { return _bitmap; } + +private: + + unsigned _freeIndex; + unsigned _freeBlocks; + + unsigned _blocks; + unsigned _bitmapBlocks; + + uint8_t *_bitmap; +}; + + + +} + +#endif diff --git a/BlockDevice.cpp b/BlockDevice.cpp new file mode 100644 index 0000000..feeae82 --- /dev/null +++ b/BlockDevice.cpp @@ -0,0 +1,167 @@ +#include "BlockDevice.h" +#include "Exception.h" +#include "MappedFile.h" + +#include +#include +#include + +#include +#include +#include + +using namespace ProFUSE; + + + +BlockDevice::~BlockDevice() +{ +} + + +#pragma mark DiskImage + +DiskImage::DiskImage(const char *name, bool readOnly) : + _file(NULL) +{ + _file = new MappedFile(name, readOnly); +} + +DiskImage::DiskImage(MappedFile *file) : + _file(file) +{ +} + +DiskImage::~DiskImage() +{ + delete _file; +} + +bool DiskImage::readOnly() +{ + #undef __METHOD__ + #define __METHOD__ "DiskImage::readOnly" + + if (_file) return _file->readOnly(); + + throw Exception(__METHOD__ ": File not set."); +} + + + + + +void DiskImage::read(unsigned block, void *bp) +{ + #undef __METHOD__ + #define __METHOD__ "DiskImage::read" + + + if (_file) return _file->readBlock(block, bp); + + throw Exception(__METHOD__ ": File not set."); +} + +void DiskImage::write(unsigned block, const void *bp) +{ + #undef __METHOD__ + #define __METHOD__ "DiskImage::write" + + if (_file) return _file->writeBlock(block, bp); + + throw Exception(__METHOD__ ": File not set."); +} + + + +void DiskImage::sync() +{ + #undef __METHOD__ + #define __METHOD__ "DiskImage::sync" + + if (_file) return _file->sync(); + + throw Exception(__METHOD__ ": File not set."); +} + + + +ProDOSOrderDiskImage::ProDOSOrderDiskImage(const char *name, bool readOnly) : + DiskImage(name, readOnly) +{ + Validate(file()); +} + +ProDOSOrderDiskImage::ProDOSOrderDiskImage(MappedFile *file) : + DiskImage(file) +{ +} + +ProDOSOrderDiskImage *ProDOSOrderDiskImage::Create(const char *name, size_t blocks) +{ + MappedFile *file = new MappedFile(name, blocks * 512); + return new ProDOSOrderDiskImage(file); +} + +ProDOSOrderDiskImage *ProDOSOrderDiskImage::Open(MappedFile *file) +{ + Validate(file); + return new ProDOSOrderDiskImage(file); +} + +void ProDOSOrderDiskImage::Validate(MappedFile *f) +{ + #undef __METHOD__ + #define __METHOD__ "ProDOSOrderDiskImage::Validate" + + if (!f) throw Exception(__METHOD__ ": File not set."); + + size_t size = f->fileSize(); + + if (size % 512) + throw Exception(__METHOD__ ": Invalid file format."); + + f->reset(); + f->setBlocks(size / 512); +} + +DOSOrderDiskImage::DOSOrderDiskImage(const char *name, bool readOnly) : + DiskImage(name, readOnly) +{ + Validate(file()); +} + +DOSOrderDiskImage::DOSOrderDiskImage(MappedFile *file) : + DiskImage(file) +{ +} + +DOSOrderDiskImage *DOSOrderDiskImage::Create(const char *name, size_t blocks) +{ + MappedFile *file = new MappedFile(name, blocks * 512); + file->setDosOrder(true); + return new DOSOrderDiskImage(file); +} + +DOSOrderDiskImage *DOSOrderDiskImage::Open(MappedFile *file) +{ + Validate(file); + return new DOSOrderDiskImage(file); +} + +void DOSOrderDiskImage::Validate(MappedFile *f) +{ + #undef __METHOD__ + #define __METHOD__ "DOSOrderDiskImage::Validate" + + if (!f) throw Exception(__METHOD__ ": File not set."); + + size_t size = f->fileSize(); + + if (size % 512) + throw Exception(__METHOD__ ": Invalid file format."); + + f->reset(); + f->setDosOrder(true); + f->setBlocks(size / 512); +} diff --git a/BlockDevice.h b/BlockDevice.h new file mode 100644 index 0000000..e024161 --- /dev/null +++ b/BlockDevice.h @@ -0,0 +1,83 @@ +#ifndef __BLOCKDEVICE_H__ +#define __BLOCKDEVICE_H__ + +#include +#include + +#include "Exception.h" + +namespace ProFUSE { + +class MappedFile; + +class BlockDevice { +public: + virtual ~BlockDevice(); + + virtual void read(unsigned block, void *bp) = 0; + virtual void write(unsigned block, const void *bp) = 0; + + virtual bool readOnly() = 0; + virtual void sync() = 0; +}; + + + +class DiskImage : public BlockDevice { +public: + + virtual ~DiskImage(); + + virtual void read(unsigned block, void *bp); + virtual void write(unsigned block, const void *bp); + virtual void sync(); + + virtual bool readOnly(); + + +protected: + + DiskImage(MappedFile * = NULL); + DiskImage(const char *name, bool readOnly); + + MappedFile *file() { return _file; } + void setFile(MappedFile *); + +private: + MappedFile *_file; +}; + + +class ProDOSOrderDiskImage : public DiskImage { +public: + + ProDOSOrderDiskImage(const char *name, bool readOnly); + + static ProDOSOrderDiskImage *Create(const char *name, size_t blocks); + static ProDOSOrderDiskImage *Open(MappedFile *); + +private: + ProDOSOrderDiskImage(MappedFile *); + static void Validate(MappedFile *); +}; + +class DOSOrderDiskImage : public DiskImage { +public: + + DOSOrderDiskImage(const char *name, bool readOnly); + + static DOSOrderDiskImage *Create(const char *name, size_t blocks); + static DOSOrderDiskImage *Open(MappedFile *); + +private: + DOSOrderDiskImage(MappedFile *); + static void Validate(MappedFile *); +}; + + + + + +} + +#endif \ No newline at end of file diff --git a/Buffer.cpp b/Buffer.cpp new file mode 100644 index 0000000..9e855ad --- /dev/null +++ b/Buffer.cpp @@ -0,0 +1,40 @@ + +#include "Buffer.h" + +using namespace ProFUSE; + +void Buffer::push16be(uint16_t x) +{ + _buffer.push_back((x >> 8) & 0xff); + _buffer.push_back(x & 0xff); +} + +void Buffer::push16le(uint16_t x) +{ + _buffer.push_back(x & 0xff); + _buffer.push_back((x >> 8) & 0xff); +} + +void Buffer::push32be(uint32_t x) +{ + _buffer.push_back((x >> 24) & 0xff); + _buffer.push_back((x >> 16) & 0xff); + _buffer.push_back((x >> 8) & 0xff); + _buffer.push_back(x & 0xff); +} + +void Buffer::push32le(uint32_t x) +{ + _buffer.push_back(x & 0xff); + _buffer.push_back((x >> 8) & 0xff); + _buffer.push_back((x >> 16) & 0xff); + _buffer.push_back((x >> 24) & 0xff); +} + +void Buffer::pushBytes(const void *data, unsigned size) +{ + for (unsigned i = 0; i < size; ++i) + { + _buffer.push_back( ((uint8_t *)data)[i] ); + } +} \ No newline at end of file diff --git a/Buffer.h b/Buffer.h new file mode 100644 index 0000000..5a0ebf1 --- /dev/null +++ b/Buffer.h @@ -0,0 +1,33 @@ +#ifndef __BUFFER_H__ +#define __BUFFER_H__ + +#include + +namespace ProFUSE { +class Buffer { + +public: + Buffer() {} + Buffer(unsigned size) { _buffer.reserve(size); } + + void *buffer() const { return (void *)&_buffer[0]; } + unsigned size() const { return _buffer.size(); } + void resize(unsigned size) { _buffer.resize(size); } + + void push8(uint8_t x) { _buffer.push_back(x); } + + void push16be(uint16_t); + void push16le(uint16_t); + + void push32be(uint32_t); + void push32le(uint32_t); + + void pushBytes(const void *data, unsigned size); + + +private: + std::vector _buffer; +}; + +} +#endif diff --git a/DavexDiskImage.cpp b/DavexDiskImage.cpp new file mode 100644 index 0000000..ac8818b --- /dev/null +++ b/DavexDiskImage.cpp @@ -0,0 +1,144 @@ +#include "DavexDiskImage.h" +#include "MappedFile.h" +#include "Buffer.h" + +#include +#include +#include +#include +#include +#include + +using namespace ProFUSE; + +/* + http://www.umich.edu/~archive/apple2/technotes/ftn/FTN.E0.8004 + */ + +static const char *IdentityCheck = "\x60VSTORE [Davex]\x00"; + +DavexDiskImage::DavexDiskImage(const char *name, bool readOnly) : + DiskImage(name, readOnly) +{ + Validate(file()); +} + +// private, validation already performed. +DavexDiskImage::DavexDiskImage(MappedFile *f) : + DiskImage(f) +{ +} + + +DavexDiskImage::~DavexDiskImage() +{ + // scan and update usedBlocks? +} + +void DavexDiskImage::Validate(MappedFile *f) +{ +#undef __METHOD__ +#define __METHOD__ "DavexDiskImage::Validate" + + size_t size = f->fileSize(); + void * data = f->fileData(); + bool ok = false; + unsigned blocks = (size / 512) - 1; + + do { + if (size < 512) break; + if (size % 512) break; + + // identity. + if (std::memcmp(data, IdentityCheck, 16)) + break; + + // file format. + if (f->read8(16) != 0) + break; + + // total blocks + if (f->read32(33, LittleEndian) != blocks) + break; + + // file number -- must be 1 + if (f->read8(64) != 1) + break; + + ok = true; + } while (false); + + + if (!ok) + throw Exception(__METHOD__ ": Invalid file format."); + + f->setBlocks(blocks); + f->setOffset(512); + f->setDosOrder(false); +} + +DavexDiskImage *DavexDiskImage::Open(MappedFile *file) +{ +#undef __METHOD__ +#define __METHOD__ "DavexDiskImage::Open" + Validate(file); + + return new DavexDiskImage(file); +} + +DavexDiskImage *DavexDiskImage::Create(const char *name, size_t blocks) +{ +#undef __METHOD__ +#define __METHOD__ "DavexDiskImage::Create" + + uint8_t *data; + + MappedFile *file = new MappedFile(name, blocks * 512 + 512); + + data = (uint8_t *)file->fileData(); + + Buffer header(512); + + header.pushBytes(IdentityCheck, 16); + // file Format + header.push8(0); + //version + header.push8(0); + // version + header.push8(0x10); + + // reserved. + header.resize(32); + + //deviceNum + header.push8(1); + + // total blocks + header.push32le(blocks); + + // unused blocks + header.push32le(0); + + // volume Name + header.pushBytes("\x08Untitled", 9); + + // name + reserved. + header.resize(64); + + // file number + header.push8(1); + + //starting block + header.push32le(0); + + // reserved + header.resize(512); + + + std::memcpy(file->fileData(), header.buffer(), 512); + file->sync(); + + file->setOffset(512); + file->setBlocks(blocks); + return new DavexDiskImage(file); +} diff --git a/DavexDiskImage.h b/DavexDiskImage.h new file mode 100644 index 0000000..0541428 --- /dev/null +++ b/DavexDiskImage.h @@ -0,0 +1,30 @@ + + +#include "BlockDevice.h" +#include + +namespace ProFUSE { + +// only supports 1 file; may be split over multiple files. + +class DavexDiskImage : public DiskImage { +public: + + DavexDiskImage(const char *, bool readOnly); + virtual ~DavexDiskImage(); + + static DavexDiskImage *Create(const char *name, size_t blocks); + static DavexDiskImage *Open(MappedFile *); + + +private: + + DavexDiskImage(MappedFile *); + static void Validate(MappedFile *); + + bool _changed; + std::string _volumeName; +}; + + +} \ No newline at end of file diff --git a/Directory.cpp b/Directory.cpp new file mode 100644 index 0000000..83d7058 --- /dev/null +++ b/Directory.cpp @@ -0,0 +1,133 @@ +#include "Directory" + +#include + +#include "Exception.h" + +using namespace ProFUSE; + +static bool isalpha(unsigned c) +{ + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && x <= 'z') ; +} + +static bool isalnumdot(unsigned c) +{ + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <='9') + || (c == '.') ; + +} + +static uint16_t read16(uint8_t *data, unsigned offset) +{ + return data[offset + 0] + | (data[offset + 1] << 8) ; +} + +static uint32_t read24(uint8_t *data, unsigned offset) +{ + return data[offset + 0] + | (data[offset + 1] << 8) + | (data[offset + 2] << 16) ; +} + + +static uint32_t read32(uint8_t *data, unsigned offset) +{ + return data[offset + 0] + | (data[offset + 1] << 8) + | (data[offset + 2] << 16) ; + | (data[offset + 3] << 24) ; +} + +unsigned ValidName(const char *name) +{ + unsigned length; + + if (!name) return 0; + + if (!isalpha(*name)) return 0; + length = 1; + + for (length = 1; length < 17; ++length) + { + if (!isalnumdot(name[length])) return 0; + } + + if (length > 15) return 0; + return length; +} + +Directory::Directory(unsigned type, const char *name) +{ +#undef __METHOD__ +#define __METHOD__ "Directory::Directory" + + _nameLength = ValidName(name); + + if (!_length) + throw Exception(__METHOD__ ": Invalid name."); + + _storageType = type; + std::strncpy(_name, name, 16); + + _access = 0xc3; + _entryLength = 0x27; + _entriesPerBlock = 13; + _fileCount = 0; + + _device = NULL; +} + +Directory::Directory(const void *bp) : + _creation(0, 0) +{ +#undef __METHOD__ +#define __METHOD__ "Directory::Directory" + + const uint8_t *data = (const uint8_t *)bp; + + _storageType = data[0x00] >> 4; + _nameLength = data[0x00] & 0x0f; + std::memcpy(_name, data + 1, _nameLength); + _name[_nameLength] = 0; + _creation = DateTime(read16(data, 0x1c), read16(data, 0x1e)); + + _access = data[0x22]; + _entryLength = data[0x23]; + _entriesPerBlock = data[0x24]; + + _fileCount = read16(data, 0x25); + + // parse child file entries. +} + +Directory::~Directory() +{ +} + +Directory::setAccess(unsigned access) +{ +#undef __METHOD__ +#define __METHOD__ "Directory::setAccess" + + if ((access & 0xe3) != access) + throw Exception(__METHOD__ ": Illegal access."); + _access = access; + + // todo -- mark dirty? update block? +} + +Directory::setName(const char *name) +{ + unsigned length = ValidName(name); + if (!length) + throw Exception(__METHOD__ ": Invalid name."); + _nameLength = length; + std::strncpy(_name, name, 16); + + // todo -- update or mark dirty. +} diff --git a/Directory.h b/Directory.h new file mode 100644 index 0000000..8fe229c --- /dev/null +++ b/Directory.h @@ -0,0 +1,151 @@ +#ifndef __DIRECTORY_H__ +#define __DIRECTORY_H__ + +#include +#include + +#include "DateTime.h" + + +namespace ProFUSE { + +class BlockDevice; +class Bitmap; +class FileEntry; +class Volume; + + +enum { + DestroyEnabled = 0x80, + RenameEnabled = 0x40, + BackupNeeded = 0x20, + WriteEnabled = 0x02, + ReadEnabled = 0x01 +}; + + +class Entry { +public: + virtual ~Entry(); + + + + unsigned storageType() const { return _storageType; } + + unsigned nameLength() const { return _nameLength; } + const char *name() const { return _name; } + void setName(const char *name); + + + + // returns strlen() on success, 0 on failure. + static unsigned ValidName(const char *); + +protected: + Entry(int storageType, const char *name); + +private: + + unsigned _address; // absolute address on disk. + Volume *_volume; + + unsigned _storageType; + unsigned _nameLength; + char _name[15+1]; + +}; + +class Directory public Entry { +public: + virtual ~Directory(); + + + DateTime creation() const { return _creation; } + + unsigned access() const { return _access; } + unsigned entryLength() const { return _entryLength; } + unsigned entriesPerBlock() const { return _entriesPerBlock; } + + unsigned fileCount() const { return _fileCount; } + + void setAccess(unsigned access); + + +protected: + Directory(unsigned type, const char *name); + Directory(BlockDevice *, unsigned block); + + std::vector _children; + std::vector _entryBlocks; + + BlockDevice *_device; + + +private: + + DateTime _creation; + // version + // min version + unsigned _access; + usnigned _entryLength; // always 0x27 + unsigned _entriesPerBlock; //always 0x0d + + unsigned _fileCount; +}; + + + +class VolumeDirectory: public Directory { +public: + + virtual ~VolumeDirectory(); + + unsigned bitmapPointer() const { return _bitmapPointer; } + unsigned totalBlocks() const { return _totalBlocks; } + + // bitmap stuff... + int allocBlock(); + void freeBlock(unsigned block); + +private: + Bitmap *_bitmap; + + unsigned _totalBlocks; + unsigned _bitmapPointer; + + // inode / free inode list? + +}; + + +class SubDirectory : public Directory { +public: + +private: + unsigned _parentPointer; + unsigned _parentEntryNumber; + unsigned _parentEntryLength; +}; + + +class FileEntry : public Entry { +public: + + +private: + unsigned _fileType; + unsigned _keyPointer; + unsigned _blocksUsed; + unsigned _eof; + DateTime _creation; + //version + //min version + unsigned _access; + unsigned _auxType; + DateTime _modification; + unsigned _headerPointer; + +}; + +} +#endif diff --git a/DiskCopy42Image.cpp b/DiskCopy42Image.cpp new file mode 100644 index 0000000..10f1d94 --- /dev/null +++ b/DiskCopy42Image.cpp @@ -0,0 +1,181 @@ +#include "DiskCopy42Image.h" +#include "MappedFile.h" +#include "Buffer.h" + +#include + +using namespace ProFUSE; + +DiskCopy42Image::DiskCopy42Image(MappedFile *f) : + DiskImage(f), + _changed(false) +{ +} + +DiskCopy42Image::DiskCopy42Image(const char *name, bool readOnly) : + DiskImage(name, readOnly), + _changed(false) +{ + Validate(file()); +} + +DiskCopy42Image::~DiskCopy42Image() +{ + if (_changed) + { + MappedFile *f = file(); + if (f) + { + uint32_t cs = Checksum(f->offset() + (uint8_t *)f->fileData(), + f->blocks() * 512); + + f->write32(72, cs, BigEndian); + f->sync(); + } + // TODO -- checksum + } +} + +uint32_t DiskCopy42Image::Checksum(void *data, size_t size) +{ + uint32_t rv = 0; + uint8_t *dp = (uint8_t *)data; + + if (size & 0x01) return rv; + + for (size_t i = 0; i < size; i += 2) + { + rv += dp[i] << 8; + rv += dp[i+1]; + + rv = (rv >> 1) + (rv << 31); + } + + return rv; +} + +DiskCopy42Image *DiskCopy42Image::Open(MappedFile *f) +{ + Validate(f); + return new DiskCopy42Image(f); +} + +static uint8_t DiskFormat(size_t blocks) +{ + switch (blocks) + { + case 800: return 0; + case 1600: return 1; + case 1440: return 2; + case 2880: return 3; + default: return 0xff; + } +} + +static uint8_t FormatByte(size_t blocks) +{ + switch(blocks) + { + case 800: return 0x12; + default: return 0x22; + } +} +DiskCopy42Image *DiskCopy42Image::Create(const char *name, size_t blocks) +{ + MappedFile *file = new MappedFile(name, blocks * 512 + 84); + file->setOffset(84); + file->setBlocks(blocks); + + Buffer header(84); + + // name -- 64byte pstring. + header.pushBytes("\x08Untitled", 9); + header.resize(64); + + // data size -- number of bytes + header.push32be(blocks * 512); + + // tag size + header.push32be(0); + + // data checksum + // if data is 0, will be 0. + //header.push32be(Checksum(file->fileData(), blocks * 512)); + header.push32be(0); + + // tag checksum + header.push32be(0); + + // disk format. + /* + * 0 = 400k + * 1 = 800k + * 2 = 720k + * 3 = 1440k + * 0xff = other + */ + header.push8(DiskFormat(blocks)); + + // formatbyte + /* + * 0x12 = 400k + * 0x22 = >400k mac + * 0x24 = 800k appleII + */ + header.push8(FormatByte(blocks)); + + // private + header.push16be(0x100); + + std::memcpy(file->fileData(), header.buffer(), 84); + file->sync(); + + return new DiskCopy42Image(file); +} + +void DiskCopy42Image::Validate(MappedFile *file) +{ + size_t bytes = 0; + size_t size = file->fileSize(); + bool ok = false; + uint32_t checksum; + + do { + if (size < 84) break; + + // name must be < 64 + if (file->read8(0) > 63) break; + + if (file->read32(82, BigEndian) != 0x100) + break; + + // bytes, not blocks. + bytes = file->read32(64, BigEndian); + + if (bytes % 512) break; + + if (size < 84 + bytes) break; + + // todo -- checksum. + checksum = file->read32(72, BigEndian); + + ok = true; + } while (false); + + + uint32_t cs = Checksum(64 + (uint8_t *)file->fileData(), bytes); + + if (cs != checksum) + { + fprintf(stderr, "Warning: checksum invalid.\n"); + } + file->reset(); + file->setOffset(64); + file->setBlocks(bytes / 512); +} + +void DiskCopy42Image::write(unsigned block, const void *bp) +{ + DiskImage::write(block, bp); + _changed = true; +} \ No newline at end of file diff --git a/DiskCopy42Image.h b/DiskCopy42Image.h new file mode 100644 index 0000000..689deca --- /dev/null +++ b/DiskCopy42Image.h @@ -0,0 +1,32 @@ +#ifndef __DISKCOPY42IMAGE_H__ +#define __DISKCOPY42IMAGE_H__ + +#include "BlockDevice.h" + +#include + +namespace ProFUSE { + +class DiskCopy42Image : public DiskImage { +public: + DiskCopy42Image(const char *name, bool readOnly); + virtual ~DiskCopy42Image(); + + static DiskCopy42Image *Create(const char *name, size_t blocks); + static DiskCopy42Image *Open(MappedFile *); + + static uint32_t Checksum(void *data, size_t size); + + virtual void write(unsigned block, const void *bp); + + +private: + + DiskCopy42Image(MappedFile *); + static void Validate(MappedFile *); + bool _changed; +}; + +} + +#endif \ No newline at end of file diff --git a/Exception.cpp b/Exception.cpp new file mode 100644 index 0000000..ee0cbee --- /dev/null +++ b/Exception.cpp @@ -0,0 +1,13 @@ + +#include "Exception.h" + +using namespace ProFUSE; + +Exception::~Exception() throw() +{ +} + +const char *Exception::what() +{ + return _string.c_str(); +} \ No newline at end of file diff --git a/Exception.h b/Exception.h new file mode 100644 index 0000000..cf9ffb7 --- /dev/null +++ b/Exception.h @@ -0,0 +1,59 @@ +#ifndef __EXCEPTION_H__ +#define __EXCEPTION_H__ + +#include +#include + +namespace ProFUSE { + +class Exception : public std::exception +{ +public: + Exception(const char *cp); + Exception(const std::string &str); + Exception(const char *cp, int error); + Exception(const std::string& string, int error); + + virtual ~Exception() throw (); + + virtual const char *what(); + + int error() const; +private: + int _error; + std::string _string; + +}; + +inline Exception::Exception(const char *cp): + _error(0), + _string(cp) +{ +} + +inline Exception::Exception(const std::string& string): + _error(0), + _string(string) +{ +} + +inline Exception::Exception(const char *cp, int error): + _error(error), + _string(cp) +{ +} + +inline Exception::Exception(const std::string& string, int error): + _error(error), + _string(string) +{ +} + +inline int Exception::error() const +{ + return _error; +} + +} + +#endif \ No newline at end of file diff --git a/Lock.cpp b/Lock.cpp new file mode 100644 index 0000000..ddd9fa4 --- /dev/null +++ b/Lock.cpp @@ -0,0 +1,26 @@ +#include "Lock.h" + +Lock::Lock() +{ + pthread_mutex_init(&_mutex, NULL); +} + +Lock::~Lock() +{ + pthread_mutex_destroy(&_mutex); +} + +void Lock::lock() +{ + pthread_mutex_lock(&_mutex); +} + +void Lock::unlock() +{ + pthread_mutex_unlock(&_mutex); +} + +bool Lock::tryLock() +{ + return pthread_mutex_trylock(&_mutex) == 0; +} \ No newline at end of file diff --git a/Lock.h b/Lock.h new file mode 100644 index 0000000..cbb3bc7 --- /dev/null +++ b/Lock.h @@ -0,0 +1,28 @@ +#ifndef __LOCK_H__ +#define __LOCK_H__ + +#include + +class Lock { +public: + Lock(); + ~Lock(); + + void lock(); + void unlock(); + + bool tryLock(); + +private: + pthread_mutex_t _mutex; +}; + +class Locker { +public: + Locker(Lock& lock) : _lock(lock) { _lock.lock(); } + ~Locker() { _lock.unlock(); } +private: + Lock &_lock; +}; + +#endif \ No newline at end of file diff --git a/MappedFile.cpp b/MappedFile.cpp new file mode 100644 index 0000000..fa3b198 --- /dev/null +++ b/MappedFile.cpp @@ -0,0 +1,307 @@ +#include "MappedFile.h" +#include "Exception.h" +#include +#include +#include + +#include +#include +#include + +#include "auto.h" + +using namespace ProFUSE; + + + +MappedFile::MappedFile(const char *name, bool readOnly) : + _fd(-1), + _map(MAP_FAILED) +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::MappedFile" + + auto_fd fd(::open(name, readOnly ? O_RDONLY : O_RDWR)); + + if (fd < 0) + { + throw Exception(__METHOD__ ": Unable to open file.", errno); + } + // + init(fd.release(), readOnly); +} + +MappedFile::MappedFile(int fd, bool readOnly) : + _fd(-1), + _map(MAP_FAILED) +{ + init(fd, readOnly); +} + +// todo -- verify throw calls destructor. +MappedFile::MappedFile(const char *name, size_t size) : + _fd(-1), + _map(MAP_FAILED) +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::MappedFile" + + _size = size; + _readOnly = false; + auto_fd fd(::open(name, O_CREAT | O_TRUNC | O_RDWR, 0644)); + + if (fd < 0) + throw Exception(__METHOD__ ": Unable to create file.", errno); + + // TODO -- is ftruncate portable? + if (::ftruncate(fd, _size) < 0) + { + throw Exception(__METHOD__ ": Unable to truncate file.", errno); + } + + //_map = ::mmap(NULL, _size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, _fd, 0); + + auto_map map( + fd, + _size, + PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED + ); + + if (map == MAP_FAILED) throw Exception(__METHOD__ ": Unable to map file.", errno); + + _fd = fd.release(); + _map = map.release(); +} + +MappedFile::~MappedFile() +{ + if (_map != MAP_FAILED) ::munmap(_map, _size); + if (_fd >= 0) ::close(_fd); +} + +void MappedFile::init(int f, bool readOnly) +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::init" + + auto_fd fd(f); + + _offset = 0; + _blocks = 0; + _size = 0; + _readOnly = readOnly; + _dosOrder = false; + + _size = ::lseek(_fd, 0, SEEK_END); + + if (_size < 0) + throw Exception(__METHOD__ ": Unable to determine file size.", errno); + +/* + _map = ::mmap(NULL, _size, readOnly ? PROT_READ : PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED, fd, 0); +*/ + auto_map map( + fd, + _size, + readOnly ? PROT_READ : PROT_READ | PROT_WRITE, + readOnly ? MAP_FILE : MAP_FILE | MAP_SHARED + ); + + if (map == MAP_FAILED) throw Exception(__METHOD__ ": Unable to map file.", errno); + + _fd = fd.release(); + _map = map.release(); +} + + +const unsigned MappedFile::DOSMap[] = { + 0x00, 0x0e, 0x0d, 0x0c, + 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x0f +}; + + +void MappedFile::readBlock(unsigned block, void *bp) +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::readBlock" + + + if (block >= _blocks) throw Exception(__METHOD__ ": Invalid block number."); + if (bp == 0) throw Exception(__METHOD__ ": Invalid address."); + + + if (_dosOrder) + { + unsigned track = (block & ~0x07) << 9; + unsigned sector = (block & 0x07) << 1; + + for (unsigned i = 0; i < 2; ++i) + { + size_t address = track | (DOSMap[sector+i] << 8); + + std::memcpy(bp, (uint8_t *)_map + _offset + address, 256); + + bp = (uint8_t *)bp + 256; + } + + } + else + { + size_t address = block * 512; + std::memcpy(bp, (uint8_t *)_map + _offset + address, 512); + } +} + +void MappedFile::writeBlock(unsigned block, const void *bp) +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::writeBlock" + + if (block > _blocks) throw Exception(__METHOD__ ": Invalid block number."); + + if ( (_readOnly) || (_map == MAP_FAILED)) + throw Exception(__METHOD__ ": File is readonly."); + + if (_dosOrder) + { + unsigned track = (block & ~0x07) << 9; + unsigned sector = (block & 0x07) << 1; + + for (unsigned i = 0; i < 2; ++i) + { + size_t address = track | (DOSMap[sector+i] << 8); + + std::memcpy((uint8_t *)_map + _offset + address, bp, 256); + bp = (uint8_t *)bp + 256; + } + } + else + { + size_t address = block * 512; + std::memcpy((uint8_t *)_map + _offset + address , bp, 512); + } +} + +void MappedFile::sync() +{ + #undef __METHOD__ + #define __METHOD__ "MappedFile::sync" + + if ( (_readOnly) || (_map == MAP_FAILED)) + return; + + if (::msync(_map, _size, MS_SYNC) < 0) + throw Exception(__METHOD__ ": msync error.", errno); +} + +void MappedFile::reset() +{ + _offset = 0; + _blocks = 0; + _dosOrder = false; +} + +/* +uint8_t MappedFile::read8(size_t location) const +{ + // check for size? + uint8_t *map = (uint8_t *)_map; + + return map[location]; +} +*/ + +uint16_t MappedFile::read16(size_t location, int byteOrder) const +{ + // check for size? + uint8_t *map = (uint8_t *)_map; + + switch(byteOrder) + { + case LittleEndian: + return (map[location + 1] << 8) + | (map[location]); + case BigEndian: + return (map[location] << 8) + | (map[location+1]); + default: + return 0; + } +} + +uint32_t MappedFile::read32(size_t location, int byteOrder) const +{ + // check for size? + uint8_t *map = (uint8_t *)_map; + + switch(byteOrder) + { + case LittleEndian: + return (map[location+3] << 24) + | (map[location+2] << 16) + | (map[location+1] << 8) + | (map[location]) + ; + case BigEndian: + return (map[location] << 24) + | (map[location+1] << 16) + | (map[location+2] << 8) + | (map[location+3]) + ; + + default: + return 0; + } +} + + +/* +void MappedFile::write8(size_t location, uint8_t data) +{ + uint8_t *map = (uint8_t *)_map; + + map[location] = data; +} +*/ + +void MappedFile::write16(size_t location, uint16_t data, int byteOrder) +{ + uint8_t *map = (uint8_t *)_map; + + switch(byteOrder) + { + case LittleEndian: + map[location] = data & 0xff; + map[location+1] = (data >> 8) & 0xff; + break; + case BigEndian: + map[location] = (data >> 8) & 0xff; + map[location+1] = data & 0xff; + break; + } +} + +void MappedFile::write32(size_t location, uint32_t data, int byteOrder) +{ + uint8_t *map = (uint8_t *)_map; + + switch(byteOrder) + { + case LittleEndian: + map[location] = data & 0xff; + map[location+1] = (data >> 8) & 0xff; + map[location+2] = (data >> 16) & 0xff; + map[location+3] = (data >> 24) & 0xff; + break; + case BigEndian: + map[location] = (data >> 24) & 0xff; + map[location+1] = (data >> 16) & 0xff; + map[location+2] = (data >> 8) & 0xff; + map[location+3] = data & 0xff; + break; + } +} diff --git a/MappedFile.h b/MappedFile.h new file mode 100644 index 0000000..9414067 --- /dev/null +++ b/MappedFile.h @@ -0,0 +1,78 @@ +#ifndef __MAPPED_FILE__ +#define __MAPPED_FILE__ + + +#include +#include + +namespace ProFUSE { + + +enum { + LittleEndian = 0x3412, + BigEndian = 0x1234 +}; + +class MappedFile { +public: + MappedFile(const char *name, bool ReadOnly); + MappedFile(int fd, bool readOnly); + MappedFile(const char *name, size_t size); + + ~MappedFile(); + + void readBlock(unsigned block, void *bp); + void writeBlock(unsigned block, const void *bp); + + void sync(); + + void reset(); + + bool dosOrder() const { return _dosOrder; } + void setDosOrder(bool tf) { _dosOrder = tf; } + + unsigned offset() const { return _offset; } + void setOffset(unsigned o) { _offset = o; } + + unsigned blocks() const { return _blocks; } + void setBlocks(unsigned b) { _blocks = b; } + + bool readOnly() const { return _readOnly; } + size_t fileSize() const { return _size; } + void *fileData() const { return _map; } + + uint8_t read8(size_t location) const + { + return ((uint8_t *)_map)[location]; + } + uint16_t read16(size_t location, int byteOrder) const; + uint32_t read32(size_t location, int byteOrder) const; + + void write8(size_t location, uint8_t data) + { + ((uint8_t *)_map)[location] = data; + } + void write16(size_t location, uint16_t data, int byteOrder); + void write32(size_t location, uint32_t data, int byteOrder); + + +private: + MappedFile& operator=(const MappedFile& other); + + void init(int fd, bool readOnly); + + static const unsigned DOSMap[]; + + int _fd; + void *_map; + + size_t _size; + bool _readOnly; + + bool _dosOrder; + unsigned _offset; + unsigned _blocks; + +}; +} +#endif \ No newline at end of file diff --git a/UniversalDiskImage.cpp b/UniversalDiskImage.cpp new file mode 100644 index 0000000..688b93f --- /dev/null +++ b/UniversalDiskImage.cpp @@ -0,0 +1,110 @@ +#include "UniversalDiskImage.h" +#include "MappedFile.h" +#include "Buffer.h" + +using namespace ProFUSE; + +UniversalDiskImage::UniversalDiskImage(const char *name, bool readOnly) : + DiskImage(name, readOnly) +{ + Validate(file()); +} + +UniversalDiskImage::UniversalDiskImage(MappedFile *file) : + DiskImage(file) +{ +} + +UniversalDiskImage *UniversalDiskImage::Create(const char *name, size_t blocks) +{ + // 64-byte header. + MappedFile *file = new MappedFile(name, blocks * 512 + 64); + + Buffer header(64); + + + // magic + creator + header.pushBytes("2IMGPRFS", 8); + + // header size. + header.push16le(64); + + // version + header.push16le(1); + + //image format -- ProDOS order + header.push32le(1); + + // flags + header.push32le(0); + + // # blocks. s/b 0 unless prodos-order + header.push32le(blocks); + + // offset to disk data + header.push32le(64); + + // data length + header.push32le(512 * blocks); + + // comment offset, creator, reserved -- 0. + header.resize(64); + + std::memcpy(file->fileData(), header.buffer(), 64); + + + file->setOffset(64); + file->setBlocks(blocks); + return new UniversalDiskImage(file); +} + +UniversalDiskImage *UniversalDiskImage::Open(MappedFile *file) +{ + Validate(file); + return new UniversalDiskImage(file); +} + + +/* + * TODO -- support dos-order & nibblized + * TODO -- honor read-only flag. + * + */ +void UniversalDiskImage::Validate(MappedFile *file) +{ +#undef __METHOD__ +#define __METHOD__ "DavexDiskImage::Validate" + + uint8_t *data = (uint8_t *)file->fileData(); + size_t size = file->fileSize(); + bool ok = false; + unsigned blocks = 0; + unsigned offset = 0; + + do { + + if (size < 64) break; + + if (std::memcmp(data, "2IMG", 4)) break; + + // only prodos supported, for now... + if (file->read32(0x0c, LittleEndian) != 1) break; + + offset = file->read32(0x20, LittleEndian); + blocks = file->read32(0x14, LittleEndian); + + // file size == blocks * 512 + if (file->read32(0x1c, LittleEndian) != blocks * 512) break; + + if (offset + blocks * 512 > size) break; + + ok = true; + } while (false); + + if (!ok) + throw Exception(__METHOD__ ": Invalid file format."); + + file->reset(); + file->setOffset(offset); + file->setBlocks(blocks); +} \ No newline at end of file diff --git a/UniversalDiskImage.h b/UniversalDiskImage.h new file mode 100644 index 0000000..cfcd887 --- /dev/null +++ b/UniversalDiskImage.h @@ -0,0 +1,23 @@ +#ifndef __UNIVERSALDISKIMAGE_H__ +#define __UNIVERSALDISKIMAGE_H__ + + +#include "BlockDevice.h" + +namespace ProFUSE { + +class UniversalDiskImage : public DiskImage { +public: + UniversalDiskImage(const char *name, bool readOnly); + + static UniversalDiskImage *Create(const char *name, size_t blocks); + static UniversalDiskImage *Open(MappedFile *); + +private: + UniversalDiskImage(MappedFile *); + static void Validate(MappedFile *); +}; + +} + +#endif diff --git a/Volume.h b/Volume.h new file mode 100644 index 0000000..47c6973 --- /dev/null +++ b/Volume.h @@ -0,0 +1,92 @@ +#ifndef __VOLUME_H__ +#define __VOLUME_H__ + +#include +#include + +namespace ProFUSE { + +class Bitmap; +class BlockDevice; + +class Volume; +class Directory; +class FileEntry; + + +class Entry { +public: + Entry() : _address(0), _volume(NULL) { } + virtual ~Entry(); + + unsigned address() { return _address; } + void setAddress(unsigned address) { _address = address; } + + Volume *volume() const { return _volume; } + void setVolume(Volume *v) { _volume = v; } + +private: + // physical location on disk (block * 512 + offset) + unsigned _address; + Volume *_volume; +}; + +class Directory { + + FileEntry *childAtIndex(unsigned index); + +private: + unsigned _childCount; + + std::vector_children; +}; + +class FileEntry { + virtual ~FileEntry(); + + virtual unsigned inode(); + Directory *directory(); + Directory *parent(); + +private: + Directory *_parent; + Volume *_volume; +} + +class Volume { +public: + + ~Volume(); + + Volume *Create(BlockDevice *); + Volume *Open(BlockDevice *); + + + int allocBlock(); + +private: + + Volume(BlockDevice *, int); + + + Bitmap *_bitmap; + BlockDevice *_device; + + +}; + + +class FileEntry : public Entry { +public: + virtual ~FileEntry(); + + unsigned inode(); + +private: + _unsigned _inode; + _unsigned _lookupCount; + _unsigned _openCount; +}; +} + +#endif \ No newline at end of file diff --git a/auto.h b/auto.h new file mode 100644 index 0000000..73f6768 --- /dev/null +++ b/auto.h @@ -0,0 +1,72 @@ +#ifndef __AUTO_H__ +#define __AUTO_H__ + +template +class auto_array +{ +public: + auto_array() : _t(NULL) {} + auto_array(T *t) : _t(t) {} + ~auto_array() { if (_t) delete []_t; } + + + T* release() + { T *tmp = _t; _t = NULL; return tmp; } + + T* get() const { return _t; } + operator T*() const { return _t; } + T& operator[](int index) { return _t[index]; } + +private: + T *_t; +}; + +// ::close +#if defined(O_CREAT) +class auto_fd +{ +public: + auto_fd(int fd) : _fd(fd) { } + + ~auto_fd() + { if (_fd != -1) ::close(_fd); } + + int release() + { int tmp = _fd; _fd = -1; return tmp; } + + int get() const { return _fd; } + operator int() const { return _fd; } + +private: + int _fd; +}; +#endif + +// ::mmap, :munmap +#if defined(MAP_FAILED) +class auto_map +{ +public: + auto_map(int fd, size_t size, int prot, int flags) : + _size(size), + _map(::mmap(NULL, size, prot, flags, fd, 0)) + { } + + ~auto_map() + { if (_map != MAP_FAILED) ::munmap(_map, _size); } + + void *release() + { void *tmp = _map; _map = MAP_FAILED; return tmp; } + + void *get() const { return _map; } + operator void *() const { return _map; } + +private: + size_t _size; + void *_map; +}; +#endif + + +#endif + diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..3a657f8 --- /dev/null +++ b/test.cpp @@ -0,0 +1,27 @@ +#include "BlockDevice.h" +#include "Exception.h" +#include "DavexDiskImage.h" +#include "UniversalDiskImage.h" +#include "DiskCopy42Image.h" + +#include + +int main(int argc, char **argv) +{ + if (argc == 2) + { + try { + //ProFUSE::BlockDevice *dev = ProFUSE::ProDOSOrderDiskImage::Create(argv[1], 1600); + //ProFUSE::BlockDevice *dev = ProFUSE::DavexDiskImage::Create(argv[1], 1600); + //ProFUSE::BlockDevice *dev = ProFUSE::UniversalDiskImage::Create(argv[1], 1600); + ProFUSE::BlockDevice *dev = ProFUSE::DiskCopy42Image::Create(argv[1], 1600); + + delete dev; + } catch (ProFUSE::Exception e) { + if (e.error()) printf("%s %s", e.what(), strerror(e.error())); + else printf("%s\n", e.what()); + } + + } + return 0; +}