From c75a32a5c899d1e3bdc3ef1f31fdf30d48bd20ef Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 21 Dec 2022 19:10:51 +0000 Subject: [PATCH] Add OpenDir method to IReadOnlyFilesystem. --- Aaru.CommonTypes | 2 +- Aaru.Filesystems/AppleDOS/Dir.cs | 34 ++ Aaru.Filesystems/AppleDOS/Structs.cs | 8 + Aaru.Filesystems/AppleMFS/Dir.cs | 37 ++ Aaru.Filesystems/AppleMFS/Structs.cs | 9 + Aaru.Filesystems/CPM/Dir.cs | 23 ++ Aaru.Filesystems/CPM/Structs.cs | 8 + Aaru.Filesystems/FAT/Dir.cs | 362 +++++++++++++++++++ Aaru.Filesystems/FAT/Structs.cs | 9 +- Aaru.Filesystems/FATX/Dir.cs | 130 +++++++ Aaru.Filesystems/FATX/Structs.cs | 8 + Aaru.Filesystems/ISO9660/Dir.cs | 110 +++++- Aaru.Filesystems/ISO9660/Structs/Internal.cs | 8 + Aaru.Filesystems/LisaFS/Dir.cs | 44 +++ Aaru.Filesystems/LisaFS/Structs.cs | 9 +- Aaru.Filesystems/Opera/Dir.cs | 92 +++++ Aaru.Filesystems/Opera/Structs.cs | 8 + Aaru.Filesystems/UCSDPascal/Dir.cs | 34 ++ Aaru.Filesystems/UCSDPascal/Structs.cs | 8 + 19 files changed, 939 insertions(+), 4 deletions(-) diff --git a/Aaru.CommonTypes b/Aaru.CommonTypes index 83d7e34f1..719613f8e 160000 --- a/Aaru.CommonTypes +++ b/Aaru.CommonTypes @@ -1 +1 @@ -Subproject commit 83d7e34f1bc52ac7811e67fa7aaf854be7989a4a +Subproject commit 719613f8e25fa8e3b06bf9371a22ddddf8041c21 diff --git a/Aaru.Filesystems/AppleDOS/Dir.cs b/Aaru.Filesystems/AppleDOS/Dir.cs index 4a0a53516..0a4a5a9ec 100644 --- a/Aaru.Filesystems/AppleDOS/Dir.cs +++ b/Aaru.Filesystems/AppleDOS/Dir.cs @@ -35,6 +35,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; @@ -49,6 +50,39 @@ public sealed partial class AppleDOS return !_mounted ? ErrorNumber.AccessDenied : ErrorNumber.NotSupported; } + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(!string.IsNullOrEmpty(path) && + string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return ErrorNumber.NotSupported; + + List contents = _catalogCache.Keys.ToList(); + + if(_debug) + { + contents.Add("$"); + contents.Add("$Boot"); + contents.Add("$Vtoc"); + } + + contents.Sort(); + + node = new AppleDosDirNode + { + Path = path, + _position = 0, + _contents = contents.ToArray() + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/AppleDOS/Structs.cs b/Aaru.Filesystems/AppleDOS/Structs.cs index 5655dd5b1..f23333ee5 100644 --- a/Aaru.Filesystems/AppleDOS/Structs.cs +++ b/Aaru.Filesystems/AppleDOS/Structs.cs @@ -114,4 +114,12 @@ public sealed partial class AppleDOS /// public long Offset { get; set; } } + + sealed class AppleDosDirNode : IDirNode + { + internal string[] _contents; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/AppleMFS/Dir.cs b/Aaru.Filesystems/AppleMFS/Dir.cs index 90b8303c3..74724acad 100644 --- a/Aaru.Filesystems/AppleMFS/Dir.cs +++ b/Aaru.Filesystems/AppleMFS/Dir.cs @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Console; using Aaru.Helpers; @@ -38,6 +39,42 @@ namespace Aaru.Filesystems; // Information from Inside Macintosh Volume II public sealed partial class AppleMFS { + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(!string.IsNullOrEmpty(path) && + string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return ErrorNumber.NotSupported; + + List contents = _idToFilename.Select(kvp => kvp.Value).ToList(); + + if(_debug) + { + contents.Add("$"); + contents.Add("$Bitmap"); + contents.Add("$MDB"); + + if(_bootBlocks != null) + contents.Add("$Boot"); + } + + contents.Sort(); + + node = new AppleMfsDirNode + { + Path = path, + _position = 0, + contents = contents.ToArray() + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/AppleMFS/Structs.cs b/Aaru.Filesystems/AppleMFS/Structs.cs index 782fa4ec6..3c6b23a21 100644 --- a/Aaru.Filesystems/AppleMFS/Structs.cs +++ b/Aaru.Filesystems/AppleMFS/Structs.cs @@ -119,4 +119,13 @@ public sealed partial class AppleMFS /// public long Offset { get; set; } } + + sealed class AppleMfsDirNode : IDirNode + { + internal string[] contents; + + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/CPM/Dir.cs b/Aaru.Filesystems/CPM/Dir.cs index a0e23359d..ddc9af591 100644 --- a/Aaru.Filesystems/CPM/Dir.cs +++ b/Aaru.Filesystems/CPM/Dir.cs @@ -34,12 +34,35 @@ using System; using System.Collections.Generic; using System.Text; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; public sealed partial class CPM { + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(!string.IsNullOrEmpty(path) && + string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return ErrorNumber.NotSupported; + + node = new CpmDirNode + { + Path = path, + _position = 0, + _contents = _dirList.ToArray() + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/CPM/Structs.cs b/Aaru.Filesystems/CPM/Structs.cs index c0f9e339d..109ba4048 100644 --- a/Aaru.Filesystems/CPM/Structs.cs +++ b/Aaru.Filesystems/CPM/Structs.cs @@ -378,4 +378,12 @@ public sealed partial class CPM /// public long Offset { get; set; } } + + sealed class CpmDirNode : IDirNode + { + internal string[] _contents; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/FAT/Dir.cs b/Aaru.Filesystems/FAT/Dir.cs index 88ba5bc6b..62dfb916e 100644 --- a/Aaru.Filesystems/FAT/Dir.cs +++ b/Aaru.Filesystems/FAT/Dir.cs @@ -32,6 +32,7 @@ using System.Globalization; using System.Linq; using System.Text; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Console; using Aaru.Helpers; @@ -50,6 +51,367 @@ public sealed partial class FAT return ErrorNumber.NotSupported; } + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(string.IsNullOrWhiteSpace(path) || + path == "/") + { + node = new FatDirNode + { + Path = path, + _position = 0, + _entries = _rootDirectoryCache.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string cutPath = path.StartsWith("/", StringComparison.Ordinal) ? path[1..].ToLower(_cultureInfo) + : path.ToLower(_cultureInfo); + + if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + { + node = new FatDirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string[] pieces = cutPath.Split(new[] + { + '/' + }, StringSplitOptions.RemoveEmptyEntries); + + KeyValuePair entry = + _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[0]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory)) + return ErrorNumber.NotDirectory; + + string currentPath = pieces[0]; + + currentDirectory = _rootDirectoryCache; + + for(int p = 0; p < pieces.Length; p++) + { + entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[p]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.Dirent.attributes.HasFlag(FatAttributes.Subdirectory)) + return ErrorNumber.NotDirectory; + + currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; + uint currentCluster = entry.Value.Dirent.start_cluster; + + if(_fat32) + currentCluster += (uint)(entry.Value.Dirent.ea_handle << 16); + + if(_directoryCache.TryGetValue(currentPath, out currentDirectory)) + continue; + + // Reserved unallocated directory, seen in Atari ST + if(currentCluster == 0) + { + _directoryCache[currentPath] = new Dictionary(); + + node = new FatDirNode + { + Path = path, + _position = 0, + _entries = Array.Empty() + }; + + return ErrorNumber.NoError; + } + + uint[] clusters = GetClusters(currentCluster); + + if(clusters is null) + return ErrorNumber.InvalidArgument; + + byte[] directoryBuffer = new byte[_bytesPerCluster * clusters.Length]; + + for(int i = 0; i < clusters.Length; i++) + { + ErrorNumber errno = _image.ReadSectors(_firstClusterSector + (clusters[i] * _sectorsPerCluster), + _sectorsPerCluster, out byte[] buffer); + + if(errno != ErrorNumber.NoError) + return errno; + + Array.Copy(buffer, 0, directoryBuffer, i * _bytesPerCluster, _bytesPerCluster); + } + + currentDirectory = new Dictionary(); + byte[] lastLfnName = null; + byte lastLfnChecksum = 0; + + for(int pos = 0; pos < directoryBuffer.Length; pos += Marshal.SizeOf()) + { + DirectoryEntry dirent = + Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos, + Marshal.SizeOf()); + + if(dirent.filename[0] == DIRENT_FINISHED) + break; + + if(dirent.attributes.HasFlag(FatAttributes.LFN)) + { + if(_namespace != Namespace.Lfn && + _namespace != Namespace.Ecs) + continue; + + LfnEntry lfnEntry = + Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos, + Marshal.SizeOf()); + + int lfnSequence = lfnEntry.sequence & LFN_MASK; + + if((lfnEntry.sequence & LFN_ERASED) > 0) + continue; + + if((lfnEntry.sequence & LFN_LAST) > 0) + { + lastLfnName = new byte[lfnSequence * 26]; + lastLfnChecksum = lfnEntry.checksum; + } + + if(lastLfnName is null) + continue; + + if(lfnEntry.checksum != lastLfnChecksum) + continue; + + lfnSequence--; + + Array.Copy(lfnEntry.name1, 0, lastLfnName, lfnSequence * 26, 10); + Array.Copy(lfnEntry.name2, 0, lastLfnName, (lfnSequence * 26) + 10, 12); + Array.Copy(lfnEntry.name3, 0, lastLfnName, (lfnSequence * 26) + 22, 4); + + continue; + } + + // Not a correct entry + if(dirent.filename[0] < DIRENT_MIN && + dirent.filename[0] != DIRENT_E5) + continue; + + // Self + if(_encoding.GetString(dirent.filename).TrimEnd() == ".") + continue; + + // Parent + if(_encoding.GetString(dirent.filename).TrimEnd() == "..") + continue; + + // Deleted + if(dirent.filename[0] == DIRENT_DELETED) + continue; + + string filename; + + if(dirent.attributes.HasFlag(FatAttributes.VolumeLabel)) + continue; + + var completeEntry = new CompleteDirectoryEntry + { + Dirent = dirent + }; + + if(_namespace is Namespace.Lfn or Namespace.Ecs && + lastLfnName != null) + { + byte calculatedLfnChecksum = LfnChecksum(dirent.filename, dirent.extension); + + if(calculatedLfnChecksum == lastLfnChecksum) + { + filename = StringHandlers.CToString(lastLfnName, Encoding.Unicode, true); + + completeEntry.Lfn = filename; + lastLfnName = null; + lastLfnChecksum = 0; + } + } + + if(dirent.filename[0] == DIRENT_E5) + dirent.filename[0] = DIRENT_DELETED; + + string name = _encoding.GetString(dirent.filename).TrimEnd(); + string extension = _encoding.GetString(dirent.extension).TrimEnd(); + + if(name == "" && + extension == "") + { + AaruConsole.DebugWriteLine("FAT filesystem", Localization.Found_empty_filename_in_0, path); + + if(!_debug || + dirent is { size: > 0, start_cluster: 0 }) + continue; // Skip invalid name + + // If debug, add it + name = ":{EMPTYNAME}:"; + + // Try to create a unique filename with an extension from 000 to 999 + for(int uniq = 0; uniq < 1000; uniq++) + { + extension = $"{uniq:D03}"; + + if(!currentDirectory.ContainsKey($"{name}.{extension}")) + break; + } + + // If we couldn't find it, just skip over + if(currentDirectory.ContainsKey($"{name}.{extension}")) + continue; + } + + if(_namespace == Namespace.Nt) + { + if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseExtension)) + extension = extension.ToLower(CultureInfo.CurrentCulture); + + if(dirent.caseinfo.HasFlag(CaseInfo.LowerCaseBasename)) + name = name.ToLower(CultureInfo.CurrentCulture); + } + + if(extension != "") + filename = name + "." + extension; + else + filename = name; + + if(_namespace == Namespace.Human) + { + HumanDirectoryEntry humanEntry = + Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, pos, + Marshal.SizeOf()); + + completeEntry.HumanDirent = humanEntry; + + name = StringHandlers.CToString(humanEntry.name1, _encoding).TrimEnd(); + extension = StringHandlers.CToString(humanEntry.extension, _encoding).TrimEnd(); + string name2 = StringHandlers.CToString(humanEntry.name2, _encoding).TrimEnd(); + + if(extension != "") + filename = name + name2 + "." + extension; + else + filename = name + name2; + + completeEntry.HumanName = filename; + } + + // Atari ST allows slash AND colon so cannot simply substitute one for the other like in Mac filesystems + filename = filename.Replace('/', '\u2215'); + + // Using array accessor ensures that repeated entries just get substituted. + // Repeated entries are not allowed but some bad implementations (e.g. FAT32.IFS)allow to create them + // when using spaces + completeEntry.Shortname = filename; + currentDirectory[completeEntry.ToString()] = completeEntry; + } + + // Check OS/2 .LONGNAME + if(_eaCache != null && + _namespace is Namespace.Os2 or Namespace.Ecs && + !_fat32) + { + List> filesWithEas = + currentDirectory.Where(t => t.Value.Dirent.ea_handle != 0).ToList(); + + foreach(KeyValuePair fileWithEa in filesWithEas) + { + Dictionary eas = GetEas(fileWithEa.Value.Dirent.ea_handle); + + if(eas is null) + continue; + + if(!eas.TryGetValue("com.microsoft.os2.longname", out byte[] longnameEa)) + continue; + + if(BitConverter.ToUInt16(longnameEa, 0) != EAT_ASCII) + continue; + + ushort longnameSize = BitConverter.ToUInt16(longnameEa, 2); + + if(longnameSize + 4 > longnameEa.Length) + continue; + + byte[] longnameBytes = new byte[longnameSize]; + + Array.Copy(longnameEa, 4, longnameBytes, 0, longnameSize); + + string longname = StringHandlers.CToString(longnameBytes, _encoding); + + if(string.IsNullOrWhiteSpace(longname)) + continue; + + // Forward slash is allowed in .LONGNAME, so change it to visually similar division slash + longname = longname.Replace('/', '\u2215'); + + fileWithEa.Value.Longname = longname; + currentDirectory.Remove(fileWithEa.Key); + currentDirectory[fileWithEa.Value.ToString()] = fileWithEa.Value; + } + } + + // Check FAT32.IFS EAs + if(_fat32 || _debug) + { + List> fat32EaSidecars = + currentDirectory.Where(t => t.Key.EndsWith(FAT32_EA_TAIL, true, _cultureInfo)).ToList(); + + foreach(KeyValuePair sidecar in fat32EaSidecars) + { + // No real file this sidecar accompanies + if(!currentDirectory.TryGetValue(sidecar.Key[..^FAT32_EA_TAIL.Length], + out CompleteDirectoryEntry fileWithEa)) + continue; + + // If not in debug mode we will consider the lack of EA bitflags to mean the EAs are corrupted or not real + if(!_debug) + if(!fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEaOld) && + !fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa) && + !fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.NormalEa) && + !fileWithEa.Dirent.caseinfo.HasFlag(CaseInfo.CriticalEa)) + continue; + + fileWithEa.Fat32Ea = sidecar.Value.Dirent; + + if(!_debug) + currentDirectory.Remove(sidecar.Key); + } + } + + _directoryCache.Add(currentPath, currentDirectory); + } + + if(currentDirectory is null) + return ErrorNumber.NoSuchFile; + + node = new FatDirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + /// /// Lists contents from a directory. /// Directory path. diff --git a/Aaru.Filesystems/FAT/Structs.cs b/Aaru.Filesystems/FAT/Structs.cs index 15abef2a0..8deeedca9 100644 --- a/Aaru.Filesystems/FAT/Structs.cs +++ b/Aaru.Filesystems/FAT/Structs.cs @@ -987,13 +987,20 @@ public sealed partial class FAT sealed class FatFileNode : IFileNode { + internal uint[] _clusters; /// public string Path { get; init; } /// public long Length { get; init; } /// public long Offset { get; set; } + } - internal uint[] _clusters; + sealed class FatDirNode : IDirNode + { + internal CompleteDirectoryEntry[] _entries; + internal int _position; + /// + public string Path { get; init; } } } \ No newline at end of file diff --git a/Aaru.Filesystems/FATX/Dir.cs b/Aaru.Filesystems/FATX/Dir.cs index d9d21499c..2b2838b73 100644 --- a/Aaru.Filesystems/FATX/Dir.cs +++ b/Aaru.Filesystems/FATX/Dir.cs @@ -30,12 +30,142 @@ using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; public sealed partial class XboxFatPlugin { + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(string.IsNullOrWhiteSpace(path) || + path == "/") + { + node = new FatxDirNode + { + Path = path, + _position = 0, + _entries = _rootDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string cutPath = path.StartsWith('/') ? path[1..].ToLower(_cultureInfo) : path.ToLower(_cultureInfo); + + if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + { + node = new FatxDirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string[] pieces = cutPath.Split(new[] + { + '/' + }, StringSplitOptions.RemoveEmptyEntries); + + KeyValuePair entry = + _rootDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[0]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.attributes.HasFlag(Attributes.Directory)) + return ErrorNumber.NotDirectory; + + string currentPath = pieces[0]; + + currentDirectory = _rootDirectory; + + for(int p = 0; p < pieces.Length; p++) + { + entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(_cultureInfo) == pieces[p]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.attributes.HasFlag(Attributes.Directory)) + return ErrorNumber.NotDirectory; + + currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; + uint currentCluster = entry.Value.firstCluster; + + if(_directoryCache.TryGetValue(currentPath, out currentDirectory)) + continue; + + uint[] clusters = GetClusters(currentCluster); + + if(clusters is null) + return ErrorNumber.InvalidArgument; + + byte[] directoryBuffer = new byte[_bytesPerCluster * clusters.Length]; + + for(int i = 0; i < clusters.Length; i++) + { + ErrorNumber errno = + _imagePlugin.ReadSectors(_firstClusterSector + ((clusters[i] - 1) * _sectorsPerCluster), + _sectorsPerCluster, out byte[] buffer); + + if(errno != ErrorNumber.NoError) + return errno; + + Array.Copy(buffer, 0, directoryBuffer, i * _bytesPerCluster, _bytesPerCluster); + } + + currentDirectory = new Dictionary(); + + int pos = 0; + + while(pos < directoryBuffer.Length) + { + DirectoryEntry dirent = _littleEndian + ? Marshal.ByteArrayToStructureLittleEndian(directoryBuffer, + pos, Marshal.SizeOf()) + : Marshal.ByteArrayToStructureBigEndian(directoryBuffer, + pos, Marshal.SizeOf()); + + pos += Marshal.SizeOf(); + + if(dirent.filenameSize is UNUSED_DIRENTRY or FINISHED_DIRENTRY) + break; + + if(dirent.filenameSize is DELETED_DIRENTRY or > MAX_FILENAME) + continue; + + string filename = _encoding.GetString(dirent.filename, 0, dirent.filenameSize); + + currentDirectory.Add(filename, dirent); + } + + _directoryCache.Add(currentPath, currentDirectory); + } + + if(currentDirectory is null) + return ErrorNumber.NoSuchFile; + + node = new FatxDirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/FATX/Structs.cs b/Aaru.Filesystems/FATX/Structs.cs index cfee20e87..e97d089ca 100644 --- a/Aaru.Filesystems/FATX/Structs.cs +++ b/Aaru.Filesystems/FATX/Structs.cs @@ -73,4 +73,12 @@ public sealed partial class XboxFatPlugin /// public long Offset { get; set; } } + + sealed class FatxDirNode : IDirNode + { + internal DirectoryEntry[] _entries; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/ISO9660/Dir.cs b/Aaru.Filesystems/ISO9660/Dir.cs index 26d024225..00c4a7a4e 100644 --- a/Aaru.Filesystems/ISO9660/Dir.cs +++ b/Aaru.Filesystems/ISO9660/Dir.cs @@ -34,6 +34,7 @@ using System.IO; using System.Linq; using System.Text; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; @@ -42,6 +43,112 @@ public sealed partial class ISO9660 { Dictionary> _directoryCache; + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(string.IsNullOrWhiteSpace(path) || + path == "/") + { + node = new Iso9660DirNode + { + Path = path, + _position = 0, + _entries = _rootDirectoryCache.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string cutPath = path.StartsWith("/", StringComparison.Ordinal) + ? path[1..].ToLower(CultureInfo.CurrentUICulture) + : path.ToLower(CultureInfo.CurrentUICulture); + + if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + { + node = new Iso9660DirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + + string[] pieces = cutPath.Split(new[] + { + '/' + }, StringSplitOptions.RemoveEmptyEntries); + + KeyValuePair entry = + _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[0]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) + return ErrorNumber.NotDirectory; + + string currentPath = pieces[0]; + + currentDirectory = _rootDirectoryCache; + + for(int p = 0; p < pieces.Length; p++) + { + entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[p]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) + return ErrorNumber.NotDirectory; + + currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; + + if(_directoryCache.TryGetValue(currentPath, out currentDirectory)) + continue; + + if(entry.Value.Extents.Count == 0) + return ErrorNumber.InvalidArgument; + + currentDirectory = _cdi + ? DecodeCdiDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength, + entry.Value.Extents[0].size) + : _highSierra + ? DecodeHighSierraDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength, + entry.Value.Extents[0].size) + : DecodeIsoDirectory(entry.Value.Extents[0].extent + entry.Value.XattrLength, + entry.Value.Extents[0].size); + + if(_usePathTable) + foreach(DecodedDirectoryEntry subDirectory in _cdi + ? GetSubdirsFromCdiPathTable(currentPath) + : _highSierra + ? GetSubdirsFromHighSierraPathTable(currentPath) + : GetSubdirsFromIsoPathTable(currentPath)) + currentDirectory[subDirectory.Filename] = subDirectory; + + _directoryCache.Add(currentPath, currentDirectory); + } + + if(currentDirectory is null) + return ErrorNumber.NoSuchFile; + + node = new Iso9660DirNode + { + Path = path, + _position = 0, + _entries = currentDirectory.Values.ToArray() + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { @@ -983,7 +1090,8 @@ public sealed partial class ISO9660 errno = ReadSingleExtent(ca.offset, ca.ca_length, ca.block, out byte[] caData); // TODO: Check continuation area definition, this is not a proper fix - if(errno == ErrorNumber.NoError && caData.Length > 0) + if(errno == ErrorNumber.NoError && + caData.Length > 0) DecodeSystemArea(caData, 0, (int)ca.ca_length, ref entry, out hasResourceFork); systemAreaOff += ceLength; diff --git a/Aaru.Filesystems/ISO9660/Structs/Internal.cs b/Aaru.Filesystems/ISO9660/Structs/Internal.cs index 003119e4c..21a3aff72 100644 --- a/Aaru.Filesystems/ISO9660/Structs/Internal.cs +++ b/Aaru.Filesystems/ISO9660/Structs/Internal.cs @@ -114,4 +114,12 @@ public sealed partial class ISO9660 /// public long Offset { get; set; } } + + sealed class Iso9660DirNode : IDirNode + { + internal DecodedDirectoryEntry[] _entries; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/LisaFS/Dir.cs b/Aaru.Filesystems/LisaFS/Dir.cs index 5ee672920..6cbd28fad 100644 --- a/Aaru.Filesystems/LisaFS/Dir.cs +++ b/Aaru.Filesystems/LisaFS/Dir.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; using Aaru.Decoders; using Aaru.Helpers; @@ -51,6 +52,49 @@ public sealed partial class LisaFS return ErrorNumber.NotSupported; } + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + ErrorNumber error = LookupFileId(path, out short fileId, out bool isDir); + + if(error != ErrorNumber.NoError) + return error; + + if(!isDir) + return ErrorNumber.NotDirectory; + + /*List catalog; + error = ReadCatalog(fileId, out catalog); + if(error != ErrorNumber.NoError) + return error;*/ + + ReadDir(fileId, out List contents); + + // On debug add system files as readable files + // Syntax similar to NTFS + if(_debug && fileId == DIRID_ROOT) + { + contents.Add("$MDDF"); + contents.Add("$Boot"); + contents.Add("$Loader"); + contents.Add("$Bitmap"); + contents.Add("$S-Record"); + contents.Add("$"); + } + + contents.Sort(); + + node = new LisaDirNode + { + Path = path, + _contents = contents.ToArray(), + _position = 0 + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/LisaFS/Structs.cs b/Aaru.Filesystems/LisaFS/Structs.cs index bc33dbeb2..d3af653dc 100644 --- a/Aaru.Filesystems/LisaFS/Structs.cs +++ b/Aaru.Filesystems/LisaFS/Structs.cs @@ -401,13 +401,20 @@ public sealed partial class LisaFS sealed class LisaFileNode : IFileNode { + internal short _fileId; /// public string Path { get; init; } /// public long Length { get; init; } /// public long Offset { get; set; } + } - internal short _fileId; + sealed class LisaDirNode : IDirNode + { + internal string[] _contents; + internal int _position; + /// + public string Path { get; init; } } } \ No newline at end of file diff --git a/Aaru.Filesystems/Opera/Dir.cs b/Aaru.Filesystems/Opera/Dir.cs index 0515117da..f4829b52e 100644 --- a/Aaru.Filesystems/Opera/Dir.cs +++ b/Aaru.Filesystems/Opera/Dir.cs @@ -31,12 +31,104 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; public sealed partial class OperaFS { + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(string.IsNullOrWhiteSpace(path) || + path == "/") + { + node = new OperaDirNode + { + Path = path, + _contents = _rootDirectoryCache.Keys.ToArray(), + _position = 0 + }; + + return ErrorNumber.NoError; + } + + string cutPath = path.StartsWith("/", StringComparison.Ordinal) + ? path[1..].ToLower(CultureInfo.CurrentUICulture) + : path.ToLower(CultureInfo.CurrentUICulture); + + if(_directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + { + node = new OperaDirNode + { + Path = path, + _contents = currentDirectory.Keys.ToArray(), + _position = 0 + }; + + return ErrorNumber.NoError; + } + + string[] pieces = cutPath.Split(new[] + { + '/' + }, StringSplitOptions.RemoveEmptyEntries); + + KeyValuePair entry = + _rootDirectoryCache.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[0]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if((entry.Value.Entry.flags & FLAGS_MASK) != (int)FileFlags.Directory) + return ErrorNumber.NotDirectory; + + string currentPath = pieces[0]; + + currentDirectory = _rootDirectoryCache; + + for(int p = 0; p < pieces.Length; p++) + { + entry = currentDirectory.FirstOrDefault(t => t.Key.ToLower(CultureInfo.CurrentUICulture) == pieces[p]); + + if(string.IsNullOrEmpty(entry.Key)) + return ErrorNumber.NoSuchFile; + + if((entry.Value.Entry.flags & FLAGS_MASK) != (int)FileFlags.Directory) + return ErrorNumber.NotDirectory; + + currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; + + if(_directoryCache.TryGetValue(currentPath, out currentDirectory)) + continue; + + if(entry.Value.Pointers.Length < 1) + return ErrorNumber.InvalidArgument; + + currentDirectory = DecodeDirectory((int)entry.Value.Pointers[0]); + + _directoryCache.Add(currentPath, currentDirectory); + } + + if(currentDirectory is null) + return ErrorNumber.NoSuchFile; + + node = new OperaDirNode + { + Path = path, + _contents = currentDirectory.Keys.ToArray(), + _position = 0 + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/Opera/Structs.cs b/Aaru.Filesystems/Opera/Structs.cs index 3e5309550..e213e6321 100644 --- a/Aaru.Filesystems/Opera/Structs.cs +++ b/Aaru.Filesystems/Opera/Structs.cs @@ -124,4 +124,12 @@ public sealed partial class OperaFS /// public long Offset { get; set; } } + + sealed class OperaDirNode : IDirNode + { + internal string[] _contents; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file diff --git a/Aaru.Filesystems/UCSDPascal/Dir.cs b/Aaru.Filesystems/UCSDPascal/Dir.cs index a596ff0ee..e5b47c703 100644 --- a/Aaru.Filesystems/UCSDPascal/Dir.cs +++ b/Aaru.Filesystems/UCSDPascal/Dir.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.Linq; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; namespace Aaru.Filesystems; @@ -41,6 +42,39 @@ namespace Aaru.Filesystems; // Information from Call-A.P.P.L.E. Pascal Disk Directory Structure public sealed partial class PascalPlugin { + /// + public ErrorNumber OpenDir(string path, out IDirNode node) + { + node = null; + + if(!_mounted) + return ErrorNumber.AccessDenied; + + if(!string.IsNullOrEmpty(path) && + string.Compare(path, "/", StringComparison.OrdinalIgnoreCase) != 0) + return ErrorNumber.NotSupported; + + List contents = _fileEntries.Select(ent => StringHandlers.PascalToString(ent.Filename, _encoding)). + ToList(); + + if(_debug) + { + contents.Add("$"); + contents.Add("$Boot"); + } + + contents.Sort(); + + node = new PascalDirDone + { + Path = path, + _contents = contents.ToArray(), + _position = 0 + }; + + return ErrorNumber.NoError; + } + /// public ErrorNumber ReadDir(string path, out List contents) { diff --git a/Aaru.Filesystems/UCSDPascal/Structs.cs b/Aaru.Filesystems/UCSDPascal/Structs.cs index 16e40e643..73117b12b 100644 --- a/Aaru.Filesystems/UCSDPascal/Structs.cs +++ b/Aaru.Filesystems/UCSDPascal/Structs.cs @@ -87,4 +87,12 @@ public sealed partial class PascalPlugin /// public long Offset { get; set; } } + + sealed class PascalDirDone : IDirNode + { + internal string[] _contents; + internal int _position; + /// + public string Path { get; init; } + } } \ No newline at end of file