From 304164ecfa2a3627e99b753301eeded748ae0fe1 Mon Sep 17 00:00:00 2001 From: ksherlock Date: Sun, 13 Mar 2011 16:02:30 +0000 Subject: [PATCH] integrate profuse (classic) git-svn-id: https://profuse.googlecode.com/svn/branches/v2@386 aa027e90-d47c-11dd-86d7-074df07e0730 --- Makefile | 58 ++++- ProDOS/Bitmap.cpp | 101 +++++--- ProDOS/Bitmap.h | 17 +- ProDOS/Disk.cpp | 486 ++++++++++++++++++++++++++++++++++ ProDOS/Disk.h | 83 ++++++ ProDOS/File.cpp | 230 +++++++++++++++++ ProDOS/File.h | 133 ++++++++++ ProDOS/Makefile | 7 - ProDOS/common.h | 18 ++ bin/profuse.cpp | 374 +++++++++++++++++++++++++++ bin/profuse.h | 62 +++++ bin/profuse_dirent.cpp | 130 ++++++++++ bin/profuse_file.cpp | 136 ++++++++++ bin/profuse_stat.cpp | 252 ++++++++++++++++++ bin/profuse_xattr.cpp | 573 +++++++++++++++++++++++++++++++++++++++++ 15 files changed, 2598 insertions(+), 62 deletions(-) create mode 100644 ProDOS/Disk.cpp create mode 100644 ProDOS/Disk.h create mode 100644 ProDOS/File.cpp create mode 100644 ProDOS/File.h delete mode 100644 ProDOS/Makefile create mode 100644 ProDOS/common.h create mode 100644 bin/profuse.cpp create mode 100644 bin/profuse.h create mode 100644 bin/profuse_dirent.cpp create mode 100644 bin/profuse_file.cpp create mode 100644 bin/profuse_stat.cpp create mode 100644 bin/profuse_xattr.cpp diff --git a/Makefile b/Makefile index 45cf1cf..02ebdab 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ LIBS += -lpthread UNAME = $(shell uname -s) ifeq ($(UNAME),Darwin) - fuse_pascal_LIBS += -lfuse_ino64 + FUSE_LIBS += -lfuse_ino64 else - fuse_pascal_LIBS += -lfuse + FUSE_LIBS += -lfuse endif ifdef HAVE_NUFX @@ -25,8 +25,9 @@ OBJECTS += ${wildcard Endian/*.o} OBJECTS += ${wildcard File/*.o} OBJECTS += ${wildcard Pascal/*.o} OBJECTS += ${wildcard ProFUSE/*.o} +OBJECTS += ${wildcard ProDOS/*.o} -TARGETS = apfm newfs_pascal profuse_pascal xattr +TARGETS = o/apfm o/newfs_pascal o/fuse_pascal o/profuse o/xattr BIN_OBJECTS += bin/apfm.o BIN_OBJECTS += bin/fuse_pascal_ops.o @@ -34,6 +35,13 @@ BIN_OBJECTS += bin/newfs_prodos.o BIN_OBJECTS += bin/fuse_pascal.o BIN_OBJECTS += bin/newfs_pascal.o BIN_OBJECTS += bin/xattr.o +BIN_OBJECTS += bin/profuse.o +BIN_OBJECTS += bin/profuse_dirent.o +BIN_OBJECTS += bin/profuse_file.o +BIN_OBJECTS += bin/profuse_stat.o +BIN_OBJECTS += bin/profuse_xattr.o + + CACHE_OBJECTS += Cache/BlockCache.o CACHE_OBJECTS += Cache/ConcreteBlockCache.o @@ -62,12 +70,25 @@ PASCAL_OBJECTS += Pascal/VolumeEntry.o PROFUSE_OBJECTS += ProFUSE/Exception.o PROFUSE_OBJECTS += ProFUSE/Lock.o +PRODOS_OBJECTS += ProDOS/DateTime.o +PRODOS_OBJECTS += ProDOS/Disk.o +PRODOS_OBJECTS += ProDOS/File.o + +all: $(TARGETS) -xattr: bin/xattr.o +#apfm: o/apfm +#fuse_pascal: o/fuse_pascal +#newfs_pascal: o/newfs_pascal +#profuse: o/profuse +#xattr: o/xattr + + + +o/xattr: bin/xattr.o $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ -newfs_pascal: bin/newfs_pascal.o \ +o/newfs_pascal: bin/newfs_pascal.o \ ${CACHE_OBJECTS} \ ${DEVICE_OBJECTS} \ ${ENDIAN_OBJECTS} \ @@ -76,7 +97,7 @@ newfs_pascal: bin/newfs_pascal.o \ ${PASCAL_OBJECTS} $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ -apfm: bin/apfm.o \ +o/apfm: bin/apfm.o \ ${CACHE_OBJECTS} \ ${DEVICE_OBJECTS} \ ${ENDIAN_OBJECTS} \ @@ -86,14 +107,25 @@ apfm: bin/apfm.o \ $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ -fuse_pascal: bin/fuse_pascal.o bin/fuse_pascal_ops.o \ +o/fuse_pascal: bin/fuse_pascal.o bin/fuse_pascal_ops.o \ ${CACHE_OBJECTS} \ ${DEVICE_OBJECTS} \ ${ENDIAN_OBJECTS} \ ${FILE_OBJECTS} \ ${PROFUSE_OBJECTS} \ ${PASCAL_OBJECTS} - $(CC) $(LDFLAGS) $^ $(LIBS) $(fuse_pascal_LIBS) -o $@ + $(CC) $(LDFLAGS) $^ $(LIBS) $(FUSE_LIBS) -o $@ + + +o/profuse: bin/profuse.o bin/profuse_dirent.o bin/profuse_file.o \ + bin/profuse_stat.o bin/profuse_xattr.o \ + ${CACHE_OBJECTS} \ + ${DEVICE_OBJECTS} \ + ${ENDIAN_OBJECTS} \ + ${FILE_OBJECTS} \ + ${PROFUSE_OBJECTS} \ + ${PRODOS_OBJECTS} + $(CC) $(LDFLAGS) $^ $(LIBS) $(FUSE_LIBS) -o $@ clean: @@ -201,3 +233,13 @@ Pascal/VolumeEntry.o: Pascal/VolumeEntry.cpp Pascal/Pascal.h Pascal/Date.h \ Pascal/TextWriter.o: Pascal/TextWriter.cpp Pascal/TextWriter.h \ Pascal/FileEntry.h Pascal/Entry.h Pascal/Date.h ProFUSE/Exception.h + + + +ProDOS/DateTime.o: ProDOS/DateTime.cpp ProDOS/DateTime.h + +ProDOS/Disk.o: ProDOS/Disk.cpp ProDOS/Disk.h + +ProDOS/File.o: ProDOS/File.cpp ProDOS/File.h + + diff --git a/ProDOS/Bitmap.cpp b/ProDOS/Bitmap.cpp index 57ed601..c6868aa 100644 --- a/ProDOS/Bitmap.cpp +++ b/ProDOS/Bitmap.cpp @@ -1,9 +1,8 @@ #include #include -#include -#include "auto.h" - +#include +#include using namespace ProDOS; @@ -34,26 +33,25 @@ Bitmap::Bitmap(unsigned blocks) _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); + _bitmap.reserve(bitmapSize); - // edge case - unsigned tmp = blocks & 0x07; + // mark blocks as free.. + _bitmap.resize(blocks / 8, 0xff); - bitmap[blocks / 8] = ~(0xff >> tmp); + // edge case... - _bitmap = bitmap.release(); + if (blocks & 0x0f) + { + _bitmap.push_back( ~(0xff >> (blocks & 0x0f)) ); + } + // mark any trailing blocks as in use. + _bitmap.resize(bitmapSize, 0x00); } -Bitmap::Bitmap(BlockDevice *device, unsigned keyPointer, unsigned blocks) +Bitmap::Bitmap(Device::BlockCache *cache, unsigned keyPointer, unsigned blocks) { _blocks = blocks; _freeBlocks = 0; @@ -64,47 +62,68 @@ Bitmap::Bitmap(BlockDevice *device, unsigned keyPointer, unsigned blocks) unsigned bitmapSize = _bitmapBlocks * 512; unsigned blockSize = blocks / 8; - auto_array bitmap(new uint8_t[bitmapSize]); + _bitmap.reserve(bitmapSize); + + // load the full block(s). for (unsigned i = 0; i < blockSize; ++i) { - device->read(keyPointer + i, bitmap + 512 * i); + uint8_t *buffer = (uint8_t *)cache->acquire(keyPointer); + + _bitmap.insert(_bitmap.end(), buffer, buffer + 512); + + cache->release(keyPointer); + + keyPointer++; } - // make sure all trailing bits are marked in use. - - // edge case - unsigned tmp = blocks & 0x07; + // and any remaining partial block. - 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) + if (blocks & 4095) { - _freeBlocks += popCount(bitmap[i]); - } - - if (_freeBlocks) - { - for (unsigned i = 0; i < (blocks + 7) / 8; ++i) + + uint8_t *buffer = (uint8_t *)cache->acquire(keyPointer); + + unsigned bits = blocks & 4095; + unsigned bytes = bits / 8; + + //for (unsigned i = 0; i < bits / 8; ++i) _bitmap.push_back(buffer[i]); + + _bitmap.insert(_bitmap.end(), buffer, buffer + bytes); + // partial... + + if (blocks & 0x0f) { - if (bitmap[i]) - { - _freeIndex = i; - break; - } + uint8_t tmp = buffer[bytes]; + tmp &= ~(0xff >> (blocks & 0x0f)); + + _bitmap.push_back(tmp); } - } + + // remainder set to in use. + _bitmap.resize(bitmapSize, 0x00); + cache->release(keyPointer); + + keyPointer++; + + } - _bitmap = bitmap.release(); + + // now set _freeBlocks and _freeIndex; + std::vector::iterator iter; + + _freeIndex = -1; + for (iter = _bitmap.begin(); iter != _bitmap.end(); ++iter) + { + _freeBlocks += popCount(*iter); + if (_freeIndex == -1 && *iter) + _freeIndex = std::distance(_bitmap.begin(), iter); + } } Bitmap::~Bitmap() { - if (_bitmap) delete []_bitmap; } diff --git a/ProDOS/Bitmap.h b/ProDOS/Bitmap.h index 77388a6..54fe12e 100644 --- a/ProDOS/Bitmap.h +++ b/ProDOS/Bitmap.h @@ -2,20 +2,25 @@ #define __BITMAP_H__ #include +#include +namespace Device +{ + class BlockDevice; + class BlockCache; +} + namespace ProDOS { -class BlockDevice; - class Bitmap { public: Bitmap(unsigned blocks); - Bitmap(BlockDevice *device, unsigned keyPointer, unsigned blocks); - //todo -- constructor by loading from, block device... + Bitmap(Device::BlockCache *cache, unsigned keyPointer, unsigned blocks); + ~Bitmap(); int allocBlock(); @@ -28,7 +33,7 @@ public: unsigned blocks() const { return _blocks; } unsigned bitmapBlocks() const { return _bitmapBlocks; } unsigned bitmapSize() const { return _bitmapBlocks * 512; } - const void *bitmap() const { return _bitmap; } + const void *bitmap() const { return &_bitmap[0]; } private: @@ -38,7 +43,7 @@ private: unsigned _blocks; unsigned _bitmapBlocks; - uint8_t *_bitmap; + std::vector _bitmap; }; diff --git a/ProDOS/Disk.cpp b/ProDOS/Disk.cpp new file mode 100644 index 0000000..0270073 --- /dev/null +++ b/ProDOS/Disk.cpp @@ -0,0 +1,486 @@ +/* + * Disk.cpp + * ProFUSE + * + * Created by Kelvin Sherlock on 12/18/08. + * + */ + +#include "Disk.h" + + +#include "common.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +struct ucmp +{ + bool operator()(unsigned a, unsigned b) const + { + return a < b; + } +}; + +using std::set; +using std::vector; + +using namespace LittleEndian; + +typedef set uset; + +Disk::Disk() +{ + _blocks = 0; + +} + +Disk::~Disk() +{ +} + +Disk::Disk(Device::BlockDevicePointer device) : + _device(device) +{ + _blocks = _device->blocks(); +} + +DiskPointer Disk::OpenFile(Device::BlockDevicePointer device) +{ + DiskPointer disk(new Disk(device)); + + return disk; +} + +// load the mini entry into the regular entry. +int Disk::Normalize(FileEntry &f, unsigned fork, ExtendedEntry *ee) +{ + uint8_t buffer[BLOCK_SIZE]; + int ok; + + if (fork > 1) return -P8_INVALID_FORK; + + if (f.storage_type != EXTENDED_FILE) + { + return fork == 0 ? 0 : -P8_INVALID_FORK; + } + + ok = Read(f.key_pointer, buffer); + if (ok < 0) return ok; + + ExtendedEntry e; + e.Load(buffer); + + if (fork == 0) + { + f.storage_type = e.dataFork.storage_type; + f.key_pointer = e.dataFork.key_block; + f.eof = e.dataFork.eof; + f.blocks_used = e.dataFork.blocks_used; + } + else + { + f.storage_type = e.resourceFork.storage_type; + f.key_pointer = e.resourceFork.key_block; + f.eof = e.resourceFork.eof; + f.blocks_used = e.resourceFork.blocks_used; + } + + if (ee) *ee = e; + + return 0; + +} + + +int Disk::Read(unsigned block, void *buffer) +{ + + if (block > _blocks) return -P8_INVALID_BLOCK; + + _device->read(block, buffer); + + return 1; +} + + +void *Disk::ReadFile(const FileEntry &f, unsigned fork, uint32_t *size, int *error) +{ + +#define SET_ERROR(x) if (error) *error = (x) +#define SET_SIZE(x) if (size) *size = (x) + + + SET_ERROR(0); + SET_SIZE(0); + + if (fork != P8_DATA_FORK && fork != P8_RESOURCE_FORK) + { + SET_ERROR(-P8_INVALID_FORK); + return NULL; + } + + uint8_t buffer[BLOCK_SIZE]; + int ok; + uint32_t eof; + uint32_t alloc; + unsigned blocks; + unsigned storage_type; + unsigned key_block; + + switch(f.storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + if (fork != P8_DATA_FORK) + { + SET_ERROR(1); + return NULL; + } + storage_type = f.storage_type; + eof = f.eof; + key_block = f.key_pointer; + break; + + case EXTENDED_FILE: + { + ok = Read(f.key_pointer, buffer); + if (ok < 0) + { + SET_ERROR(ok); + return NULL; + } + + ExtendedEntry entry; + entry.Load(buffer); + + if (fork == P8_DATA_FORK) + { + storage_type = entry.dataFork.storage_type; + eof = entry.dataFork.eof; + key_block = entry.dataFork.key_block; + } + else + { + storage_type = entry.resourceFork.storage_type; + eof = entry.resourceFork.eof; + key_block = entry.resourceFork.key_block; + } + } + break; + default: + SET_ERROR(-P8_INVALID_STORAGE_TYPE); + return NULL; + } + + if (eof == 0) + { + SET_ERROR(1); + return NULL; + } + + blocks = (eof + BLOCK_SIZE - 1) >> 9; + alloc = (eof + BLOCK_SIZE - 1) & (~BLOCK_SIZE); + uint8_t* data = new uint8_t[alloc]; + + + switch (storage_type) + { + case SEEDLING_FILE: + ok = Read(key_block, data); + break; + case SAPLING_FILE: + ok = ReadIndex(f.key_pointer, buffer, 1, 0, blocks); + break; + case TREE_FILE: + ok = ReadIndex(f.key_pointer, buffer, 2, 0, blocks); + break; + + default: + ok = false; + } + + if (ok < 0) + { + SET_ERROR(ok); + delete[] data; + return NULL; + } + + + bzero(data + eof, alloc - eof); + SET_SIZE(eof); + + return data; +} + + +int Disk::ReadFile(const FileEntry &f, void *buffer) +{ + int blocks = (f.eof + BLOCK_SIZE - 1) >> 9; + int ok; + + switch(f.storage_type) + { + case TREE_FILE: + ok = ReadIndex(f.key_pointer, buffer, 2, 0, blocks); + break; + case SAPLING_FILE: + ok = ReadIndex(f.key_pointer, buffer, 1, 0, blocks); + break; + case SEEDLING_FILE: + ok = Read(f.key_pointer, buffer); + break; + + default: + return -P8_INVALID_STORAGE_TYPE; + } + + if (ok >= 0) + { + bzero((uint8_t *)buffer + f.eof, (blocks << 9) - f.eof); + } + + return ok; +} + + +int Disk::ReadIndex(unsigned block, void *buffer, unsigned level, off_t offset, unsigned blocks) +{ + if (level == 0) + { + // data level + if (block == 0) // sparse file + { + bzero(buffer, BLOCK_SIZE); + return 1; + } + return Read(block, buffer); + } + + + unsigned blockCount; + unsigned readSize; + unsigned first; + //unsigned last; + + + switch(level) + { + case 1: + first = (offset >> 9) & 0xff; + blockCount = 1; + readSize = BLOCK_SIZE; + offset = 0; + break; + case 2: + first = (offset >> 17) & 0xff; + blockCount = 256; + readSize = BLOCK_SIZE << 8; + offset &= 0x1ffff; + break; + default: + return -P8_INTERNAL_ERROR; + } + + + + + int ok; + uint8_t key[BLOCK_SIZE]; + + if (block) // not sparse. + { + ok = Read(block, key); + if (ok < 0 ) return ok; + } + else + { + // sparse -- zero it out so code below works w/o special cases. + bzero(key, BLOCK_SIZE); + } + + for (unsigned i = first; blocks; i++) + { + // block pointers are split up since 8-bit indexing is limited to 256. + unsigned newBlock = (key[i]) | (key[256 + i] << 8); + + unsigned b = std::min(blocks, blockCount); + + ok = ReadIndex(newBlock, buffer, level - 1, offset, b); + if (ok < 0) return ok; + offset = 0; + buffer = ((char *)buffer) + readSize; + blocks -= b; + } + return blocks; +} + + + +int Disk::ReadVolume(VolumeEntry *volume, std::vector *files) +{ + if (files) files->resize(0); + + uint8_t buffer[BLOCK_SIZE]; + int ok; + unsigned prev; + unsigned next; + + uset blocks; + + unsigned block = 2; + blocks.insert(block); + ok = Read(block, buffer); + + if (ok < 0) return ok; + + prev = Read16(&buffer[0x00]); + next = Read16(&buffer[0x02]); + + VolumeEntry v; + v.Load(buffer + 0x04); + + if (v.storage_type != VOLUME_HEADER) return -P8_INVALID_STORAGE_TYPE; + + if (volume) *volume = v; + + if (!files) return 1; + + if (v.file_count) + { + files->reserve(v.file_count); + //files->resize(v.file_count); + + //unsigned count = 0; + unsigned index = 1; // skip the header. + for(;;) + { + // + if ( (buffer[0x04 + v.entry_length * index] >> 4) != DELETED_FILE) + { + unsigned offset = v.entry_length * index + 0x4; + FileEntry f; + f.Load(buffer + offset); + f.address = (block << 9) + offset; + + files->push_back(f); + //if (++count == v.file_count) break; + } + index++; + if (index >= v.entries_per_block) + { + if (!next) break; // all done! + + + if (blocks.insert(next).second == false) + { + return -P8_CYCLICAL_BLOCK; + } + + ok = Read(next, buffer); + if (ok < 0) return ok; + block = next; + + prev = Read16(&buffer[0x00]); + next = Read16(&buffer[0x02]); + + index = 0; + } + } + } + + return 1; +} + + +int Disk::ReadDirectory(unsigned block, SubdirEntry *dir, std::vector *files) +{ + if (files) files->resize(0); + + uint8_t buffer[BLOCK_SIZE]; + int ok; + unsigned prev; + unsigned next; + + // keep a list of blocks to prevent cyclical problems. + uset blocks; + + blocks.insert(block); + + ok = Read(block, buffer); + + if (ok < 0) return ok; + + prev = Read16(&buffer[0x00]); + next = Read16(&buffer[0x02]); + + SubdirEntry v; + v.Load(buffer + 0x04); + + if (v.storage_type != SUBDIR_HEADER) return -P8_INVALID_STORAGE_TYPE; + + + if (dir) *dir = v; + + if (!files) return 1; + + if (v.file_count) + { + files->reserve(v.file_count); + //files->resize(v.file_count); + + //unsigned count = 0; + unsigned index = 1; // skip the header. + for(;;) + { + // + if ( (buffer[0x04 + v.entry_length * index] >> 4) != DELETED_FILE) + { + unsigned offset = v.entry_length * index + 0x4; + FileEntry f; + f.Load(buffer + offset); + f.address = (block << 9) + offset; + + files->push_back(f); + + //if (++count == v.file_count) break; + } + index++; + if (index >= v.entries_per_block) + { + if (!next) break; // all done! + + + if (blocks.insert(next).second == false) + { + return -P8_CYCLICAL_BLOCK; + } + + ok = Read(next, buffer); + if (ok < 0) return ok; + block = next; + + + prev = Read16(&buffer[0x00]); + next = Read16(&buffer[0x02]); + + index = 0; + } + } + } + + return 1; +} diff --git a/ProDOS/Disk.h b/ProDOS/Disk.h new file mode 100644 index 0000000..683defc --- /dev/null +++ b/ProDOS/Disk.h @@ -0,0 +1,83 @@ +/* + * Disk.h + * ProFUSE + * + * Created by Kelvin Sherlock on 12/18/08. + * + */ +#ifndef __DISK_H__ +#define __DISK_H__ + +#include +#include + +#include + +#include +#include + +#include + + +enum { + P8_OK = 0, + P8_INTERNAL_ERROR, + P8_INVALID_FORK, + P8_INVALID_BLOCK, + P8_INVALID_STORAGE_TYPE, + P8_CYCLICAL_BLOCK + +}; + +enum { + P8_DATA_FORK = 0, + P8_RESOURCE_FORK = 1 +}; + + +/* flags */ +enum { + P8_DOS_ORDER = 1, + P8_2MG = 2, + P8_DC42 = 4 + +}; + +class Disk; +typedef std::tr1::shared_ptr DiskPointer; + +class Disk { + +public: + ~Disk(); + + //static Disk *Open2MG(const char *file); + static DiskPointer OpenFile(Device::BlockDevicePointer device); + + + int Normalize(FileEntry &f, unsigned fork, ExtendedEntry *ee = NULL); + + int Read(unsigned block, void *buffer); + int ReadIndex(unsigned block, void *buffer, unsigned level, off_t offset, unsigned blocks); + + int ReadFile(const FileEntry &f, void *buffer); + + void *ReadFile(const FileEntry &f, unsigned fork, uint32_t *size, int * error); + + + int ReadVolume(VolumeEntry *volume, std::vector *files); + int ReadDirectory(unsigned block, SubdirEntry *dir, std::vector *files); + +private: + Disk(); + Disk(Device::BlockDevicePointer device); + + unsigned _blocks; + + Device::BlockDevicePointer _device; +}; + + + +#endif + diff --git a/ProDOS/File.cpp b/ProDOS/File.cpp new file mode 100644 index 0000000..985d8a4 --- /dev/null +++ b/ProDOS/File.cpp @@ -0,0 +1,230 @@ +/* + * File.cpp + * ProFUSE + * + * Created by Kelvin Sherlock on 12/18/08. + * + */ + +#include +#include +#include + +#include "common.h" +#include +#include +#include +#include + +using namespace LittleEndian; + + +bool FileEntry::Load(const void *data) +{ + const uint8_t *cp = (const uint8_t *)data; + + address = 0; + + storage_type = cp[0x00] >> 4; + name_length = cp[0x00] & 0x0f; + + memcpy(file_name, &cp[0x01], name_length); + file_name[name_length] = 0; + + file_type = cp[0x10]; + + key_pointer = Read16(&cp[0x11]); + + blocks_used = Read16(&cp[0x13]); + + eof = Read24(&cp[0x15]); + + creation = ProDOS::DateTime(Read16(&cp[0x18]), Read16(&cp[0x1a])); + + //version = cp[0x1c]; + //min_version = cp[0x1d]; + + unsigned xcase = Read16(&cp[0x1c]); + if (xcase & 0x8000) + { + // gsos technote #8 + unsigned mask = 0x4000; + for (unsigned i = 0; i < name_length; i++) + { + if (xcase & mask) file_name[i] = tolower(file_name[i]); + mask = mask >> 1; + } + } + + + + access = cp[0x1e]; + + + aux_type = Read16(&cp[0x1f]); + + last_mod = ProDOS::DateTime(Read16(&cp[0x21]), Read16(&cp[0x23])); + + header_pointer = Read16(&cp[0x25]); + + return true; +} + + + + +bool ExtendedEntry::Load(const void *data) +{ + const uint8_t *cp = (const uint8_t *)data; + + //prodos technote #25. + // offset 0 - mini entry for data fork + + dataFork.storage_type = cp[0x00] & 0x0f; + dataFork.key_block = Read16(&cp[0x01]); + dataFork.blocks_used = Read16(&cp[0x03]); + dataFork.eof = Read24(&cp[0x05]); + + // offset 256 - mini entry for resource fork. + + resourceFork.storage_type = cp[256 + 0x00] & 0x0f; + resourceFork.key_block = Read16(&cp[256 + 0x01]); + resourceFork.blocks_used = Read16(&cp[256 + 0x03]); + resourceFork.eof = Read24(&cp[256 + 0x05]); + + // xFInfo may be missing. + bzero(FInfo, sizeof(FInfo)); + bzero(xFInfo, sizeof(xFInfo)); + + // size must be 18. + unsigned size; + unsigned entry; + + for (unsigned i = 0; i < 2; i++) + { + unsigned ptr = i == 0 ? 8 : 26; + size = cp[ptr]; + if (size != 18) continue; + entry = cp[ptr + 1]; + switch(entry) + { + case 1: + memcpy(FInfo, &cp[ptr + 2], 16); + break; + case 2: + memcpy(xFInfo, &cp[ptr + 2], 16); + break; + } + } + // + return true; +} + + + +bool VolumeEntry::Load(const void *data) +{ + const uint8_t *cp = (const uint8_t *)data; + + //prev_block = load16(&cp[0x00]); + //next_block = load16(&cp[0x02]); + + storage_type = cp[0x00] >> 4; + name_length = cp[0x00] & 0x0f; + + memcpy(volume_name, &cp[0x01], name_length); + volume_name[name_length] = 0; + + // 0x14--0x1b reserved + + creation = ProDOS::DateTime(Read16(&cp[0x18]), Read16(&cp[0x1a])); + last_mod = ProDOS::DateTime(Read16(&cp[0x12]), Read16(&cp[0x14])); + + if (last_mod == 0) last_mod = creation; + + //version = cp[0x1c]; + //min_version = cp[0x1d]; + + unsigned xcase = Read16(&cp[0x16]); + if (xcase & 0x8000) + { + // gsos technote #8 + unsigned mask = 0x4000; + for (unsigned i = 0; i < name_length; i++) + { + if (xcase & mask) volume_name[i] = tolower(volume_name[i]); + mask = mask >> 1; + } + } + + + access = cp[0x1e]; + + entry_length = cp[0x1f]; + + entries_per_block = cp[0x20]; + + file_count = Read16(&cp[0x21]); + + bit_map_pointer = Read16(&cp[0x23]); + + total_blocks = Read16(&cp[0x25]); + + return true; +} + + + + +bool SubdirEntry::Load(const void *data) +{ + const uint8_t *cp = (const uint8_t *)data; + + + //prev_block = load16(&cp[0x00]); + //next_block = load16(&cp[0x02]); + + storage_type = cp[0x00] >> 4; + name_length = cp[0x00] & 0x0f; + + memcpy(subdir_name, &cp[0x01], name_length); + subdir_name[name_length] = 0; + + // 0x14 should be $14. + + // 0x145-0x1b reserved + + creation = ProDOS::DateTime(Read16(&cp[0x18]), Read16(&cp[0x1a])); + + //version = cp[0x1c]; + //min_version = cp[0x1d]; + /* + unsigned xcase = load16(&cp[0x1c]); + if (xcase & 0x8000) + { + // gsos technote #8 + unsigned mask = 0x4000; + for (unsigned i = 0; i < name_length; i++) + { + if (xcase & mask) subdir_name[i] = tolower(subdir_name[i]); + mask = mask >> 1; + } + } + */ + + access = cp[0x1e]; + + entry_length = cp[0x1f]; + + entries_per_block = cp[0x20]; + + file_count = Read16(&cp[0x21]); + + parent_pointer = Read16(&cp[0x23]); + + parent_entry = cp[0x25]; + + parent_entry_length = cp[0x26]; + + return true; +} diff --git a/ProDOS/File.h b/ProDOS/File.h new file mode 100644 index 0000000..4d76c86 --- /dev/null +++ b/ProDOS/File.h @@ -0,0 +1,133 @@ +/* + * File.h + * ProFUSE + * + * Created by Kelvin Sherlock on 12/18/08. + * + */ + +#ifndef __PRODOS_FILE_H__ +#define __PRODOS_FILE_H__ + +#include +#include + +enum { + DELETED_FILE = 0, + SEEDLING_FILE = 1, + SAPLING_FILE = 2, + TREE_FILE = 3, + PASCAL_FILE = 4, + EXTENDED_FILE = 5, + DIRECTORY_FILE = 0x0d, + SUBDIR_HEADER = 0x0e, + VOLUME_HEADER = 0x0f +}; + + +enum { + FILE_ENTRY_SIZE = 0x27, +}; + + +enum { + ACCESS_DESTROY = 0x80, + ACCESS_RENAME = 0x40, + ACCESS_MODIFIED = 0x20, + ACCESS_WRITE = 0x02, + ACCRESS_READ = 0x01 +}; + + +class FileEntry { +public: + + bool Load(const void *data); + + unsigned storage_type; + unsigned name_length; + char file_name[15 + 1]; + unsigned file_type; + unsigned key_pointer; + unsigned blocks_used; + uint32_t eof; + time_t creation; + //unsigned version; + //unsigned min_version; + unsigned access; + unsigned aux_type; + time_t last_mod; + unsigned header_pointer; + + uint32_t address; +}; + + + +struct MiniEntry { + + unsigned storage_type; + unsigned key_block; + unsigned blocks_used; + uint32_t eof; + +}; + +class ExtendedEntry { +public: + + bool Load(const void *data); + + MiniEntry dataFork; + MiniEntry resourceFork; + + uint8_t FInfo[16]; + uint8_t xFInfo[16]; +}; + + +class VolumeEntry { +public: + + bool Load(const void *data); + + unsigned storage_type; + unsigned name_length; + char volume_name[15+1]; + time_t creation; + time_t last_mod; + //unsigned version; + //unsigned min_version; + unsigned access; + unsigned entry_length; + unsigned entries_per_block; + unsigned file_count; + unsigned bit_map_pointer; + unsigned total_blocks; + + friend class DirIter; + +}; + +class SubdirEntry { +public: + + bool Load(const void *data); + + unsigned storage_type; + unsigned name_length; + char subdir_name[15+1]; + time_t creation; + //unsigned version; + //unsigned min_version; + unsigned access; + unsigned entry_length; + unsigned entries_per_block; + unsigned file_count; + unsigned parent_pointer; + unsigned parent_entry; + unsigned parent_entry_length; +}; + + +#endif diff --git a/ProDOS/Makefile b/ProDOS/Makefile deleted file mode 100644 index 84af29e..0000000 --- a/ProDOS/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -CC = g++ -CPPFLAGS += -g -Wall -I../ - - -all : DateTime.o - -DateTime.o : DateTime.cpp DateTime.h diff --git a/ProDOS/common.h b/ProDOS/common.h new file mode 100644 index 0000000..615bfbc --- /dev/null +++ b/ProDOS/common.h @@ -0,0 +1,18 @@ +/* + * common.h + * ProFUSE + * + * Created by Kelvin Sherlock on 12/20/08. + * + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include + +#define BLOCK_SIZE 512 + + +#endif + diff --git a/bin/profuse.cpp b/bin/profuse.cpp new file mode 100644 index 0000000..62dff80 --- /dev/null +++ b/bin/profuse.cpp @@ -0,0 +1,374 @@ +/* + * main.cpp + * ProFUSE + * + * Created by Kelvin Sherlock on 12/24/08. + * + */ +/* + +#define __FreeBSD__ 10 +#define _FILE_OFFSET_BITS 64 +#define __DARWIN_64_BIT_INO_T 1 +#define _REENTRANT +#define _POSIX_C_SOURCE 200112L +*/ + +#include +#include +#include +#include + +#include +#include + +#include + +#include + + +#include "profuse.h" + + + +using std::vector; +using std::string; +using std::tr1::shared_ptr; + + +/* + * globals variables. + * + */ + +std::string fDiskImage; + + +DiskPointer disk; +VolumeEntry volume; + +bool validProdosName(const char *name) +{ + // OS X looks for hidden files that don't exist (and aren't legal prodos names) + // most are not legal prodos names, so this filters them out easily. + + // [A-Za-z][0-9A-Za-z.]{0,14} + + if (!isalpha(*name)) return false; + + unsigned i; + for(i = 1; name[i]; i++) + { + char c = name[i]; + if (c == '.' || isalnum(c)) continue; + + return false; + + } + + return i < 16; +} + + + + + + + +static struct fuse_lowlevel_ops prodos_oper; + +enum { + PRODOS_OPT_HELP, + PRODOS_OPT_VERSION, + PRODOS_OPT_WRITE, + PRODOS_OPT_FORMAT, + PRODOS_OPT_VERBOSE +}; + +struct options { + char *format; + int readOnly; + int readWrite; + int verbose; + int debug; + +} options; + +#define PRODOS_OPT_KEY(T, P, V) {T, offsetof(struct options, P), V} + + +static struct fuse_opt prodos_opts[] = { + FUSE_OPT_KEY("-h", PRODOS_OPT_HELP), + FUSE_OPT_KEY("--help", PRODOS_OPT_HELP), + + FUSE_OPT_KEY("-V", PRODOS_OPT_VERSION), + FUSE_OPT_KEY("--version", PRODOS_OPT_VERSION), + + PRODOS_OPT_KEY("-v", verbose, 1), + + PRODOS_OPT_KEY("-w", readWrite, 1), + PRODOS_OPT_KEY("rw", readWrite, 1), + + PRODOS_OPT_KEY("-d", debug, 1), + + PRODOS_OPT_KEY("--format=%s", format, 0), + PRODOS_OPT_KEY("format=%s", format, 0), + {0, 0, 0} +}; + +static void usage() +{ + fprintf(stderr, "profuse [options] disk_image [mountpoint]\n" + + "Options:\n" + " -d debug\n" + " -r readonly\n" + " -w mount writable [not yet]\n" + " -v verbose\n" + " --format=format specify the disk image format. Valid values are:\n" + " dc42 DiskCopy 4.2 Image\n" + " davex Davex Disk Image\n" + " sdk ShrinkIt Disk Image\n" + " 2img Universal Disk Image\n" + " do DOS Order Disk Image\n" + " po ProDOS Order Disk Image (default)\n" + " -o opt1,opt2... other mount parameters.\n" + + ); +} + +static int prodos_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) +{ + switch(key) + { + case PRODOS_OPT_HELP: + usage(); + exit(0); + break; + + case PRODOS_OPT_VERSION: + // TODO + exit(1); + break; + + case FUSE_OPT_KEY_NONOPT: + // first arg is the disk image. + if (fDiskImage.empty()) + { + fDiskImage = arg; + return 0; + } + return 1; + } + return 1; +} + + +#ifdef __APPLE__ + +// create a dir in /Volumes/diskname. +bool make_mount_dir(string name, string &path) +{ + path = ""; + + if (name.find('/') != string::npos) return false; + if (name.find('\\') != string::npos) return false; + if (name.find(':') != string::npos) return false; + + path = ""; + path = "/Volumes/" + name; + rmdir(path.c_str()); + if (mkdir(path.c_str(), 0777) == 0) return true; + + for (unsigned i = 0; i < 26; i++) + { + path = "/Volumes/" + name + " " + (char)('a' + i); + + rmdir(path.c_str()); + if (mkdir(path.c_str(), 0777) == 0) return true; + + + + } + + path = ""; + return false; + +} + +#endif + +int main(int argc, char *argv[]) +{ + + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_chan *ch; + char *mountpoint = NULL; + int err = -1; + struct options options; + + unsigned format = 0; + + int foreground = false; + int multithread = false; + + +#if __APPLE__ + string mountpath; +#endif + + + + + std::memset(&prodos_oper, 0, sizeof(prodos_oper)); + + std::memset(&options, 0, sizeof(options)); + + + prodos_oper.listxattr = prodos_listxattr; + prodos_oper.getxattr = prodos_getxattr; + + prodos_oper.opendir = prodos_opendir; + prodos_oper.releasedir = prodos_releasedir; + prodos_oper.readdir = prodos_readdir; + + prodos_oper.lookup = prodos_lookup; + prodos_oper.getattr = prodos_getattr; + + prodos_oper.open = prodos_open; + prodos_oper.release = prodos_release; + prodos_oper.read = prodos_read; + + prodos_oper.statfs = prodos_statfs; + + + // scan the argument list, looking for the name of the disk image. + if (fuse_opt_parse(&args, &options , prodos_opts, prodos_opt_proc) == -1) + exit(1); + + if (fDiskImage.empty()) + { + usage(); + exit(1); + } + + // default prodos-order disk image. + if (options.format) + { + format = Device::BlockDevice::ImageType(options.format); + if (!format) + std::fprintf(stderr, "Warning: Unknown image type ``%s''\n", options.format); + } + + try { + Device::BlockDevicePointer device; + + device = Device::BlockDevice::Open(fDiskImage.c_str(), File::ReadOnly, format); + + if (!device) + { + std::fprintf(stderr, "Error: Unknown or unsupported device type.\n"); + exit(1); + } + + disk = Disk::OpenFile(device); + + if (!disk) + { + fprintf(stderr, "Unable to mount disk %s\n", fDiskImage.c_str()); + exit(1); + } + } + + catch (ProFUSE::Exception &e) + { + std::fprintf(stderr, "%s\n", e.what()); + std::fprintf(stderr, "%s\n", e.errorString()); + return -1; + } + + + + disk->ReadVolume(&volume, NULL); + +#ifdef __APPLE__ + { + // Macfuse supports custom volume names (displayed in Finder) + string str="-ovolname="; + str += volume.volume_name; + fuse_opt_add_arg(&args, str.c_str()); + } +#endif + + + // use 512byte blocks. + #if __APPLE__ + fuse_opt_add_arg(&args, "-oiosize=512"); + #endif + + do { + + if (fuse_parse_cmdline(&args, &mountpoint, NULL, NULL) == -1) break; + +#ifdef __APPLE__ + + if (mountpoint == NULL || *mountpoint == 0) + { + if (make_mount_dir(volume.volume_name, mountpath)) + mountpoint = (char *)mountpath.c_str(); + } + +#endif + + foreground = options.debug; + + + if (mountpoint == NULL || *mountpoint == 0) + { + fprintf(stderr, "no mount point\n"); + break; + } + + + + if ( (ch = fuse_mount(mountpoint, &args)) != NULL) + { + struct fuse_session *se; + + se = fuse_lowlevel_new(&args, &prodos_oper, sizeof(prodos_oper), NULL); + if (se != NULL) do { + + err = fuse_daemonize(foreground); + if (err < 0 ) break; + + err = fuse_set_signal_handlers(se); + if (err < 0) break; + + + fuse_session_add_chan(se, ch); + + if (multithread) err = fuse_session_loop_mt(se); + else err = fuse_session_loop(se); + + fuse_remove_signal_handlers(se); + fuse_session_remove_chan(ch); + + } while (false); + + if (se) fuse_session_destroy(se); + fuse_unmount(mountpoint, ch); + } + + } while (false); + + fuse_opt_free_args(&args); + + disk.reset(); + + +#ifdef __APPLE__ + if (mountpath.size()) rmdir(mountpath.c_str()); +#endif + + return err ? 1 : 0; +} diff --git a/bin/profuse.h b/bin/profuse.h new file mode 100644 index 0000000..999c213 --- /dev/null +++ b/bin/profuse.h @@ -0,0 +1,62 @@ +/* + * profuse.h + * profuse + * + * Created by Kelvin Sherlock on 1/23/2009. + * + */ + +#ifndef __PROFUSE_H__ +#define __PROFUSE_H__ + +#include +#include +#include + + +#ifdef __APPLE__ +#define __FreeBSD__ 10 +#define __DARWIN_64_BIT_INO_T 1 +#endif + +#define _FILE_OFFSET_BITS 64 +#define FUSE_USE_VERSION 27 + +#include +#include + + +#undef ERROR +#define ERROR(cond,errno) if ( (cond) ){ fuse_reply_err(req, errno); return; } + + +extern DiskPointer disk; + +bool validProdosName(const char *name); + +// xattr +void prodos_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size, uint32_t off); +void prodos_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size); +void prodos_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size); + +//dirent +void prodos_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +void prodos_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +void prodos_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); + +// stat +void prodos_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +void prodos_lookup(fuse_req_t req, fuse_ino_t parent, const char *name); + +void prodos_statfs(fuse_req_t req, fuse_ino_t ino); + + +// file io. +void prodos_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +void prodos_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +void prodos_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); + + + +#endif + diff --git a/bin/profuse_dirent.cpp b/bin/profuse_dirent.cpp new file mode 100644 index 0000000..a124739 --- /dev/null +++ b/bin/profuse_dirent.cpp @@ -0,0 +1,130 @@ +/* + * profuse_dirent.cpp + * profuse + * + * Created by Kelvin Sherlock on 1/23/2009. + * + */ + +#include "profuse.h" + +#include +#include + +#include +#include +#include + +using std::string; +using std::vector; + +#pragma mark Directory Functions + +/* + * when the directory is opened, we load the volume/directory and store the FileEntry vector into + * fi->fh. + * + */ +void prodos_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + fprintf(stderr, "opendir: %u\n", (unsigned)ino); + // verify it's a directory/volume here? + + + uint8_t buffer[BLOCK_SIZE]; + vector files; + bool ok; + + + ok = disk->Read(ino == 1 ? 2 : ino >> 9, buffer); + ERROR(ok < 0, EIO) + + + if (ino == 1) + { + VolumeEntry v; + v.Load(buffer + 0x04); + + ok = disk->ReadVolume(&v, &files); + + ERROR(ok < 0, EIO) + } + else + { + + FileEntry e; + e.Load(buffer + (ino & 0x1ff)); + + ERROR(e.storage_type != DIRECTORY_FILE, ENOTDIR) + + ok = disk->ReadDirectory(e.key_pointer, NULL, &files); + + ERROR(ok < 0, EIO); + } + + // copy the vector contents to a vector *. + vector *fp = new vector(); + files.swap(*fp); + + fi->fh = (uint64_t)fp; + fuse_reply_open(req, fi); +} + +void prodos_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + fprintf(stderr,"releasedir: %u\n", (unsigned)ino); + vector *files = (vector *)fi->fh; + + if (files) delete files; + + fuse_reply_err(req, 0); +} + +void prodos_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) +{ + vector *files = (vector *)fi->fh; + struct stat st; + + fprintf(stderr, "readdir %u %u %u\n", (unsigned)ino, (unsigned)size, (unsigned)off); + + // TODO -- add "." and ".." entries... + + + // if the offset >= number of entries, get out. + if (!files || files->size() <= off) + { + fprintf(stderr, "fuse_reply_buf(req, NULL, 0)\n"); + fuse_reply_buf(req, NULL, 0); + return; + } + + + // now some dirent info... + + bzero(&st, sizeof(st)); + // only mode and ino are used. + + char *buffer = new char[size]; + + unsigned count = files->size(); + unsigned current_size = 0; + for (unsigned i = off; i < count; ++i) + { + FileEntry &f = (*files)[i]; + + st.st_mode = f.storage_type == DIRECTORY_FILE ? S_IFDIR | 0555 : S_IFREG | 0444; + st.st_ino = f.address; + + unsigned entry_size = fuse_add_direntry(req, NULL, 0, f.file_name, NULL, 0); + if (entry_size + current_size >= size) break; + + + fuse_add_direntry(req, (char *)buffer + current_size, size, f.file_name, &st, i + 1); + current_size += entry_size; + + } + + fuse_reply_buf(req, buffer, current_size); + delete []buffer; +} + diff --git a/bin/profuse_file.cpp b/bin/profuse_file.cpp new file mode 100644 index 0000000..69d5125 --- /dev/null +++ b/bin/profuse_file.cpp @@ -0,0 +1,136 @@ +/* + * profuse_file.cpp + * profuse + * + * Created by Kelvin Sherlock on 1/23/2009. + * + */ + +#include "profuse.h" +#include +#include + + +#pragma mark Read Functions + +void prodos_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + fprintf(stderr, "open: %u\n", (unsigned)ino); + + + uint8_t buffer[BLOCK_SIZE]; + int ok; + + FileEntry *e = NULL; + + ERROR(ino == 1, EISDIR) + + ok = disk->Read(ino >> 9, buffer); + ERROR(ok < 0, EIO) + + e = new FileEntry(); + e->Load(buffer + (ino & 0x1ff)); + + if (e->storage_type == EXTENDED_FILE) + { + ok = disk->Normalize(*e, 0); + + if (ok < 0) + { + delete e; + ERROR(true, EIO) + } + } + + // EXTENDED_FILE already handled (it would be an error here.) + switch(e->storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + break; + //case PASCAL_FILE: //? + case DIRECTORY_FILE: + delete e; + ERROR(true, EISDIR) + break; + default: + ERROR(true, EIO) + } + + if ( (fi->flags & O_ACCMODE) != O_RDONLY) + { + delete e; + ERROR(true, EACCES); + } + fi->fh = (uint64_t)e; + + fuse_reply_open(req, fi); +} + +void prodos_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + fprintf(stderr, "release: %u\n", (unsigned)ino); + + FileEntry *e = (FileEntry *)fi->fh; + + if (e) delete e; + + fuse_reply_err(req, 0); + +} + +void prodos_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) +{ + fprintf(stderr, "read: %u %u %u\n", (unsigned)ino, (unsigned)size, (unsigned)off); + + FileEntry *e = (FileEntry *)fi->fh; + + ERROR(e == NULL, EIO) + + if (off >= e->eof) + { + fuse_reply_buf(req, NULL, 0); + return; + } + + + unsigned level = 0; + switch(e->storage_type) + { + case TREE_FILE: + level = 2; + break; + case SAPLING_FILE: + level = 1; + break; + case SEEDLING_FILE: + level = 0; + break; + } + + // currently, reading is done on a block basis. + // experimentally, fuse reads the entire file + // this may not hold for larger files. + + + // TODO -- error if size + off > eof. + + unsigned blocks = (size + (off & 0x1ff) + BLOCK_SIZE - 1) >> 9; + int ok; + uint8_t *buffer = new uint8_t[blocks << 9]; + + fprintf(stderr, "ReadIndex(%x, buffer, %x, %x, %x)\n", e->key_pointer, level, (int)off, (int)blocks); + + ok = disk->ReadIndex(e->key_pointer, buffer, level, off, blocks); + if (ok < 0) + { + fuse_reply_err(req, EIO); + } + else + { + fuse_reply_buf(req, (const char *)buffer + (off & 0x1ff), size); + } + + delete []buffer; +} diff --git a/bin/profuse_stat.cpp b/bin/profuse_stat.cpp new file mode 100644 index 0000000..68e8546 --- /dev/null +++ b/bin/profuse_stat.cpp @@ -0,0 +1,252 @@ +/* + * prodos_stat.cpp + * profuse + * + * Created by Kelvin Sherlock on 1/23/2009. + * + */ + + +#pragma mark Stat Functions + +#include "profuse.h" +#include +#include +#include + +#include + +#include +#include + +using std::vector; + +int prodos_stat(FileEntry& e, struct stat *st) +{ + uint8_t buffer[BLOCK_SIZE]; + int ok; + + + if (e.storage_type == EXTENDED_FILE) + { + ok = disk->Normalize(e, 0); + if (ok < 0) return ok; + } + + st->st_blksize = BLOCK_SIZE; + + st->st_ctime = e.creation; +#ifdef HAVE_STAT_BIRTHTIME + st->st_birthtime = e.creation; +#endif + + st->st_mtime = e.last_mod; + st->st_atime = e.last_mod; + + + st->st_nlink = 1; + st->st_mode = 0444 | S_IFREG; + st->st_size = e.eof; + + + if (e.storage_type == DIRECTORY_FILE) + { + ok = disk->Read(e.key_pointer, buffer); + if (ok < 0) return -1; + + SubdirEntry se; + se.Load(buffer + 0x04); + + if (se.storage_type != SUBDIR_HEADER) return -1; + + st->st_mode = S_IFDIR | 0555; + st->st_size = BLOCK_SIZE; + st->st_nlink = se.file_count + 1; + + return 0; + } + + + switch(e.storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + //case PASCAL_FILE: + break; + + default: + return -1; + } + + return 0; +} + +int prodos_stat(const VolumeEntry &v, struct stat *st) +{ + + if (v.storage_type != VOLUME_HEADER) return -1; + + st->st_mode = S_IFDIR | 0555; + st->st_ctime = v.creation; + +#ifdef HAVE_STAT_BIRTHTIME + st->st_birthtime = v.creation; +#endif + st->st_mtime = v.last_mod; + st->st_atime = v.last_mod; + + st->st_nlink = v.file_count + 1; + st->st_size = BLOCK_SIZE; + st->st_blksize = BLOCK_SIZE; + + + return 1; +} + + +void prodos_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + uint8_t buffer[BLOCK_SIZE]; + struct stat st; + int ok; + + fprintf(stderr, "get_attr %u\n", (unsigned)ino); + + bzero(&st, sizeof(st)); + + /* + * ino 1 is the volume header. Others are pointers. + * + */ + + + ok = disk->Read(ino == 1 ? 2 : ino >> 9, buffer); + ERROR(ok < 0, EIO) + + // ino 1 is the volume header. + if (ino == 1) + { + VolumeEntry v; + v.Load(buffer + 0x04); + ok = prodos_stat(v, &st); + ERROR(ok < 0, EIO); + + st.st_ino = ino; + + fuse_reply_attr(req, &st, 0.0); + return; + } + else + { + + + FileEntry e; + e.Load(buffer + (ino & 0x1ff)); + ok = prodos_stat(e, &st); + + ERROR(ok < 0, EIO); + + st.st_ino = ino; + + fuse_reply_attr(req, &st, 0.0); // + } +} + + +// TODO -- add Disk::Lookup support so we don't have to parse the entire dir header. +// TODO -- add caching. +void prodos_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + uint8_t buffer[BLOCK_SIZE]; + struct fuse_entry_param entry; + int ok; + vector files; + + + fprintf(stderr, "lookup: %u %s\n", (unsigned)parent, name); + + ERROR(!validProdosName(name), ENOENT) + + ok = disk->Read(parent == 1 ? 2 : parent >> 9, buffer); + ERROR(ok < 0, EIO) + + bzero(&entry, sizeof(entry)); + + entry.attr_timeout = 0.0; + entry.entry_timeout = 0.0; + + // get the file list + // TODO -- Disk::look-up-one-file + if (parent == 1) + { + VolumeEntry v; + ok = disk->ReadVolume(&v, &files); + ERROR(ok < 0, EIO) + } + else + { + FileEntry e; + e.Load(buffer + (parent & 0x1ff)); + ERROR(e.storage_type != DIRECTORY_FILE, ENOENT); + + ok = disk->ReadDirectory(e.key_pointer, NULL, &files); + ERROR(ok < 0, EIO) + } + // ok, now go through the file list and look for a (case insensitive) match. + + + ok = -1; + unsigned name_length = strlen(name); + + for(vector::iterator iter = files.begin(); iter != files.end(); ++iter) + { + FileEntry& f = *iter; + if ( (f.name_length == name_length) && (strcasecmp(name, f.file_name) == 0)) + { + ok = prodos_stat(f, &entry.attr); + fprintf(stderr, "stat %s %x (%x %x) %d\n", f.file_name, f.address, f.address >> 9, f.address & 0x1ff, ok); + entry.ino = f.address; + entry.attr.st_ino = f.address; + break; + } + } + + ERROR(ok < 0, ENOENT); + + fprintf(stderr, "file found!\n"); + + fuse_reply_entry(req, &entry); +} + + + + +void prodos_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct statvfs vst; + + VolumeEntry volume; + + + disk->ReadVolume(&volume, NULL); + + // returns statvfs for the mount path or any file in the fs + // therefore, ignore ino. + std::memset(&vst, 0, sizeof(vst)); + + vst.f_bsize = 512; // fs block size + vst.f_frsize = 512; // fundamental fs block size + vst.f_blocks = volume.total_blocks; + vst.f_bfree = 0; // free blocks + vst.f_bavail = 0; // free blocks (non-root) + vst.f_files = 0; // ? + vst.f_ffree = -1; // free inodes. + vst.f_favail = -1; // free inodes (non-root) + vst.f_fsid = 0; // file system id? + vst.f_flag = ST_RDONLY | ST_NOSUID; + vst.f_namemax = 15; + + fuse_reply_statfs(req, &vst); +} + diff --git a/bin/profuse_xattr.cpp b/bin/profuse_xattr.cpp new file mode 100644 index 0000000..6159e86 --- /dev/null +++ b/bin/profuse_xattr.cpp @@ -0,0 +1,573 @@ +/* + * profuse_xattr.cpp + * profuse + * + * Created by Kelvin Sherlock on 1/23/2009. + * + */ + + +#include "profuse.h" + +#include +#include + +#include +#include +#include + +using std::string; + + +#ifdef __APPLE__ +#define NO_ATTRIBUTE ENOATTR +#else +#define NO_ATTRIBUTE EOPNOTSUPP +#endif + +static bool isTextFile(unsigned ftype, unsigned auxtype) +{ + if (ftype == 0x04) return true; // ascii text + if (ftype == 0xb0) return true; // source code. + if (ftype == 0x50 && auxtype == 0x5445) return true; // teach text + + return false; +} + + +static const char *mimeType(unsigned ftype, unsigned auxtype) +{ + switch(ftype) + { + case 0x04: + if (auxtype == 0) return "text/plain"; + break; + case 0xb0: + return "text/plain"; + break; + case 0x50: + if (auxtype == 0x5445) return "text/plain"; + break; + case 0xc0: + if (auxtype == 0x8006) return "image/gif"; + break; + case 0xe0: + if (auxtype == 0x8000) return "application/x-BinaryII"; + if (auxtype == 0x8002) return "application/x-Shrinkit"; + break; + } + return NULL; +} + + +static void setCreator(uint8_t *finfo, unsigned ftype, unsigned auxtype) +{ + + /* + + 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 + + + + */ + + finfo[0] = 'p'; + finfo[1] = ftype; + finfo[2] = auxtype >> 8; + finfo[3] = auxtype; + + memcpy(finfo + 4, "pdos", 4); + + switch (ftype) + { + case 0x00: + if (auxtype == 0) memcpy(finfo, "BINA", 4); + break; + + case 0x04: + if (auxtype == 0) memcpy(finfo, "TEXT", 4); + break; + + case 0x50: + if (auxtype == 0x5445) memcpy(finfo, "TEXT", 4); + break; + + case 0xb0: + memcpy(finfo, "TEXT", 4); + break; + + case 0xb3: + if ((auxtype >> 8) != 0xdb) memcpy(finfo, "PS16", 4); + break; + + case 0xd7: + if (auxtype == 0) memcpy(finfo, "MIDI", 4); + break; + + case 0xd8: + if (auxtype == 0) memcpy(finfo, "AIFF", 4); + if (auxtype == 1) memcpy(finfo, "AIFC", 4); + break; + + case 0xe0: + if (auxtype == 5) memcpy(finfo, "dImgdCpy", 8); + break; + + case 0xff: + memcpy(finfo, "PSYS", 4); + break; + } +} + + +#pragma mark XAttribute Functions + + +static void xattr_filetype(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + uint8_t attr = e.file_type; + unsigned attr_size = 1; + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + // consider position here? + fuse_reply_buf(req, (char *)&attr, attr_size); +} + + +static void xattr_auxtype(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + uint8_t attr[2]; + unsigned attr_size = 2; + + attr[0] = e.aux_type & 0xff; + attr[1] = (e.aux_type >> 8) & 0xff; + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + // consider position here? + fuse_reply_buf(req, (char *)&attr, attr_size); +} + +// user.charset +static void xattr_charset(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + const char attr[] = "macintosh"; + unsigned attr_size = sizeof(attr) - 1; + + ERROR(!isTextFile(e.file_type, e.aux_type), NO_ATTRIBUTE) + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + fuse_reply_buf(req, (char *)&attr, attr_size); +} + +//apple.TextEncoding +static void xattr_textencoding(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + const char attr[] = "MACINTOSH;0"; + unsigned attr_size = sizeof(attr) - 1; + + ERROR(!isTextFile(e.file_type, e.aux_type), NO_ATTRIBUTE) + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + fuse_reply_buf(req, (char *)&attr, attr_size); +} + +static void xattr_rfork(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + int ok; + unsigned level; + + ERROR (e.storage_type != EXTENDED_FILE, NO_ATTRIBUTE) + + ok = disk->Normalize(e, 1); + ERROR(ok < 0, EIO) + + + switch(e.storage_type) + { + case SEEDLING_FILE: + level = 0; + break; + case SAPLING_FILE: + level = 1; + break; + case TREE_FILE: + level = 2; + break; + default: + ERROR(true, EIO) + } + + if (size == 0) + { + fuse_reply_xattr(req, e.eof); + return; + } + + size = std::min((uint32_t)(size + off), e.eof); + + unsigned blocks = (size + (off & 0x1ff) + BLOCK_SIZE - 1) >> 9; + uint8_t *buffer = new uint8_t[blocks << 9]; + + fprintf(stderr, "ReadIndex(%x, buffer, %x, %x, %x)\n", e.key_pointer, level, (int)off, (int)blocks); + + ok = disk->ReadIndex(e.key_pointer, buffer, level, off, blocks); + + if (ok < 0) + { + fuse_reply_err(req, EIO); + } + else + { + fuse_reply_buf(req, (char *)buffer + (off & 0x1ff), size); + } + delete []buffer; + return; +} + + + +// Finder info. +static void xattr_finfo(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + int ok; + ExtendedEntry ee; + + uint8_t attr[32]; + unsigned attr_size = 32; + + //ERROR (e.storage_type != EXTENDED_FILE, ENOENT) + + switch (e.storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + bzero(attr, attr_size); + setCreator(attr, e.file_type, e.aux_type); + fuse_reply_buf(req, (char *)attr, attr_size); + return; + + case EXTENDED_FILE: + // handled below. + break; + + default: + ERROR(true, NO_ATTRIBUTE); + } + + + ok = disk->Normalize(e, 1, &ee); + ERROR(ok < 0, EIO) + + // sanity check + switch(e.storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + break; + default: + ERROR(true, EIO) + } + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + memcpy(attr, ee.FInfo, 16); + memcpy(attr + 16, ee.xFInfo, 16); + + // if no creator, create one. + if (memcmp(attr, "\0\0\0\0\0\0\0\0", 8) == 0) + setCreator(attr, e.file_type, e.aux_type); + + fuse_reply_buf(req, (char *)attr, attr_size); +} + + +static void xattr_mimetype(FileEntry& e, fuse_req_t req, size_t size, off_t off) +{ + unsigned attr_size; + const char *mime = mimeType(e.file_type, e.aux_type); + ERROR(!mime, NO_ATTRIBUTE); + + attr_size = strlen(mime); + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + ERROR (size < attr_size, ERANGE) + + fuse_reply_buf(req, mime, attr_size); +} + + + +void prodos_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + // list of supported attributes. + // +#define NO_ATTR() \ +{ \ +if (size) fuse_reply_buf(req, NULL, 0); \ +else fuse_reply_xattr(req, 0); \ +return; \ +} + + fprintf(stderr, "listxattr %u\n", (unsigned)ino); + + uint8_t buffer[BLOCK_SIZE]; + int ok; + unsigned attr_size; + string attr; + + + + if(ino == 1) + NO_ATTR() + + ok = disk->Read(ino >> 9, buffer); + + ERROR(ok < 0, EIO) + + + FileEntry e; + e.Load(buffer + (ino & 0x1ff)); + + + attr += "prodos.FileType"; + attr.append(1, 0); + + attr += "prodos.AuxType"; + attr.append(1, 0); + + switch(e.storage_type) + { + case EXTENDED_FILE: + { + // TODO -- pretend there's no resource fork if resource fork eof == 0 ? + // + //ok = disk->Normalize(e, 1); + //ERROR(ok < 0, EIO) + + attr += "prodos.ResourceFork"; + attr.append(1, 0); + + attr += "com.apple.FinderInfo"; + attr.append(1, 0); + break; + } + + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + // generate HFS creator codes. + attr += "com.apple.FinderInfo"; + attr.append(1, 0); + break; + + case DIRECTORY_FILE: + NO_ATTR() + break; + + default: + + NO_ATTR() + break; + } + + if (isTextFile(e.file_type, e.aux_type)) + { + attr += "com.apple.TextEncoding"; + attr.append(1, 0); + + attr += "user.charset"; + attr.append(1, 0); + } + + if (mimeType(e.file_type, e.aux_type)) + { + attr += "user.mime_type"; + attr.append(1, 0); + } + + attr_size = attr.length(); + + fprintf(stderr, "%d %s\n", attr_size, attr.c_str()); + + if (size == 0) + { + fuse_reply_xattr(req, attr_size); + return; + } + + if (size < attr_size) + { + fuse_reply_err(req, ERANGE); + return; + } + + fuse_reply_buf(req, attr.data(), attr_size); + return; +} + +/* + * offset is only valid in OS X for the resource fork. + * + */ +void prodos_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size, uint32_t off) +{ + + + fprintf(stderr, "getxattr: %u %s %u %u \n", (unsigned)ino, name, (unsigned)size, (unsigned)off); + + uint8_t buffer[BLOCK_SIZE]; + + + ERROR(ino == 1, NO_ATTRIBUTE) // finder can't handle EISDIR. + + + int ok = disk->Read(ino >> 9, buffer); + + ERROR(ok < 0, EIO) + + + FileEntry e; + e.Load(buffer + (ino & 0x1ff)); + + switch(e.storage_type) + { + case SEEDLING_FILE: + case SAPLING_FILE: + case TREE_FILE: + case EXTENDED_FILE: + break; + case DIRECTORY_FILE: + ERROR(true, NO_ATTRIBUTE) // Finder can't handle EISDIR. + default: + ERROR(true, NO_ATTRIBUTE); + } + + if (strcmp("prodos.FileType", name) == 0) + { + xattr_filetype(e, req, size, off); + return; + } + + if (strcmp("prodos.AuxType", name) == 0) + { + xattr_auxtype(e, req, size, off); + return; + } + + if (strcmp("com.apple.TextEncoding", name) == 0) + { + xattr_textencoding(e, req, size, off); + return; + } + + if ( (e.storage_type == EXTENDED_FILE) && (strcmp("prodos.ResourceFork", name) == 0)) + { + xattr_rfork(e, req, size, off); + return; + } + + if ( strcmp("com.apple.FinderInfo", name) == 0) + { + xattr_finfo(e, req, size, off); + return; + } + + // linux standard + if (strcmp("user.mime_type", name) == 0) + { + xattr_mimetype(e, req, size, off); + return; + } + + // linux standard + if (strcmp("user.charset", name) == 0) + { + xattr_charset(e, req, size, off); + return; + } + + fuse_reply_err(req, NO_ATTRIBUTE); + +} +/* + * Linux, et alia do not have an offset parameter. + */ +void prodos_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) +{ + prodos_getxattr(req, ino, name, size, 0); +} +