mirror of
https://github.com/ksherlock/profuse.git
synced 2025-02-09 03:30:43 +00:00
integrate profuse (classic)
git-svn-id: https://profuse.googlecode.com/svn/branches/v2@386 aa027e90-d47c-11dd-86d7-074df07e0730
This commit is contained in:
parent
84e0c8e08c
commit
304164ecfa
58
Makefile
58
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
|
||||
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <ProDOS/Bitmap.h>
|
||||
#include <ProDOS/BlockDevice.h>
|
||||
#include "auto.h"
|
||||
|
||||
#include <Device/BlockDevice.h>
|
||||
#include <Cache/BlockCache.h>
|
||||
|
||||
using namespace ProDOS;
|
||||
|
||||
@ -34,26 +33,25 @@ Bitmap::Bitmap(unsigned blocks)
|
||||
_freeIndex = 0;
|
||||
|
||||
unsigned bitmapSize = _bitmapBlocks * 512;
|
||||
unsigned blockSize = blocks / 8;
|
||||
|
||||
auto_array<uint8_t> 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<uint8_t> 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<uint8_t>::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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,20 +2,25 @@
|
||||
#define __BITMAP_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
|
||||
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<uint8_t> _bitmap;
|
||||
};
|
||||
|
||||
|
||||
|
486
ProDOS/Disk.cpp
Normal file
486
ProDOS/Disk.cpp
Normal file
@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Disk.cpp
|
||||
* ProFUSE
|
||||
*
|
||||
* Created by Kelvin Sherlock on 12/18/08.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Disk.h"
|
||||
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <Endian/Endian.h>
|
||||
|
||||
struct ucmp
|
||||
{
|
||||
bool operator()(unsigned a, unsigned b) const
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
};
|
||||
|
||||
using std::set;
|
||||
using std::vector;
|
||||
|
||||
using namespace LittleEndian;
|
||||
|
||||
typedef set<unsigned, ucmp> 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<FileEntry> *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<FileEntry> *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;
|
||||
}
|
83
ProDOS/Disk.h
Normal file
83
ProDOS/Disk.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Disk.h
|
||||
* ProFUSE
|
||||
*
|
||||
* Created by Kelvin Sherlock on 12/18/08.
|
||||
*
|
||||
*/
|
||||
#ifndef __DISK_H__
|
||||
#define __DISK_H__
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <ProDOS/File.h>
|
||||
#include <Device/BlockDevice.h>
|
||||
|
||||
#include <tr1/memory>
|
||||
|
||||
|
||||
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<Disk> 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<FileEntry> *files);
|
||||
int ReadDirectory(unsigned block, SubdirEntry *dir, std::vector<FileEntry> *files);
|
||||
|
||||
private:
|
||||
Disk();
|
||||
Disk(Device::BlockDevicePointer device);
|
||||
|
||||
unsigned _blocks;
|
||||
|
||||
Device::BlockDevicePointer _device;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
230
ProDOS/File.cpp
Normal file
230
ProDOS/File.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* File.cpp
|
||||
* ProFUSE
|
||||
*
|
||||
* Created by Kelvin Sherlock on 12/18/08.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ProDOS/File.h>
|
||||
#include <ProDOS/DateTime.h>
|
||||
#include <Endian/Endian.h>
|
||||
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
133
ProDOS/File.h
Normal file
133
ProDOS/File.h
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* File.h
|
||||
* ProFUSE
|
||||
*
|
||||
* Created by Kelvin Sherlock on 12/18/08.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __PRODOS_FILE_H__
|
||||
#define __PRODOS_FILE_H__
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
@ -1,7 +0,0 @@
|
||||
CC = g++
|
||||
CPPFLAGS += -g -Wall -I../
|
||||
|
||||
|
||||
all : DateTime.o
|
||||
|
||||
DateTime.o : DateTime.cpp DateTime.h
|
18
ProDOS/common.h
Normal file
18
ProDOS/common.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* common.h
|
||||
* ProFUSE
|
||||
*
|
||||
* Created by Kelvin Sherlock on 12/20/08.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __COMMON_H__
|
||||
#define __COMMON_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BLOCK_SIZE 512
|
||||
|
||||
|
||||
#endif
|
||||
|
374
bin/profuse.cpp
Normal file
374
bin/profuse.cpp
Normal file
@ -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 <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <tr1/memory>
|
||||
|
||||
#include <Device/BlockDevice.h>
|
||||
|
||||
|
||||
#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;
|
||||
}
|
62
bin/profuse.h
Normal file
62
bin/profuse.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* profuse.h
|
||||
* profuse
|
||||
*
|
||||
* Created by Kelvin Sherlock on 1/23/2009.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __PROFUSE_H__
|
||||
#define __PROFUSE_H__
|
||||
|
||||
#include <ProDOS/File.h>
|
||||
#include <ProDOS/Disk.h>
|
||||
#include <ProDOS/common.h>
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define __FreeBSD__ 10
|
||||
#define __DARWIN_64_BIT_INO_T 1
|
||||
#endif
|
||||
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#define FUSE_USE_VERSION 27
|
||||
|
||||
#include <fuse/fuse_opt.h>
|
||||
#include <fuse/fuse_lowlevel.h>
|
||||
|
||||
|
||||
#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
|
||||
|
130
bin/profuse_dirent.cpp
Normal file
130
bin/profuse_dirent.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* profuse_dirent.cpp
|
||||
* profuse
|
||||
*
|
||||
* Created by Kelvin Sherlock on 1/23/2009.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "profuse.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<FileEntry> 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<FileEntry> *fp = new vector<FileEntry>();
|
||||
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<FileEntry> *files = (vector<FileEntry> *)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<FileEntry> *files = (vector<FileEntry> *)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;
|
||||
}
|
||||
|
136
bin/profuse_file.cpp
Normal file
136
bin/profuse_file.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* profuse_file.cpp
|
||||
* profuse
|
||||
*
|
||||
* Created by Kelvin Sherlock on 1/23/2009.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "profuse.h"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#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;
|
||||
}
|
252
bin/profuse_stat.cpp
Normal file
252
bin/profuse_stat.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* prodos_stat.cpp
|
||||
* profuse
|
||||
*
|
||||
* Created by Kelvin Sherlock on 1/23/2009.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma mark Stat Functions
|
||||
|
||||
#include "profuse.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
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<FileEntry> 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<FileEntry>::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);
|
||||
}
|
||||
|
573
bin/profuse_xattr.cpp
Normal file
573
bin/profuse_xattr.cpp
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* profuse_xattr.cpp
|
||||
* profuse
|
||||
*
|
||||
* Created by Kelvin Sherlock on 1/23/2009.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "profuse.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user