Files
fluxengine/lib/vfs/zdos.cc
2023-07-24 21:48:37 +02:00

326 lines
8.9 KiB
C++

#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/layout.h"
#include <iomanip>
/* See
* https://oldcomputers.dyndns.org/public/pub/rechner/zilog/zds/manuals/z80-rio_os_userman.pdf,
* page 116. */
enum
{
ZDOS_TYPE_DATA = 0x10,
ZDOS_TYPE_ASCII = 0x20,
ZDOS_TYPE_DIRECTORY = 0x40,
ZDOS_TYPE_PROCEDURE = 0x80
};
enum
{
ZDOS_MODE_FORCE = 1 << 2,
ZDOS_MODE_RANDOM = 1 << 3,
ZDOS_MODE_SECRET = 1 << 4,
ZDOS_MODE_LOCKED = 1 << 5,
ZDOS_MODE_ERASEPROTECT = 1 << 6,
ZDOS_MODE_WRITEPROTECT = 1 << 7
};
static const std::map<uint8_t, std::string> fileTypeMap = {
{0, "INVALID" },
{ZDOS_TYPE_DATA, "DATA" },
{ZDOS_TYPE_ASCII, "ASCII" },
{ZDOS_TYPE_DIRECTORY, "DIRECTORY"},
{ZDOS_TYPE_PROCEDURE, "PROCEDURE"}
};
static std::string convertTime(std::string zdosTime)
{
/* Due to a bug in std::get_time, we can't parse the string directly --- a
* pattern of %y%m%d causes the first four digits of the string to become
* the year. So we need to reform the string. */
zdosTime = fmt::format("{}-{}-{}",
zdosTime.substr(0, 2),
zdosTime.substr(2, 2),
zdosTime.substr(4, 2));
std::tm tm = {};
std::stringstream(zdosTime) >> std::get_time(&tm, "%y-%m-%d");
std::stringstream ss;
ss << std::put_time(&tm, "%FT%T%z");
return ss.str();
}
class ZDosFilesystem : public Filesystem
{
class ZDosDescriptor
{
public:
ZDosDescriptor(ZDosFilesystem* zfs, int block): zfs(zfs)
{
Bytes bytes = zfs->getLogicalSector(block);
ByteReader br(bytes);
br.seek(8);
firstRecord = zfs->readBlockNumber(br);
br.seek(12);
type = br.read_8();
recordCount = br.read_le16();
recordSize = br.read_le16();
br.seek(19);
properties = br.read_8();
startAddress = br.read_le16();
lastRecordSize = br.read_le16();
br.seek(24);
ctime = br.read(8);
mtime = br.read(8);
rewind();
}
void rewind()
{
currentRecord = firstRecord;
eof = false;
}
Bytes readRecord()
{
assert(!eof);
int count = recordSize / 0x80;
Bytes result;
ByteWriter bw(result);
while (count--)
{
Bytes sector = zfs->getLogicalSector(currentRecord);
ByteReader br(sector);
bw += br.read(0x80);
br.skip(2);
int sectorId = br.read_8();
int track = br.read_8();
currentRecord = zfs->toBlockNumber(sectorId, track);
if (sectorId == 0xff)
eof = true;
}
return result;
}
public:
ZDosFilesystem* zfs;
uint16_t firstRecord;
uint8_t type;
uint16_t recordCount;
uint16_t recordSize;
uint8_t properties;
uint16_t startAddress;
uint16_t lastRecordSize;
std::string ctime;
std::string mtime;
uint16_t currentRecord;
bool eof;
};
class ZDosDirent : public Dirent
{
public:
ZDosDirent(ZDosFilesystem* zfs,
const std::string& filename,
int descriptorBlock):
zfs(zfs),
descriptorBlock(descriptorBlock),
zd(zfs, descriptorBlock)
{
file_type = TYPE_FILE;
this->filename = filename;
length = (zd.recordCount - 1) * zd.recordSize + zd.lastRecordSize;
mode = "";
if (zd.properties & ZDOS_MODE_FORCE)
mode += 'F';
if (zd.properties & ZDOS_MODE_RANDOM)
mode += 'R';
if (zd.properties & ZDOS_MODE_SECRET)
mode += 'S';
if (zd.properties & ZDOS_MODE_LOCKED)
mode += 'L';
if (zd.properties & ZDOS_MODE_ERASEPROTECT)
mode += 'E';
if (zd.properties & ZDOS_MODE_WRITEPROTECT)
mode += 'W';
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["zdos.descriptor_record"] =
std::to_string(descriptorBlock);
attributes["zdos.first_record"] = std::to_string(zd.firstRecord);
attributes["zdos.record_size"] = std::to_string(zd.recordSize);
attributes["zdos.record_count"] = std::to_string(zd.recordCount);
attributes["zdos.last_record_size"] =
std::to_string(zd.lastRecordSize);
attributes["zdos.start_address"] =
fmt::format("0x{:04x}", zd.startAddress);
attributes["zdos.type"] = fileTypeMap.at(zd.type & 0xf0);
attributes["zdos.ctime"] = convertTime(zd.ctime);
attributes["zdos.mtime"] = convertTime(zd.mtime);
}
public:
ZDosFilesystem* zfs;
int descriptorBlock;
ZDosDescriptor zd;
};
public:
ZDosFilesystem(
const ZDosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "128";
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
dirent->zd.rewind();
Bytes bytes;
ByteWriter bw(bytes);
while (!dirent->zd.eof)
{
bw += dirent->zd.readRecord();
}
return bytes.slice(0, dirent->length);
}
private:
void mount()
{
_sectorsPerTrack = Layout::getLayoutOfTrack(0, 0)->numSectors;
int rootBlock = toBlockNumber(_config.filesystem_start().sector(),
_config.filesystem_start().track());
ZDosDescriptor zd(this, rootBlock);
if (zd.type != ZDOS_TYPE_DIRECTORY)
throw BadFilesystemException();
_totalBlocks = getLogicalSectorCount();
_usedBlocks = (zd.recordCount * zd.recordSize) / 0x80 + 1;
while (!zd.eof)
{
Bytes bytes = zd.readRecord();
ByteReader br(bytes);
for (;;)
{
int len = br.read_8();
if (len == 0xff)
break;
std::string filename = br.read(len & 0x7f);
int descriptorBlock = readBlockNumber(br);
auto dirent = std::make_unique<ZDosDirent>(
this, filename, descriptorBlock);
_usedBlocks +=
(dirent->zd.recordCount * dirent->zd.recordSize) / 0x80 + 1;
_dirents.push_back(std::move(dirent));
}
}
}
std::shared_ptr<ZDosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
int toBlockNumber(int sectorId, int track)
{
return track * _sectorsPerTrack + sectorId;
}
int readBlockNumber(ByteReader& br)
{
int sectorId = br.read_8();
int track = br.read_8();
return toBlockNumber(sectorId, track);
}
private:
const ZDosProto& _config;
unsigned _sectorsPerTrack;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<ZDosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<ZDosFilesystem>(config.zdos(), sectors);
}