mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			668 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			668 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lib/globals.h"
 | |
| #include "lib/vfs/vfs.h"
 | |
| #include "lib/config.pb.h"
 | |
| #include <fmt/format.h>
 | |
| #include <regex>
 | |
| 
 | |
| class CpmFsFilesystem : public Filesystem, public HasBitmap, public HasMount
 | |
| {
 | |
|     class Entry
 | |
|     {
 | |
|     public:
 | |
|         Entry(const Bytes& bytes, int map_entry_size, unsigned index):
 | |
|             index(index)
 | |
|         {
 | |
|             if (bytes[0] == 0xe5)
 | |
|                 deleted = true;
 | |
| 
 | |
|             user = bytes[0] & 0x0f;
 | |
| 
 | |
|             {
 | |
|                 std::stringstream ss;
 | |
| 
 | |
|                 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;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Bytes toBytes(int map_entry_size) const
 | |
|         {
 | |
|             Bytes bytes(32);
 | |
|             ByteWriter bw(bytes);
 | |
| 
 | |
|             if (deleted)
 | |
|             {
 | |
|                 for (int i = 0; i < 32; i++)
 | |
|                     bw.write_8(0xe5);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 bw.write_8(user);
 | |
| 
 | |
|                 /* Encode the filename. */
 | |
| 
 | |
|                 for (int i = 1; i < 12; i++)
 | |
|                     bytes[i] = 0x20;
 | |
|                 for (char c : filename)
 | |
|                 {
 | |
|                     if (islower(c))
 | |
|                         throw BadPathException();
 | |
|                     if (c == '.')
 | |
|                     {
 | |
|                         if (bw.pos >= 9)
 | |
|                             throw BadPathException();
 | |
|                         bw.seek(9);
 | |
|                         continue;
 | |
|                     }
 | |
|                     if ((bw.pos == 9) || (bw.pos == 12))
 | |
|                         throw BadPathException();
 | |
|                     bw.write_8(c);
 | |
|                 }
 | |
| 
 | |
|                 /* Set the mode. */
 | |
| 
 | |
|                 if (mode.find('R') != std::string::npos)
 | |
|                     bytes[9] |= 0x80;
 | |
|                 if (mode.find('S') != std::string::npos)
 | |
|                     bytes[10] |= 0x80;
 | |
|                 if (mode.find('A') != std::string::npos)
 | |
|                     bytes[11] |= 0x80;
 | |
| 
 | |
|                 /* EX, S1, S2, RC */
 | |
| 
 | |
|                 bw.seek(12);
 | |
|                 bw.write_8(extent & 0x1f); /* EX */
 | |
|                 bw.write_8(0);             /* S1 */
 | |
|                 bw.write_8(extent >> 5);   /* S2 */
 | |
|                 bw.write_8(records);       /* RC */
 | |
| 
 | |
|                 /* Allocation map. */
 | |
| 
 | |
|                 switch (map_entry_size)
 | |
|                 {
 | |
|                     case 1:
 | |
|                         for (int i = 0; i < 16; i++)
 | |
|                             bw.write_8(allocation_map[i]);
 | |
|                         break;
 | |
| 
 | |
|                     case 2:
 | |
|                         for (int i = 0; i < 8; i++)
 | |
|                             bw.write_le16(allocation_map[i]);
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return bytes;
 | |
|         }
 | |
| 
 | |
|         void changeFilename(const std::string& filename)
 | |
|         {
 | |
|             static std::regex FORMATTER("(?:(1?[0-9]):)?([^ .]+)\\.?([^ .]*)");
 | |
|             std::smatch results;
 | |
|             bool matched = std::regex_match(filename, results, FORMATTER);
 | |
|             if (!matched)
 | |
|                 throw BadPathException();
 | |
| 
 | |
|             std::string user = results[1];
 | |
|             std::string stem = results[2];
 | |
|             std::string ext = results[3];
 | |
| 
 | |
|             if (stem.size() > 8)
 | |
|                 throw BadPathException();
 | |
|             if (ext.size() > 3)
 | |
|                 throw BadPathException();
 | |
| 
 | |
|             this->user = std::stoi(user);
 | |
|             if (this->user > 15)
 | |
|                 throw BadPathException();
 | |
| 
 | |
|             if (ext.empty())
 | |
|                 this->filename = stem;
 | |
|             else
 | |
|                 this->filename = fmt::format("{}.{}", stem, ext);
 | |
|         }
 | |
| 
 | |
|         std::string combinedFilename() const
 | |
|         {
 | |
|             return fmt::format("{}:{}", user, filename);
 | |
|         }
 | |
| 
 | |
|     public:
 | |
|         unsigned index;
 | |
|         std::string filename;
 | |
|         std::string mode;
 | |
|         unsigned user;
 | |
|         unsigned extent;
 | |
|         unsigned records;
 | |
|         std::vector<unsigned> allocation_map;
 | |
|         bool deleted = false;
 | |
|     };
 | |
| 
 | |
| public:
 | |
|     CpmFsFilesystem(
 | |
|         const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):
 | |
|         Filesystem(sectors),
 | |
|         _config(config)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     uint32_t capabilities() const override
 | |
|     {
 | |
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE | OP_DELETE |
 | |
|                OP_GETDIRENT | OP_CREATE | OP_MOVE | OP_PUTATTRS;
 | |
|     }
 | |
| 
 | |
|     std::map<std::string, std::string> getMetadata() override
 | |
|     {
 | |
|         mount();
 | |
| 
 | |
|         unsigned usedBlocks = _dirBlocks;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             auto entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
| 
 | |
|             for (unsigned block : entry->allocation_map)
 | |
|             {
 | |
|                 if (block)
 | |
|                     usedBlocks++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         std::map<std::string, std::string> attributes;
 | |
|         attributes[VOLUME_NAME] = "";
 | |
|         attributes[TOTAL_BLOCKS] = std::to_string(_filesystemBlocks);
 | |
|         attributes[USED_BLOCKS] = std::to_string(usedBlocks);
 | |
|         attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
 | |
|         return attributes;
 | |
|     }
 | |
| 
 | |
|     void create(bool, const std::string&) override
 | |
|     {
 | |
|         auto& start = _config.filesystem_start();
 | |
|         _filesystemStart =
 | |
|             getOffsetOfSector(start.track(), start.side(), start.sector());
 | |
|         _sectorSize = getLogicalSectorSize(start.track(), start.side());
 | |
| 
 | |
|         _directory = Bytes{0xe5} * (_config.dir_entries() * 32);
 | |
|         putCpmBlock(0, _directory);
 | |
|     }
 | |
| 
 | |
|     FilesystemStatus check() override
 | |
|     {
 | |
|         return FS_OK;
 | |
|     }
 | |
| 
 | |
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
 | |
|     {
 | |
|         mount();
 | |
|         if (!path.empty())
 | |
|             throw FileNotFoundException();
 | |
| 
 | |
|         std::map<std::string, std::shared_ptr<Dirent>> map;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             auto entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
| 
 | |
|             auto& dirent = map[entry->combinedFilename()];
 | |
|             if (!dirent)
 | |
|             {
 | |
|                 dirent = std::make_unique<Dirent>();
 | |
|                 dirent->filename = entry->combinedFilename();
 | |
|                 dirent->path = {dirent->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::shared_ptr<Dirent>> result;
 | |
|         for (auto& e : map)
 | |
|         {
 | |
|             auto& de = e.second;
 | |
|             de->attributes[FILENAME] = de->filename;
 | |
|             de->attributes[LENGTH] = std::to_string(de->length);
 | |
|             de->attributes[FILE_TYPE] = "file";
 | |
|             de->attributes[MODE] = de->mode;
 | |
|             result.push_back(std::move(de));
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     std::shared_ptr<Dirent> getDirent(const Path& path) override
 | |
|     {
 | |
|         mount();
 | |
|         if (path.size() != 1)
 | |
|             throw BadPathException();
 | |
| 
 | |
|         for (const auto& dirent : list(Path()))
 | |
|         {
 | |
|             if (dirent->filename == path.front())
 | |
|                 return dirent;
 | |
|         }
 | |
| 
 | |
|         throw FileNotFoundException();
 | |
|     }
 | |
| 
 | |
|     void putMetadata(const Path& path,
 | |
|         const std::map<std::string, std::string>& metadata) override
 | |
|     {
 | |
|         mount();
 | |
|         if (path.size() != 1)
 | |
|             throw BadPathException();
 | |
| 
 | |
|         /* Only updating MODE is supported. */
 | |
| 
 | |
|         if (metadata.empty())
 | |
|             return;
 | |
|         if ((metadata.size() != 1) || (metadata.begin()->first != MODE))
 | |
|             throw UnimplementedFilesystemException();
 | |
|         auto mode = metadata.begin()->second;
 | |
| 
 | |
|         /* Update all dirents corresponding to this file. */
 | |
| 
 | |
|         bool found = false;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             std::unique_ptr<Entry> entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
|             if (path[0] == entry->combinedFilename())
 | |
|             {
 | |
|                 entry->mode = mode;
 | |
|                 putEntry(entry);
 | |
|                 found = true;
 | |
|             }
 | |
|         }
 | |
|         if (!found)
 | |
|             throw FileNotFoundException();
 | |
| 
 | |
|         unmount();
 | |
|     }
 | |
| 
 | |
|     Bytes getFile(const Path& path) override
 | |
|     {
 | |
|         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->deleted)
 | |
|                     continue;
 | |
|                 if (path[0] != entry->combinedFilename())
 | |
|                     continue;
 | |
|                 if (entry->extent < logicalExtent)
 | |
|                     continue;
 | |
|                 if ((entry->extent & ~_logicalExtentMask) ==
 | |
|                     (logicalExtent & ~_logicalExtentMask))
 | |
|                     break;
 | |
|             }
 | |
| 
 | |
|             if (entry->deleted)
 | |
|             {
 | |
|                 if (logicalExtent == 0)
 | |
|                     throw FileNotFoundException();
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             /* Copy the data out. */
 | |
| 
 | |
|             int i =
 | |
|                 (logicalExtent & _logicalExtentMask) * _blocksPerLogicalExtent;
 | |
|             unsigned records =
 | |
|                 (entry->extent == logicalExtent) ? entry->records : 128;
 | |
|             while (records != 0)
 | |
|             {
 | |
|                 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;
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     void putFile(const Path& path, const Bytes& bytes) override
 | |
|     {
 | |
|         mount();
 | |
|         if (path.size() != 1)
 | |
|             throw BadPathException();
 | |
| 
 | |
|         /* Test to see if the file already exists. */
 | |
| 
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             std::unique_ptr<Entry> entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
|             if (path[0] == entry->combinedFilename())
 | |
|                 throw CannotWriteException();
 | |
|         }
 | |
| 
 | |
|         /* Write blocks, one at a time. */
 | |
| 
 | |
|         std::unique_ptr<Entry> entry;
 | |
|         ByteReader br(bytes);
 | |
|         while (!br.eof())
 | |
|         {
 | |
|             unsigned extent = br.pos / 0x4000;
 | |
|             Bytes block = br.read(_config.block_size());
 | |
| 
 | |
|             /* Allocate a block and write it. */
 | |
| 
 | |
|             auto bit = std::find(_bitmap.begin(), _bitmap.end(), false);
 | |
|             if (bit == _bitmap.end())
 | |
|                 throw DiskFullException();
 | |
|             *bit = true;
 | |
|             unsigned blocknum = bit - _bitmap.begin();
 | |
|             putCpmBlock(blocknum, block);
 | |
| 
 | |
|             /* Do we need a new directory entry? */
 | |
| 
 | |
|             if (!entry ||
 | |
|                 entry->allocation_map[std::size(entry->allocation_map) - 1])
 | |
|             {
 | |
|                 if (entry)
 | |
|                 {
 | |
|                     entry->records = 0x80;
 | |
|                     putEntry(entry);
 | |
|                 }
 | |
| 
 | |
|                 entry.reset();
 | |
|                 for (int d = 0; d < _config.dir_entries(); d++)
 | |
|                 {
 | |
|                     entry = getEntry(d);
 | |
|                     if (entry->deleted)
 | |
|                         break;
 | |
|                     entry.reset();
 | |
|                 }
 | |
| 
 | |
|                 if (!entry)
 | |
|                     throw DiskFullException();
 | |
|                 entry->deleted = false;
 | |
|                 entry->changeFilename(path[0]);
 | |
|                 entry->extent = extent;
 | |
|                 entry->mode = "";
 | |
|                 std::fill(entry->allocation_map.begin(),
 | |
|                     entry->allocation_map.end(),
 | |
|                     0);
 | |
|             }
 | |
| 
 | |
|             /* Hook up the block in the allocation map. */
 | |
| 
 | |
|             auto mit = std::find(
 | |
|                 entry->allocation_map.begin(), entry->allocation_map.end(), 0);
 | |
|             *mit = blocknum;
 | |
|         }
 | |
|         if (entry)
 | |
|         {
 | |
|             entry->records = ((bytes.size() & 0x3fff) + 127) / 128;
 | |
|             putEntry(entry);
 | |
|         }
 | |
| 
 | |
|         unmount();
 | |
|     }
 | |
| 
 | |
|     void moveFile(const Path& oldPath, const Path& newPath) override
 | |
|     {
 | |
|         mount();
 | |
|         if ((oldPath.size() != 1) || (newPath.size() != 1))
 | |
|             throw BadPathException();
 | |
| 
 | |
|         /* Check to make sure that the file exists, and that the new filename
 | |
|          * does not. */
 | |
| 
 | |
|         bool found = false;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             auto entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
| 
 | |
|             auto filename = entry->combinedFilename();
 | |
|             if (filename == oldPath[0])
 | |
|                 found = true;
 | |
|             if (filename == newPath[0])
 | |
|                 throw CannotWriteException();
 | |
|         }
 | |
|         if (!found)
 | |
|             throw FileNotFoundException();
 | |
| 
 | |
|         /* Now do the rename. */
 | |
| 
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             auto entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
| 
 | |
|             auto filename = entry->combinedFilename();
 | |
|             if (filename == oldPath[0])
 | |
|             {
 | |
|                 entry->changeFilename(newPath[0]);
 | |
|                 putEntry(entry);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         unmount();
 | |
|     }
 | |
| 
 | |
|     void deleteFile(const Path& path) override
 | |
|     {
 | |
|         mount();
 | |
|         if (path.size() != 1)
 | |
|             throw BadPathException();
 | |
| 
 | |
|         /* Remove all dirents for this file. */
 | |
| 
 | |
|         bool found = false;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             auto entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
|             if (path[0] != entry->combinedFilename())
 | |
|                 continue;
 | |
|             entry->deleted = true;
 | |
|             putEntry(entry);
 | |
|             found = true;
 | |
|         }
 | |
| 
 | |
|         if (!found)
 | |
|             throw FileNotFoundException();
 | |
|         unmount();
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     void mount() override
 | |
|     {
 | |
|         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;
 | |
|         }
 | |
|         _logicalExtentsPerEntry = physicalExtentSize / 16384;
 | |
|         _logicalExtentMask = _logicalExtentsPerEntry - 1;
 | |
|         _blocksPerLogicalExtent = 16384 / _config.block_size();
 | |
| 
 | |
|         _directory = getCpmBlock(0, _dirBlocks);
 | |
| 
 | |
|         /* Create the allocation bitmap. */
 | |
| 
 | |
|         _bitmap.clear();
 | |
|         _bitmap.resize(_filesystemBlocks);
 | |
|         for (int d = 0; d < _dirBlocks; d++)
 | |
|             _bitmap[d] = true;
 | |
|         for (int d = 0; d < _config.dir_entries(); d++)
 | |
|         {
 | |
|             std::unique_ptr<Entry> entry = getEntry(d);
 | |
|             if (entry->deleted)
 | |
|                 continue;
 | |
|             for (unsigned block : entry->allocation_map)
 | |
|             {
 | |
|                 if (block >= _filesystemBlocks)
 | |
|                     throw BadFilesystemException();
 | |
|                 if (block)
 | |
|                     _bitmap[block] = true;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void unmount()
 | |
|     {
 | |
|         putCpmBlock(0, _directory);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     std::unique_ptr<Entry> getEntry(unsigned d)
 | |
|     {
 | |
|         auto bytes = _directory.slice(d * 32, 32);
 | |
|         return std::make_unique<Entry>(bytes, _allocationMapSize, d);
 | |
|     }
 | |
| 
 | |
|     void putEntry(std::unique_ptr<Entry>& entry)
 | |
|     {
 | |
|         ByteWriter bw(_directory);
 | |
|         bw.seek(entry->index * 32);
 | |
|         bw.append(entry->toBytes(_allocationMapSize));
 | |
|     }
 | |
| 
 | |
|     unsigned computeSector(uint32_t block) const
 | |
|     {
 | |
|         unsigned sector = block * _blockSectors;
 | |
|         if (_config.has_padding())
 | |
|             sector += (sector / _config.padding().every()) *
 | |
|                       _config.padding().amount();
 | |
|         return sector;
 | |
|     }
 | |
| 
 | |
|     Bytes getCpmBlock(uint32_t block, uint32_t count = 1)
 | |
|     {
 | |
|         return getLogicalSector(
 | |
|             computeSector(block) + _filesystemStart, _blockSectors * count);
 | |
|     }
 | |
| 
 | |
|     void putCpmBlock(uint32_t block, const Bytes& bytes)
 | |
|     {
 | |
|         putLogicalSector(computeSector(block) + _filesystemStart, bytes);
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     std::vector<bool> getBitmapForDebugging() override
 | |
|     {
 | |
|         return _bitmap;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     const CpmFsProto& _config;
 | |
|     uint32_t _sectorSize;
 | |
|     uint32_t _blockSectors;
 | |
|     uint32_t _recordsPerBlock;
 | |
|     uint32_t _dirBlocks;
 | |
|     uint32_t _filesystemStart;
 | |
|     uint32_t _filesystemBlocks;
 | |
|     uint32_t _logicalExtentsPerEntry;
 | |
|     uint32_t _logicalExtentMask;
 | |
|     uint32_t _blocksPerLogicalExtent;
 | |
|     int _allocationMapSize;
 | |
|     Bytes _directory;
 | |
|     std::vector<bool> _bitmap;
 | |
| };
 | |
| 
 | |
| std::unique_ptr<Filesystem> Filesystem::createCpmFsFilesystem(
 | |
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
 | |
| {
 | |
|     return std::make_unique<CpmFsFilesystem>(config.cpmfs(), sectors);
 | |
| }
 |