From 08af6a28de37259eae2bd709b5b9cda82ba8db38 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sun, 1 Feb 2026 17:44:04 +0000 Subject: [PATCH] [HFS] Implemented file operations. --- Aaru.Filesystems/AppleHFS/File.cs | 155 +++++++++++++++++++++ Aaru.Filesystems/AppleHFS/Internal.cs | 29 ++++ Aaru.Filesystems/AppleHFS/Unimplemented.cs | 11 -- 3 files changed, 184 insertions(+), 11 deletions(-) diff --git a/Aaru.Filesystems/AppleHFS/File.cs b/Aaru.Filesystems/AppleHFS/File.cs index 5f02954c9..f670c0a20 100644 --- a/Aaru.Filesystems/AppleHFS/File.cs +++ b/Aaru.Filesystems/AppleHFS/File.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Helpers; using Aaru.Logging; @@ -44,6 +45,160 @@ namespace Aaru.Filesystems; // https://developer.apple.com/legacy/library/documentation/mac/pdf/Files/File_Manager.pdf public sealed partial class AppleHFS { + /// + public ErrorNumber OpenFile(string path, out IFileNode node) + { + node = null; + + if(!_mounted) return ErrorNumber.AccessDenied; + + // Get the file entry + ErrorNumber err = GetFileEntry(path, out CatalogEntry entry); + + if(err != ErrorNumber.NoError) return err; + + if(entry is not FileEntry fileEntry) return ErrorNumber.IsDirectory; + + // We'll open the data fork by default + node = new HfsFileNode + { + Path = path, + Length = fileEntry.DataForkLogicalSize, + Offset = 0, + FileEntry = fileEntry, + ForkType = ForkType.Data, + FirstExtents = fileEntry.DataForkExtents, + AllExtents = null // Will be lazily loaded on first read if needed + }; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber CloseFile(IFileNode node) + { + if(!_mounted) return ErrorNumber.AccessDenied; + + if(node is not HfsFileNode myNode) return ErrorNumber.InvalidArgument; + + // Clear references + myNode.FileEntry = null; + myNode.AllExtents = null; + + return ErrorNumber.NoError; + } + + /// + public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) + { + read = 0; + + if(!_mounted) return ErrorNumber.AccessDenied; + + if(buffer is null || buffer.Length < length) return ErrorNumber.InvalidArgument; + + if(node is not HfsFileNode myNode) return ErrorNumber.InvalidArgument; + + // Clamp read length to remaining file size + if(myNode.Offset + length > myNode.Length) length = myNode.Length - myNode.Offset; + + if(length <= 0) return ErrorNumber.NoError; + + // Lazy load extents if not already loaded + if(myNode.AllExtents == null) + { + ErrorNumber extentErr = GetFileExtents(myNode.FileEntry.CNID, + myNode.ForkType, + myNode.FirstExtents, + out List allExtents); + + if(extentErr != ErrorNumber.NoError) return extentErr; + + myNode.AllExtents = allExtents ?? []; + } + + if(myNode.AllExtents.Count == 0) + { + if(length > 0) return ErrorNumber.InvalidArgument; + + return ErrorNumber.NoError; + } + + // Find the starting extent and offset within that extent + long bytesProcessed = 0; + long currentOffset = myNode.Offset; + long bytesToRead = length; + var bufferPos = 0; + + foreach(ExtDescriptor extent in myNode.AllExtents) + { + if(extent.xdrNumABlks == 0) break; + + uint extentSizeBytes = extent.xdrNumABlks * _mdb.drAlBlkSiz; + + // Skip extents that are entirely before our current offset + if(currentOffset >= extentSizeBytes) + { + currentOffset -= extentSizeBytes; + + continue; + } + + // Calculate how much to read from this extent + var offsetInExtent = (uint)currentOffset; + var toReadFromExtent = (uint)Math.Min(extentSizeBytes - offsetInExtent, bytesToRead); + + // Calculate sector info for this extent + ulong sector = (ulong)(extent.xdrStABN * _mdb.drAlBlkSiz / _sectorSize) + offsetInExtent / _sectorSize; + uint sectorOffset = offsetInExtent % _sectorSize; + uint sectorCount = (toReadFromExtent + sectorOffset + _sectorSize - 1) / _sectorSize; + + AaruLogging.Debug(MODULE_NAME, + "ReadFile: Reading extent at block={0}, offset={1}, sectorCount={2}, toRead={3}", + extent.xdrStABN, + offsetInExtent, + sectorCount, + toReadFromExtent); + + ErrorNumber readErr = _imagePlugin.ReadSectors(sector, false, sectorCount, out byte[] sectorData, out _); + + if(readErr != ErrorNumber.NoError) + { + AaruLogging.Debug(MODULE_NAME, "ReadFile: Failed to read sectors: {0}", readErr); + + return readErr; + } + + if(sectorData == null || sectorData.Length == 0) + { + AaruLogging.Debug(MODULE_NAME, "ReadFile: Got empty sector data"); + + return ErrorNumber.InvalidArgument; + } + + // Copy data from sector buffer to output buffer, accounting for offset + uint bytesToCopy = Math.Min(toReadFromExtent, (uint)(sectorData.Length - sectorOffset)); + + Array.Copy(sectorData, sectorOffset, buffer, bufferPos, bytesToCopy); + + bufferPos += (int)bytesToCopy; + bytesProcessed += bytesToCopy; + bytesToRead -= bytesToCopy; + + // Reset offset for next extent + currentOffset = 0; + + if(bytesToRead <= 0) break; + } + + read = bytesProcessed; + myNode.Offset += bytesProcessed; + + AaruLogging.Debug(MODULE_NAME, "ReadFile: Read {0} bytes, new offset={1}", bytesProcessed, myNode.Offset); + + return ErrorNumber.NoError; + } + /// public ErrorNumber GetAttributes(string path, out FileAttributes attributes) { diff --git a/Aaru.Filesystems/AppleHFS/Internal.cs b/Aaru.Filesystems/AppleHFS/Internal.cs index dfb00d05c..2db648bbf 100644 --- a/Aaru.Filesystems/AppleHFS/Internal.cs +++ b/Aaru.Filesystems/AppleHFS/Internal.cs @@ -30,12 +30,41 @@ // These are not part of the Inside Macintosh specification and are // implementation-specific to the Aaru HFS plugin. +using System.Collections.Generic; using Aaru.CommonTypes.Interfaces; namespace Aaru.Filesystems; public sealed partial class AppleHFS { + /// FileNode implementation for Apple HFS + sealed class HfsFileNode : IFileNode + { + /// Cached list of all extents (including overflow) + internal List AllExtents; + /// The file entry containing extent information + internal FileEntry FileEntry; + + /// First three extents of the fork + internal ExtDataRec FirstExtents; + + /// Type of fork (Data or Resource) + internal ForkType ForkType; + +#region IFileNode Members + + /// + public string Path { get; init; } + + /// + public long Length { get; init; } + + /// + public long Offset { get; set; } + +#endregion + } + /// DirNode implementation for Apple HFS sealed class HfsDirNode : IDirNode { diff --git a/Aaru.Filesystems/AppleHFS/Unimplemented.cs b/Aaru.Filesystems/AppleHFS/Unimplemented.cs index aa34fee63..584acbf53 100644 --- a/Aaru.Filesystems/AppleHFS/Unimplemented.cs +++ b/Aaru.Filesystems/AppleHFS/Unimplemented.cs @@ -33,7 +33,6 @@ using System; using Aaru.CommonTypes.Enums; -using Aaru.CommonTypes.Interfaces; namespace Aaru.Filesystems; @@ -46,14 +45,4 @@ public sealed partial class AppleHFS /// public ErrorNumber ReadLink(string path, out string dest) => throw new NotImplementedException(); - - /// - public ErrorNumber OpenFile(string path, out IFileNode node) => throw new NotImplementedException(); - - /// - public ErrorNumber CloseFile(IFileNode node) => throw new NotImplementedException(); - - /// - public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) => - throw new NotImplementedException(); } \ No newline at end of file