From 99e1a907290cb55d19fd113be04f6c2a2371c4d9 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 28 Aug 2022 16:37:21 +0200 Subject: [PATCH] Implement basic CP/M filesystem support. --- lib/build.mk | 1 + lib/vfs/cpmfs.cc | 282 ++++++++++++++++++++++++++++++++++++++++ lib/vfs/vfs.cc | 28 +++- lib/vfs/vfs.h | 14 +- lib/vfs/vfs.proto | 13 ++ src/fe-getfile.cc | 1 - src/formats/eco1.textpb | 25 +++- 7 files changed, 352 insertions(+), 12 deletions(-) create mode 100644 lib/vfs/cpmfs.cc diff --git a/lib/build.mk b/lib/build.mk index 0b639961..b2dce8d2 100644 --- a/lib/build.mk +++ b/lib/build.mk @@ -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 \ diff --git a/lib/vfs/cpmfs.cc b/lib/vfs/cpmfs.cc new file mode 100644 index 00000000..dc670eec --- /dev/null +++ b/lib/vfs/cpmfs.cc @@ -0,0 +1,282 @@ +#include "lib/globals.h" +#include "lib/vfs/vfs.h" +#include "lib/config.pb.h" +#include + +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 allocation_map; +}; + +class CpmFsFilesystem : public Filesystem +{ +public: + CpmFsFilesystem( + const CpmFsProto& config, std::shared_ptr sectors): + Filesystem(sectors), + _config(config) + { + } + + FilesystemStatus check() + { + return FS_OK; + } + + std::vector> list(const Path& path) + { + mount(); + if (!path.empty()) + throw FileNotFoundException(); + + std::map> 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->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> result; + for (auto& e : map) + result.push_back(std::move(e.second)); + return result; + } + + std::map getMetadata(const Path& path) + { + mount(); + if (path.size() != 1) + throw BadPathException(); + + std::unique_ptr 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->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 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; + 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 getEntry(unsigned d) + { + auto bytes = _directory.slice(d * 32, 32); + if (bytes[0] == 0xe5) + return nullptr; + + return std::make_unique(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::createCpmFsFilesystem( + const FilesystemProto& config, std::shared_ptr sectors) +{ + return std::make_unique(config.cpmfs(), sectors); +} diff --git a/lib/vfs/vfs.cc b/lib/vfs/vfs.cc index e1f23c20..26c07f2c 100644 --- a/lib/vfs/vfs.cc +++ b/lib/vfs/vfs.cc @@ -73,12 +73,23 @@ std::unique_ptr 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(); } } +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(); } diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h index 9fe8f208..099823c0 100644 --- a/lib/vfs/vfs.h +++ b/lib/vfs/vfs.h @@ -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 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 location_t; @@ -157,6 +165,8 @@ public: const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createFatFsFilesystem( const FilesystemProto& config, std::shared_ptr image); + static std::unique_ptr createCpmFsFilesystem( + const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createFilesystem( const FilesystemProto& config, std::shared_ptr image); diff --git a/lib/vfs/vfs.proto b/lib/vfs/vfs.proto index b4b249f3..49db1900 100644 --- a/lib/vfs/vfs.proto +++ b/lib/vfs/vfs.proto @@ -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; } } diff --git a/src/fe-getfile.cc b/src/fe-getfile.cc index 55f1d015..363a9a24 100644 --- a/src/fe-getfile.cc +++ b/src/fe-getfile.cc @@ -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); diff --git a/src/formats/eco1.textpb b/src/formats/eco1.textpb index 9eeeda31..44554b2c 100644 --- a/src/formats/eco1.textpb +++ b/src/formats/eco1.textpb @@ -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 + } +}