From a6dda2b2c2299da63e5be8a6ae7eeee0bbe5d559 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 4 Feb 2026 16:04:59 +0000 Subject: [PATCH] [MicroDOS] Implement mount. --- Aaru.Filesystems/MicroDOS/Dir.cs | 72 ++++++ Aaru.Filesystems/MicroDOS/MicroDOS.cs | 34 +++ Aaru.Filesystems/MicroDOS/Mount.cs | 246 +++++++++++++++++++++ Aaru.Filesystems/MicroDOS/Unimplemented.cs | 10 - 4 files changed, 352 insertions(+), 10 deletions(-) create mode 100644 Aaru.Filesystems/MicroDOS/Dir.cs create mode 100644 Aaru.Filesystems/MicroDOS/Mount.cs diff --git a/Aaru.Filesystems/MicroDOS/Dir.cs b/Aaru.Filesystems/MicroDOS/Dir.cs new file mode 100644 index 000000000..e4b25f892 --- /dev/null +++ b/Aaru.Filesystems/MicroDOS/Dir.cs @@ -0,0 +1,72 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Dir.cs +// Author(s) : Natalia Portillo +// +// Component : MicroDOS filesystem plugin +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using Aaru.Helpers; +using Aaru.Logging; + +namespace Aaru.Filesystems; + +/// +public sealed partial class MicroDOS +{ + /// Processes a single directory entry and adds it to cache if valid + /// The directory entry to process + /// Counter for entries read (incremented for valid entries) + void ProcessDirectoryEntry(DirectoryEntry entry, ref int entriesRead) + { + // Skip deleted entries + if(entry.status == (byte)FileStatus.Deleted) return; + + // Skip BAD-file entries (they mark bad blocks) + if(entry.status == (byte)FileStatus.BadFile) return; + + // Skip subdirectory markers (first byte of filename = 0x7F) + // These are just directory names, not actual files + if(entry.filename is { Length: > 0 } && entry.filename[0] == SUBDIR_MARKER) return; + + // Skip entries not in root directory (directory byte != 0) + if(entry.directory != 0) return; + + // Get filename + string filename = StringHandlers.CToString(entry.filename, _encoding).Trim(); + + if(string.IsNullOrWhiteSpace(filename)) return; + + if(_rootDirectoryCache.TryAdd(filename, entry)) + { + AaruLogging.Debug(MODULE_NAME, + "Found '{0}' (block={1}, blocks={2}, length={3})", + filename, + entry.blockNo, + entry.blocks, + entry.length); + } + + entriesRead++; + } +} \ No newline at end of file diff --git a/Aaru.Filesystems/MicroDOS/MicroDOS.cs b/Aaru.Filesystems/MicroDOS/MicroDOS.cs index ade9e05bd..9a6908921 100644 --- a/Aaru.Filesystems/MicroDOS/MicroDOS.cs +++ b/Aaru.Filesystems/MicroDOS/MicroDOS.cs @@ -31,8 +31,10 @@ using System; using System.Collections.Generic; +using System.Text; using Aaru.CommonTypes.AaruMetadata; using Aaru.CommonTypes.Interfaces; +using Partition = Aaru.CommonTypes.Partition; namespace Aaru.Filesystems; @@ -43,6 +45,38 @@ namespace Aaru.Filesystems; /// public sealed partial class MicroDOS : IReadOnlyFilesystem { + const string MODULE_NAME = "MicroDOS plugin"; + + /// Block size in bytes + const int BLOCK_SIZE = 512; + + /// Directory entry size in bytes + const int DIR_ENTRY_SIZE = 24; + + /// Offset of first directory entry in block 0 + const int DIR_START_OFFSET = 320; + + /// Subdirectory marker (first byte of filename = 0x7F) + const byte SUBDIR_MARKER = 0x7F; + + /// Cached root directory entries (filename -> DirectoryEntry) + readonly Dictionary _rootDirectoryCache = new(); + + /// Cached superblock (block 0) + Block0 _block0; + + /// Encoding used for filenames (KOI8-R) + Encoding _encoding; + + /// Image plugin being accessed + IMediaImage _imagePlugin; + + /// Whether filesystem is mounted + bool _mounted; + + /// Partition being mounted + Partition _partition; + #region IFilesystem Members /// diff --git a/Aaru.Filesystems/MicroDOS/Mount.cs b/Aaru.Filesystems/MicroDOS/Mount.cs new file mode 100644 index 000000000..bdf518450 --- /dev/null +++ b/Aaru.Filesystems/MicroDOS/Mount.cs @@ -0,0 +1,246 @@ +// /*************************************************************************** +// Aaru Data Preservation Suite +// ---------------------------------------------------------------------------- +// +// Filename : Mount.cs +// Author(s) : Natalia Portillo +// +// Component : MicroDOS filesystem plugin +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2026 Natalia Portillo +// ****************************************************************************/ + +using System.Collections.Generic; +using System.Text; +using Aaru.CommonTypes.AaruMetadata; +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; +using Aaru.Logging; +using Partition = Aaru.CommonTypes.Partition; +using Marshal = Aaru.Helpers.Marshal; + +namespace Aaru.Filesystems; + +/// +public sealed partial class MicroDOS +{ + /// + public ErrorNumber Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding, + Dictionary options, string @namespace) + { + AaruLogging.Debug(MODULE_NAME, "Mounting volume"); + + _imagePlugin = imagePlugin; + _partition = partition; + + // MicroDOS uses KOI8-R encoding, register the encoding provider + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + _encoding = Encoding.GetEncoding("koi8-r"); + + // Read block 0 (superblock) + ErrorNumber errno = ReadSuperblock(); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading superblock: {0}", errno); + + return errno; + } + + AaruLogging.Debug(MODULE_NAME, "Superblock read successfully"); + + // Load the root directory + errno = LoadRootDirectory(); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error loading root directory: {0}", errno); + + return errno; + } + + AaruLogging.Debug(MODULE_NAME, + "Root directory loaded successfully with {0} entries", + _rootDirectoryCache.Count); + + Metadata = new FileSystem + { + Clusters = _block0.blocks, + ClusterSize = BLOCK_SIZE, + Type = FS_TYPE, + Files = _block0.files, + FreeClusters = (ulong)(_block0.blocks - _block0.usedBlocks) + }; + + _mounted = true; + + AaruLogging.Debug(MODULE_NAME, "Mount complete"); + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber Unmount() + { + if(!_mounted) return ErrorNumber.AccessDenied; + + AaruLogging.Debug(MODULE_NAME, "Unmounting volume"); + + _rootDirectoryCache.Clear(); + _mounted = false; + _imagePlugin = null; + _partition = default(Partition); + _block0 = default(Block0); + _encoding = null; + Metadata = null; + + AaruLogging.Debug(MODULE_NAME, "Volume unmounted successfully"); + + return ErrorNumber.NoError; + } + + /// Reads and validates the filesystem superblock (block 0) + /// Error code indicating success or failure + ErrorNumber ReadSuperblock() + { + AaruLogging.Debug(MODULE_NAME, "Reading superblock..."); + + // Read block 0 + ErrorNumber errno = _imagePlugin.ReadSector(_partition.Start, false, out byte[] block0Data, out _); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading block 0: {0}", errno); + + return errno; + } + + if(block0Data.Length < BLOCK_SIZE) + { + AaruLogging.Debug(MODULE_NAME, "Block 0 too small: {0} bytes", block0Data.Length); + + return ErrorNumber.InvalidArgument; + } + + _block0 = Marshal.ByteArrayToStructureLittleEndian(block0Data); + + // Validate magic numbers + if(_block0.label != MAGIC || _block0.mklabel != MAGIC2) + { + AaruLogging.Debug(MODULE_NAME, + "Invalid magic: label=0x{0:X4}, mklabel=0x{1:X4}", + _block0.label, + _block0.mklabel); + + return ErrorNumber.InvalidArgument; + } + + AaruLogging.Debug(MODULE_NAME, "blocks: {0}", _block0.blocks); + AaruLogging.Debug(MODULE_NAME, "files: {0}", _block0.files); + AaruLogging.Debug(MODULE_NAME, "usedBlocks: {0}", _block0.usedBlocks); + AaruLogging.Debug(MODULE_NAME, "firstUsedBlock: {0}", _block0.firstUsedBlock); + + AaruLogging.Debug(MODULE_NAME, "Superblock validated successfully"); + + return ErrorNumber.NoError; + } + + /// Loads the root directory and caches its contents + /// Error code indicating success or failure + ErrorNumber LoadRootDirectory() + { + AaruLogging.Debug(MODULE_NAME, "Loading root directory..."); + + _rootDirectoryCache.Clear(); + + // Read block 0 which contains the directory + ErrorNumber errno = _imagePlugin.ReadSector(_partition.Start, false, out byte[] block0Data, out _); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading block 0: {0}", errno); + + return errno; + } + + // Directory entries start at offset 320 (octal 500) in block 0 + // Each entry is 24 bytes (octal 30) + // Maximum entries in block 0: (512 - 320) / 24 = 8 entries in first block + + int entriesInBlock0 = (BLOCK_SIZE - DIR_START_OFFSET) / DIR_ENTRY_SIZE; + int totalEntries = _block0.files; + var entriesRead = 0; + + // Read entries from block 0 + for(var i = 0; i < entriesInBlock0 && entriesRead < totalEntries; i++) + { + int offset = DIR_START_OFFSET + i * DIR_ENTRY_SIZE; + + if(offset + DIR_ENTRY_SIZE > block0Data.Length) break; + + DirectoryEntry entry = + Marshal.ByteArrayToStructureLittleEndian(block0Data, offset, DIR_ENTRY_SIZE); + + ProcessDirectoryEntry(entry, ref entriesRead); + } + + // If we need more entries, read subsequent blocks + // Directory continues in blocks after block 0 + uint currentBlock = 1; + + while(entriesRead < totalEntries) + { + if(_partition.Start + currentBlock >= _partition.End) + { + AaruLogging.Debug(MODULE_NAME, "Reached end of partition while reading directory"); + + break; + } + + errno = _imagePlugin.ReadSector(_partition.Start + currentBlock, false, out byte[] blockData, out _); + + if(errno != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "Error reading block {0}: {1}", currentBlock, errno); + + break; + } + + int entriesPerBlock = BLOCK_SIZE / DIR_ENTRY_SIZE; + + for(var i = 0; i < entriesPerBlock && entriesRead < totalEntries; i++) + { + int offset = i * DIR_ENTRY_SIZE; + + if(offset + DIR_ENTRY_SIZE > blockData.Length) break; + + DirectoryEntry entry = + Marshal.ByteArrayToStructureLittleEndian(blockData, offset, DIR_ENTRY_SIZE); + + ProcessDirectoryEntry(entry, ref entriesRead); + } + + currentBlock++; + } + + AaruLogging.Debug(MODULE_NAME, "Cached {0} root directory entries", _rootDirectoryCache.Count); + + return ErrorNumber.NoError; + } +} \ No newline at end of file diff --git a/Aaru.Filesystems/MicroDOS/Unimplemented.cs b/Aaru.Filesystems/MicroDOS/Unimplemented.cs index 10c5564f5..6721affd1 100644 --- a/Aaru.Filesystems/MicroDOS/Unimplemented.cs +++ b/Aaru.Filesystems/MicroDOS/Unimplemented.cs @@ -28,25 +28,15 @@ using System; using System.Collections.Generic; -using System.Text; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; -using Partition = Aaru.CommonTypes.Partition; namespace Aaru.Filesystems; /// public sealed partial class MicroDOS { - /// - public ErrorNumber Mount(IMediaImage imagePlugin, Partition partition, Encoding encoding, - Dictionary options, string @namespace) => - throw new NotImplementedException(); - - /// - public ErrorNumber Unmount() => throw new NotImplementedException(); - /// public ErrorNumber GetAttributes(string path, out FileAttributes attributes) => throw new NotImplementedException();