Implement basic CP/M filesystem support.

This commit is contained in:
David Given
2022-08-28 16:37:21 +02:00
parent 54e11e96e9
commit 99e1a90729
7 changed files with 352 additions and 12 deletions

View File

@@ -66,6 +66,7 @@ LIBFLUXENGINE_SRCS = \
lib/utils.cc \
lib/vfs/brother120fs.cc \
lib/vfs/acorndfs.cc \
lib/vfs/cpmfs.cc \
lib/vfs/vfs.cc \
lib/vfs/fatfs.cc \
lib/vfs/fluxsectorinterface.cc \

282
lib/vfs/cpmfs.cc Normal file
View File

@@ -0,0 +1,282 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
class Entry
{
public:
Entry(const Bytes& bytes, int map_entry_size)
{
user = bytes[0] & 0x0f;
{
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 9; i <= 11; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
if (i == 9)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
{
std::stringstream ss;
if (bytes[9] & 0x80)
ss << 'R';
if (bytes[10] & 0x80)
ss << 'S';
if (bytes[11] & 0x80)
ss << 'A';
mode = ss.str();
}
extent = bytes[12] | (bytes[14] << 5);
records = bytes[15];
ByteReader br(bytes);
br.seek(16);
switch (map_entry_size)
{
case 1:
for (int i=0; i<16; i++)
allocation_map.push_back(br.read_8());
break;
case 2:
for (int i=0; i<8; i++)
allocation_map.push_back(br.read_le16());
break;
}
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
class CpmFsFilesystem : public Filesystem
{
public:
CpmFsFilesystem(
const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
FilesystemStatus check()
{
return FS_OK;
}
std::vector<std::unique_ptr<Dirent>> list(const Path& path)
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::map<std::string, std::unique_ptr<Dirent>> map;
for (int d = 0; d < _config.dir_entries(); d++)
{
auto entry = getEntry(d);
if (!entry)
continue;
auto& dirent = map[entry->filename];
if (!dirent)
{
dirent = std::make_unique<Dirent>();
dirent->filename = entry->filename;
dirent->mode = entry->mode;
dirent->length = 0;
dirent->file_type = TYPE_FILE;
}
dirent->length = std::max(dirent->length,
entry->extent*16384 + entry->records*128);
}
std::vector<std::unique_ptr<Dirent>> result;
for (auto& e : map)
result.push_back(std::move(e.second));
return result;
}
std::map<std::string, std::string> getMetadata(const Path& path)
{
mount();
if (path.size() != 1)
throw BadPathException();
std::unique_ptr<Dirent> dirent;
for (int d = 0; d<_config.dir_entries(); d++)
{
auto entry = getEntry(d);
if (!entry)
continue;
if (path[0] != entry->filename)
continue;
if (!dirent)
{
dirent = std::make_unique<Dirent>();
dirent->filename = entry->filename;
dirent->mode = entry->mode;
dirent->length = 0;
dirent->file_type = TYPE_FILE;
}
dirent->length = std::max(dirent->length,
entry->extent*16384 + entry->records*128);
}
if (!dirent)
throw FileNotFoundException();
std::map<std::string, std::string> attributes;
attributes["filename"] = dirent->filename;
attributes["length"] = fmt::format("{}", dirent->length);
attributes["type"] = "file";
attributes["mode"] = dirent->mode;
return attributes;
}
Bytes getFile(const Path& path)
{
mount();
if (path.size() != 1)
throw BadPathException();
Bytes data;
ByteWriter bw(data);
int logicalExtent = 0;
for (;;)
{
/* Find a directory entry for this logical extent. */
std::unique_ptr<Entry> entry;
for (int d = 0; d<_config.dir_entries(); d++)
{
entry = getEntry(d);
if (!entry)
continue;
if (path[0] != entry->filename)
continue;
if (entry->extent == logicalExtent)
break;
}
if (!entry)
{
if (logicalExtent == 0)
throw FileNotFoundException();
break;
}
/* Copy the data out. */
unsigned records = entry->records;
int i = 0;
while ((records != 0) && (i != entry->allocation_map.size()))
{
Bytes block;
unsigned blockid = entry->allocation_map[i];
if (blockid != 0)
block = getCpmBlock(entry->allocation_map[i]);
else
block.resize(_config.block_size());
unsigned r = std::min(records, _recordsPerBlock);
bw += block.slice(0, r*128);
records -= r;
i++;
}
logicalExtent++;
}
return data;
}
private:
void mount()
{
auto& start = _config.filesystem_start();
_filesystemStart = getOffsetOfSector(start.track(), start.side(), start.sector());
_sectorSize = getLogicalSectorSize(start.track(), start.side());
_blockSectors = _config.block_size() / _sectorSize;
_recordsPerBlock = _config.block_size() / 128;
_dirBlocks = (_config.dir_entries() * 32) / _config.block_size();
_filesystemBlocks = (getLogicalSectorCount() - _filesystemStart) / _blockSectors;
_allocationMapSize = (_filesystemBlocks < 256) ? 1 : 2;
int physicalExtentSize;
if (_allocationMapSize == 1)
{
/* One byte allocation maps */
physicalExtentSize = _config.block_size() * 16;
}
else
{
/* Two byte allocation maps */
physicalExtentSize = _config.block_size() * 8;
}
if (physicalExtentSize > 16384)
throw UnimplementedFilesystemException("file systems with more than one logical extent per physical extent not supported yet");
_directory = getCpmBlock(0, _dirBlocks);
}
std::unique_ptr<Entry> getEntry(unsigned d)
{
auto bytes = _directory.slice(d * 32, 32);
if (bytes[0] == 0xe5)
return nullptr;
return std::make_unique<Entry>(bytes, _allocationMapSize);
}
Bytes getCpmBlock(uint32_t number, uint32_t count = 1)
{
unsigned sector = number * _blockSectors + _filesystemStart;
return getLogicalSector(sector, _blockSectors * count);
}
private:
const CpmFsProto& _config;
uint32_t _sectorSize;
uint32_t _blockSectors;
uint32_t _recordsPerBlock;
uint32_t _dirBlocks;
uint32_t _filesystemStart;
uint32_t _filesystemBlocks;
int _allocationMapSize;
Bytes _directory;
};
std::unique_ptr<Filesystem> Filesystem::createCpmFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<CpmFsFilesystem>(config.cpmfs(), sectors);
}

View File

@@ -73,12 +73,23 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::kFatfs:
return Filesystem::createFatFsFilesystem(config, image);
case FilesystemProto::kCpmfs:
return Filesystem::createCpmFsFilesystem(config, image);
default:
Error() << "no filesystem configured";
return std::unique_ptr<Filesystem>();
}
}
Bytes Filesystem::getSector(unsigned track, unsigned side, unsigned sector)
{
auto s = _sectors->get(track, side, sector);
if (!s)
throw BadFilesystemException();
return s->data;
}
Bytes Filesystem::getLogicalSector(uint32_t number, uint32_t count)
{
if ((number + count) > _locations.size())
@@ -104,13 +115,26 @@ void Filesystem::putLogicalSector(uint32_t number, const Bytes& data)
_sectors->put(std::get<0>(i), std::get<1>(i), std::get<2>(i))->data = data;
}
unsigned Filesystem::getOffsetOfSector(unsigned track, unsigned side, unsigned sector)
{
location_t key = { track, side, sector };
for (int i = 0; i < _locations.size(); i++)
{
if (_locations[i] >= key)
return i;
}
throw BadFilesystemException();
}
unsigned Filesystem::getLogicalSectorCount()
{
return _locations.size();
}
unsigned Filesystem::getLogicalSectorSize()
unsigned Filesystem::getLogicalSectorSize(unsigned track, unsigned side)
{
auto trackdata = Layout::getLayoutOfTrack(0, 0);
auto trackdata = Layout::getLayoutOfTrack(track, side);
return trackdata.sector_size();
}

View File

@@ -20,7 +20,7 @@ struct Dirent
{
std::string filename;
FileType file_type;
uint64_t length;
uint32_t length;
std::string mode;
};
@@ -71,6 +71,11 @@ public:
class UnimplementedFilesystemException : public FilesystemException
{
public:
UnimplementedFilesystemException(const std::string& msg):
FilesystemException(msg)
{
}
UnimplementedFilesystemException():
FilesystemException("Unimplemented operation")
{
@@ -139,11 +144,14 @@ public:
protected:
Filesystem(std::shared_ptr<SectorInterface> sectors);
Bytes getSector(unsigned track, unsigned side, unsigned sector);
Bytes getLogicalSector(uint32_t number, uint32_t count = 1);
void putLogicalSector(uint32_t number, const Bytes& data);
unsigned getOffsetOfSector(unsigned track, unsigned side, unsigned sector);
unsigned getLogicalSectorCount();
unsigned getLogicalSectorSize();
unsigned getLogicalSectorSize(unsigned track = 0, unsigned side = 0);
private:
typedef std::tuple<unsigned, unsigned, unsigned> location_t;
@@ -157,6 +165,8 @@ public:
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFatFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createCpmFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -17,11 +17,24 @@ message Brother120FsProto {
message FatFsProto {
}
message CpmFsProto {
message Location {
optional uint32 track = 1 [(help) = "track number"];
optional uint32 side = 2 [(help) = "side number"];
optional uint32 sector = 3 [(help) = "sector ID"];
}
optional Location filesystem_start = 1 [(help) = "position of the start of the filesystem"];
optional int32 block_size = 2 [(help) = "allocation block size"];
optional int32 dir_entries = 3 [(help) = "number of entries in the directory"];
}
message FilesystemProto {
oneof filesystem {
AcornDfsProto acorndfs = 1;
Brother120FsProto brother120 = 2;
FatFsProto fatfs = 3;
CpmFsProto cpmfs = 4;
}
}

View File

@@ -36,7 +36,6 @@ int mainGetFile(int argc, const char* argv[])
std::string outputFilename = output;
if (outputFilename.empty())
outputFilename = inputFilename.back();
fmt::print("{}\n", outputFilename);
auto filesystem = createFilesystemFromConfig();
auto data = filesystem->getFile(inputFilename);

View File

@@ -8,13 +8,6 @@ image_writer {
layout {
tracks: 77
sides: 2
layoutdata {
sector_size: 512
physical {
sector: 1
count: 16
}
}
layoutdata {
track: 0
side: 0
@@ -33,6 +26,15 @@ layout {
count: 26
}
}
layoutdata {
track: 1
up_to_track: 76
sector_size: 512
physical {
sector: 1
count: 16
}
}
}
decoder {
@@ -49,4 +51,13 @@ heads {
end: 1
}
filesystem {
cpmfs {
filesystem_start {
track: 2
}
block_size: 2048
dir_entries: 64
}
}