mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
475 lines
12 KiB
C++
475 lines
12 KiB
C++
#include "lib/globals.h"
|
|
#include "lib/vfs/vfs.h"
|
|
#include "lib/config.pb.h"
|
|
#include "lib/proto.h"
|
|
#include "lib/layout.h"
|
|
#include "lib/logger.h"
|
|
|
|
#include "adflib.h"
|
|
#include "adf_blk.h"
|
|
#include "dep/adflib/adf_nativ.h"
|
|
#undef min
|
|
|
|
#include <ctime>
|
|
#include <iomanip>
|
|
|
|
class AmigaFfsFilesystem;
|
|
static AmigaFfsFilesystem* currentAmigaFfs;
|
|
|
|
extern struct Env adfEnv;
|
|
|
|
static RETCODE adfInitDevice(struct Device* dev, char* name, BOOL ro);
|
|
static RETCODE adfNativeReadSector(struct Device*, int32_t, int, uint8_t*);
|
|
static RETCODE adfNativeWriteSector(struct Device*, int32_t, int, uint8_t*);
|
|
static BOOL adfIsDevNative(char*);
|
|
static RETCODE adfReleaseDevice(struct Device*);
|
|
|
|
static std::string modeToString(long access)
|
|
{
|
|
std::stringstream ss;
|
|
if (hasH(access))
|
|
ss << 'H';
|
|
if (hasS(access))
|
|
ss << 'S';
|
|
if (hasP(access))
|
|
ss << 'P';
|
|
if (hasA(access))
|
|
ss << 'A';
|
|
if (hasR(access))
|
|
ss << 'R';
|
|
if (hasW(access))
|
|
ss << 'W';
|
|
if (hasE(access))
|
|
ss << 'E';
|
|
if (hasD(access))
|
|
ss << 'D';
|
|
return ss.str();
|
|
}
|
|
|
|
class AmigaFfsFilesystem : public Filesystem
|
|
{
|
|
public:
|
|
AmigaFfsFilesystem(
|
|
const AmigaFfsProto& config, std::shared_ptr<SectorInterface> sectors):
|
|
Filesystem(sectors),
|
|
_config(config)
|
|
{
|
|
}
|
|
|
|
uint32_t capabilities() const
|
|
{
|
|
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
|
|
OP_GETDIRENT | OP_DELETE | OP_MOVE | OP_CREATEDIR;
|
|
}
|
|
|
|
std::map<std::string, std::string> getMetadata() override
|
|
{
|
|
AdfMount m(this);
|
|
auto* vol = m.mount();
|
|
|
|
std::map<std::string, std::string> attributes;
|
|
attributes[VOLUME_NAME] = vol->volName;
|
|
|
|
int total = vol->lastBlock - vol->firstBlock + 1;
|
|
attributes[TOTAL_BLOCKS] = std::to_string(total);
|
|
attributes[USED_BLOCKS] =
|
|
std::to_string(total - adfCountFreeBlocks(vol));
|
|
attributes[BLOCK_SIZE] = "512";
|
|
return attributes;
|
|
}
|
|
|
|
void create(bool quick, const std::string& volumeName) override
|
|
{
|
|
if (!quick)
|
|
eraseEverythingOnDisk();
|
|
AdfMount m(this);
|
|
|
|
struct Device dev = {};
|
|
dev.readOnly = false;
|
|
dev.isNativeDev = true;
|
|
dev.devType = DEVTYPE_FLOPDD;
|
|
dev.cylinders = config.layout().tracks();
|
|
dev.heads = config.layout().sides();
|
|
dev.sectors = Layout::getLayoutOfTrack(0, 0)->numSectors;
|
|
adfInitDevice(&dev, nullptr, false);
|
|
int res = adfCreateFlop(&dev, (char*)volumeName.c_str(), 0);
|
|
if (res != RC_OK)
|
|
throw CannotWriteException();
|
|
}
|
|
|
|
FilesystemStatus check() override
|
|
{
|
|
return FS_OK;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
|
{
|
|
AdfMount m(this);
|
|
|
|
std::vector<std::shared_ptr<Dirent>> results;
|
|
|
|
auto* vol = m.mount();
|
|
changeDir(vol, path);
|
|
|
|
auto list = AdfList(adfGetDirEnt(vol, vol->curDirPtr));
|
|
struct List* cell = list;
|
|
while (cell)
|
|
{
|
|
auto* entry = (struct Entry*)cell->content;
|
|
cell = cell->next;
|
|
|
|
results.push_back(toDirent(entry, path));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
|
{
|
|
AdfMount m(this);
|
|
if (path.size() == 0)
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
changeDirButOne(vol, path);
|
|
|
|
auto entry = AdfEntry(adfFindEntry(vol, (char*)path.back().c_str()));
|
|
if (!entry)
|
|
throw FileNotFoundException();
|
|
|
|
return toDirent(entry, path.parent());
|
|
}
|
|
|
|
Bytes getFile(const Path& path) override
|
|
{
|
|
AdfMount m(this);
|
|
if (path.size() == 0)
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
changeDirButOne(vol, path);
|
|
|
|
auto* file = adfOpenFile(vol, (char*)path.back().c_str(), (char*)"r");
|
|
if (!file)
|
|
throw FileNotFoundException();
|
|
|
|
Bytes bytes;
|
|
ByteWriter bw(bytes);
|
|
while (!adfEndOfFile(file))
|
|
{
|
|
uint8_t buffer[4096];
|
|
long done = adfReadFile(file, sizeof(buffer), buffer);
|
|
|
|
bw += Bytes(buffer, done);
|
|
}
|
|
|
|
adfCloseFile(file);
|
|
return bytes;
|
|
}
|
|
|
|
void putFile(const Path& path, const Bytes& data) override
|
|
{
|
|
AdfMount m(this);
|
|
if (path.size() == 0)
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
changeDirButOne(vol, path);
|
|
|
|
auto* file = adfOpenFile(vol, (char*)path.back().c_str(), (char*)"w");
|
|
if (!file)
|
|
throw CannotWriteException();
|
|
|
|
unsigned pos = 0;
|
|
while (pos != data.size())
|
|
{
|
|
long done = adfWriteFile(
|
|
file, data.size() - pos, (uint8_t*)data.cbegin() + pos);
|
|
pos += done;
|
|
}
|
|
|
|
adfCloseFile(file);
|
|
}
|
|
|
|
void deleteFile(const Path& path) override
|
|
{
|
|
AdfMount m(this);
|
|
if (path.size() == 0)
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
changeDirButOne(vol, path);
|
|
|
|
int res =
|
|
adfRemoveEntry(vol, vol->curDirPtr, (char*)path.back().c_str());
|
|
if (res != RC_OK)
|
|
throw CannotWriteException();
|
|
}
|
|
|
|
void moveFile(const Path& oldPath, const Path& newPath) override
|
|
{
|
|
AdfMount m(this);
|
|
if ((oldPath.size() == 0) || (newPath.size() == 0))
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
|
|
changeDirButOne(vol, oldPath);
|
|
auto oldDir = vol->curDirPtr;
|
|
|
|
changeDirButOne(vol, newPath);
|
|
auto newDir = vol->curDirPtr;
|
|
|
|
int res = adfRenameEntry(vol,
|
|
oldDir,
|
|
(char*)oldPath.back().c_str(),
|
|
newDir,
|
|
(char*)newPath.back().c_str());
|
|
if (res != RC_OK)
|
|
throw CannotWriteException();
|
|
}
|
|
|
|
void createDirectory(const Path& path)
|
|
{
|
|
AdfMount m(this);
|
|
if (path.empty())
|
|
throw BadPathException();
|
|
|
|
auto* vol = m.mount();
|
|
changeDirButOne(vol, path);
|
|
int res = adfCreateDir(vol, vol->curDirPtr, (char*)path.back().c_str());
|
|
if (res != RC_OK)
|
|
throw CannotWriteException();
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<Dirent> toDirent(struct Entry* entry, const Path& container)
|
|
{
|
|
auto dirent = std::make_shared<Dirent>();
|
|
|
|
dirent->path = container;
|
|
dirent->path.push_back(entry->name);
|
|
dirent->filename = entry->name;
|
|
dirent->length = entry->size;
|
|
dirent->file_type =
|
|
(entry->type == ST_FILE) ? TYPE_FILE : TYPE_DIRECTORY;
|
|
dirent->mode = modeToString(entry->access);
|
|
|
|
dirent->attributes[FILENAME] = entry->name;
|
|
dirent->attributes[LENGTH] = std::to_string(entry->size);
|
|
dirent->attributes[FILE_TYPE] =
|
|
(entry->type == ST_FILE) ? "file" : "dir";
|
|
dirent->attributes[MODE] = modeToString(entry->access);
|
|
dirent->attributes["amigaffs.comment"] = entry->comment;
|
|
|
|
std::tm tm = {.tm_sec = entry->secs,
|
|
.tm_min = entry->mins,
|
|
.tm_hour = entry->hour,
|
|
.tm_mday = entry->days,
|
|
.tm_mon = entry->month,
|
|
.tm_year = entry->year - 1900};
|
|
std::stringstream ss;
|
|
ss << std::put_time(&tm, "%FT%T%z");
|
|
dirent->attributes["amigaffs.mtime"] = ss.str();
|
|
|
|
return dirent;
|
|
}
|
|
|
|
class AdfEntry
|
|
{
|
|
public:
|
|
AdfEntry(struct Entry* entry): _entry(entry) {}
|
|
|
|
~AdfEntry()
|
|
{
|
|
if (_entry)
|
|
adfFreeEntry(_entry);
|
|
}
|
|
|
|
operator struct Entry *() const
|
|
{
|
|
return _entry;
|
|
}
|
|
|
|
struct Entry* operator->() const
|
|
{
|
|
return _entry;
|
|
}
|
|
|
|
private:
|
|
struct Entry* _entry;
|
|
};
|
|
|
|
class AdfList
|
|
{
|
|
public:
|
|
AdfList(struct List* list): _list(list) {}
|
|
|
|
~AdfList()
|
|
{
|
|
if (_list)
|
|
adfFreeDirList(_list);
|
|
}
|
|
|
|
operator struct List *() const
|
|
{
|
|
return _list;
|
|
}
|
|
|
|
struct List* operator->()
|
|
{
|
|
return _list;
|
|
}
|
|
|
|
private:
|
|
struct List* _list;
|
|
};
|
|
|
|
class AdfMount
|
|
{
|
|
public:
|
|
friend AmigaFfsFilesystem;
|
|
AdfMount(AmigaFfsFilesystem* self): self(self)
|
|
{
|
|
currentAmigaFfs = self;
|
|
self->_ffs = nullptr;
|
|
|
|
adfEnvInitDefault();
|
|
}
|
|
|
|
~AdfMount()
|
|
{
|
|
if (vol)
|
|
adfUnMount(vol);
|
|
if (self->_ffs)
|
|
adfUnMountDev(self->_ffs);
|
|
adfEnvCleanUp();
|
|
}
|
|
|
|
struct Volume* mount()
|
|
{
|
|
self->_ffs = adfMountDev(nullptr, false);
|
|
if (!self->_ffs)
|
|
throw BadFilesystemException();
|
|
vol = adfMount(self->_ffs, 0, false);
|
|
if (!vol)
|
|
throw BadFilesystemException();
|
|
return vol;
|
|
}
|
|
|
|
private:
|
|
AmigaFfsFilesystem* self;
|
|
struct Volume* vol = nullptr;
|
|
};
|
|
|
|
void changeDir(struct Volume* vol, const Path& path)
|
|
{
|
|
adfToRootDir(vol);
|
|
for (const auto& p : path)
|
|
{
|
|
RETCODE r = adfChangeDir(vol, (char*)p.c_str());
|
|
if (r != RC_OK)
|
|
throw FileNotFoundException();
|
|
}
|
|
}
|
|
|
|
void changeDirButOne(struct Volume* vol, const Path& path)
|
|
{
|
|
adfToRootDir(vol);
|
|
for (int i = 0; i < path.size() - 1; i++)
|
|
{
|
|
RETCODE r = adfChangeDir(vol, (char*)path[i].c_str());
|
|
if (r != RC_OK)
|
|
throw FileNotFoundException();
|
|
}
|
|
}
|
|
|
|
public:
|
|
RETCODE adfInitDevice(struct Device* dev, char* name, BOOL ro)
|
|
{
|
|
dev->size = getLogicalSectorCount() * getLogicalSectorSize();
|
|
return RC_OK;
|
|
}
|
|
|
|
RETCODE adfReleaseDevice(struct Device* dev)
|
|
{
|
|
return RC_OK;
|
|
}
|
|
|
|
RETCODE adfNativeReadSector(
|
|
struct Device* dev, int32_t sector, int size, uint8_t* buffer)
|
|
{
|
|
auto bytes = getLogicalSector(sector, size / 512);
|
|
memcpy(buffer, bytes.cbegin(), bytes.size());
|
|
return RC_OK;
|
|
}
|
|
|
|
RETCODE adfNativeWriteSector(
|
|
struct Device* dev, int32_t sector, int size, uint8_t* buffer)
|
|
{
|
|
Bytes bytes(buffer, size);
|
|
putLogicalSector(sector, bytes);
|
|
return RC_OK;
|
|
}
|
|
|
|
private:
|
|
const AmigaFfsProto& _config;
|
|
struct Device* _ffs;
|
|
};
|
|
|
|
static void onAdfWarning(char* message)
|
|
{
|
|
log(message);
|
|
}
|
|
|
|
static void onAdfError(char* message)
|
|
{
|
|
throw FilesystemException(message);
|
|
}
|
|
|
|
void adfInitNativeFct()
|
|
{
|
|
auto cbs = (struct nativeFunctions*)adfEnv.nativeFct;
|
|
cbs->adfInitDevice = ::adfInitDevice;
|
|
cbs->adfNativeReadSector = ::adfNativeReadSector;
|
|
cbs->adfNativeWriteSector = ::adfNativeWriteSector;
|
|
cbs->adfIsDevNative = ::adfIsDevNative;
|
|
cbs->adfReleaseDevice = ::adfReleaseDevice;
|
|
|
|
adfChgEnvProp(PR_WFCT, (void*)onAdfWarning);
|
|
adfChgEnvProp(PR_EFCT, (void*)onAdfError);
|
|
}
|
|
|
|
static RETCODE adfInitDevice(struct Device* dev, char* name, BOOL ro)
|
|
{
|
|
return currentAmigaFfs->adfInitDevice(dev, name, ro);
|
|
}
|
|
|
|
static RETCODE adfReleaseDevice(struct Device* dev)
|
|
{
|
|
return currentAmigaFfs->adfReleaseDevice(dev);
|
|
}
|
|
|
|
static RETCODE adfNativeReadSector(
|
|
struct Device* dev, int32_t count, int size, uint8_t* buffer)
|
|
{
|
|
return currentAmigaFfs->adfNativeReadSector(dev, count, size, buffer);
|
|
}
|
|
|
|
static RETCODE adfNativeWriteSector(
|
|
struct Device* dev, int32_t count, int size, uint8_t* buffer)
|
|
{
|
|
return currentAmigaFfs->adfNativeWriteSector(dev, count, size, buffer);
|
|
}
|
|
|
|
static BOOL adfIsDevNative(char*)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<Filesystem> Filesystem::createAmigaFfsFilesystem(
|
|
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
|
|
{
|
|
return std::make_unique<AmigaFfsFilesystem>(config.amigaffs(), sectors);
|
|
}
|