From 27c963957369ce6d1fcbb0c671f28efd8398a146 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 19 Jul 2019 16:20:58 +0100 Subject: [PATCH] Implement ISO9660 readdir for subdirectories. --- DiscImageChef.Filesystems/ISO9660/Dir.cs | 193 ++++++++++++------ DiscImageChef.Filesystems/ISO9660/ISO9660.cs | 14 +- .../ISO9660/Structs/Internal.cs | 8 +- DiscImageChef.Filesystems/ISO9660/Super.cs | 29 +-- 4 files changed, 154 insertions(+), 90 deletions(-) diff --git a/DiscImageChef.Filesystems/ISO9660/Dir.cs b/DiscImageChef.Filesystems/ISO9660/Dir.cs index 2d2ae3145..2d3cb41c8 100644 --- a/DiscImageChef.Filesystems/ISO9660/Dir.cs +++ b/DiscImageChef.Filesystems/ISO9660/Dir.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using DiscImageChef.CommonTypes.Structs; @@ -9,77 +10,136 @@ namespace DiscImageChef.Filesystems.ISO9660 { public partial class ISO9660 { + Dictionary> directoryCache; + // TODO: Implement path table traversal public Errno ReadDir(string path, out List contents) { contents = null; if(!mounted) return Errno.AccessDenied; - contents = new List(); if(string.IsNullOrWhiteSpace(path) || path == "/") { - foreach(DecodedDirectoryEntry entry in rootDirectory) - { - switch(@namespace) - { - case Namespace.Normal: - contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) - ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) - : entry.IsoFilename); - - break; - case Namespace.Vms: - contents.Add(entry.IsoFilename); - break; - case Namespace.Joliet: - // TODO: Implement Joliet - break; - case Namespace.JolietNormal: - // TODO: Implement Joliet - contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) - ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) - : entry.IsoFilename); - break; - case Namespace.Rrip: - // TODO: Implement RRIP - break; - case Namespace.RripNormal: - // TODO: Implement RRIP - contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) - ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) - : entry.IsoFilename); - break; - case Namespace.RripJoliet: - // TODO: Implement RRIP - // TODO: Implement Joliet - break; - case Namespace.RripJolietNormal: - // TODO: Implement RRIP - // TODO: Implement Joliet - contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) - ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) - : entry.IsoFilename); - break; - default: throw new ArgumentOutOfRangeException(); - } - } - + contents = GetFilenames(rootDirectoryCache); return Errno.NoError; } - // TODO: Implement subdirectories - throw new NotImplementedException(); + string cutPath = path.StartsWith("/", StringComparison.Ordinal) + ? path.Substring(1).ToLower(CultureInfo.CurrentUICulture) + : path.ToLower(CultureInfo.CurrentUICulture); + + if(directoryCache.TryGetValue(cutPath, out Dictionary currentDirectory)) + { + contents = currentDirectory.Keys.ToList(); + return Errno.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 Errno.NoSuchFile; + + if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) return Errno.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 Errno.NoSuchFile; + + if(!entry.Value.Flags.HasFlag(FileFlags.Directory)) return Errno.NotDirectory; + + currentPath = p == 0 ? pieces[0] : $"{currentPath}/{pieces[p]}"; + uint currentExtent = entry.Value.Extent; + + if(directoryCache.TryGetValue(currentPath, out currentDirectory)) continue; + + if(currentExtent == 0) return Errno.InvalidArgument; + + // TODO: XA, High Sierra + byte[] directoryBuffer = image.ReadSectors(currentExtent, entry.Value.Size / 2048); + + // TODO: Decode Joliet + currentDirectory = cdi + ? DecodeCdiDirectory(directoryBuffer) + : highSierra + ? DecodeHighSierraDirectory(directoryBuffer) + : DecodeIsoDirectory(directoryBuffer); + + directoryCache.Add(currentPath, currentDirectory); + } + + contents = GetFilenames(currentDirectory); + return Errno.NoError; } - List DecodeCdiDirectory(byte[] data) => throw new NotImplementedException(); + List GetFilenames(Dictionary dirents) + { + List contents = new List(); + foreach(DecodedDirectoryEntry entry in dirents.Values) + switch(@namespace) + { + case Namespace.Normal: + contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) + ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) + : entry.IsoFilename); - List DecodeHighSierraDirectory(byte[] data) => throw new NotImplementedException(); + break; + case Namespace.Vms: + contents.Add(entry.IsoFilename); + break; + case Namespace.Joliet: + // TODO: Implement Joliet + break; + case Namespace.JolietNormal: + // TODO: Implement Joliet + contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) + ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) + : entry.IsoFilename); + break; + case Namespace.Rrip: + // TODO: Implement RRIP + break; + case Namespace.RripNormal: + // TODO: Implement RRIP + contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) + ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) + : entry.IsoFilename); + break; + case Namespace.RripJoliet: + // TODO: Implement RRIP + // TODO: Implement Joliet + break; + case Namespace.RripJolietNormal: + // TODO: Implement RRIP + // TODO: Implement Joliet + contents.Add(entry.IsoFilename.EndsWith(";1", StringComparison.Ordinal) + ? entry.IsoFilename.Substring(0, entry.IsoFilename.Length - 2) + : entry.IsoFilename); + break; + default: throw new ArgumentOutOfRangeException(); + } + + return contents; + } + + Dictionary DecodeCdiDirectory(byte[] data) => + throw new NotImplementedException(); + + Dictionary DecodeHighSierraDirectory(byte[] data) => + throw new NotImplementedException(); // TODO: Implement system area - List DecodeIsoDirectory(byte[] data) + Dictionary DecodeIsoDirectory(byte[] data) { - List entries = new List(); - int entryOff = 0; + Dictionary entries = new Dictionary(); + int entryOff = 0; while(entryOff + DirectoryRecordSize < data.Length) { @@ -97,20 +157,21 @@ namespace DiscImageChef.Filesystems.ISO9660 continue; } - DecodedDirectoryEntry entry = new DecodedDirectoryEntry(); - - entry.Extent = record.size == 0 ? 0 : record.extent; - entry.Size = record.size; - entry.Flags = record.flags; - entry.FileUnitSize = record.file_unit_size; - entry.Interleave = record.interleave; - entry.VolumeSequenceNumber = record.volume_sequence_number; - entry.IsoFilename = - Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize, record.name_len); - entry.Timestamp = DecodeIsoDateTime(record.date); + DecodedDirectoryEntry entry = new DecodedDirectoryEntry + { + Extent = record.size == 0 ? 0 : record.extent, + Size = record.size, + Flags = record.flags, + FileUnitSize = record.file_unit_size, + Interleave = record.interleave, + VolumeSequenceNumber = record.volume_sequence_number, + IsoFilename = + Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize, record.name_len), + Timestamp = DecodeIsoDateTime(record.date) + }; // TODO: Multi-extent files - if(entries.All(e => e.IsoFilename != entry.IsoFilename)) entries.Add(entry); + if(!entries.ContainsKey(entry.IsoFilename)) entries.Add(entry.IsoFilename, entry); entryOff += record.length; } diff --git a/DiscImageChef.Filesystems/ISO9660/ISO9660.cs b/DiscImageChef.Filesystems/ISO9660/ISO9660.cs index 2cf200ac7..7309b36ef 100644 --- a/DiscImageChef.Filesystems/ISO9660/ISO9660.cs +++ b/DiscImageChef.Filesystems/ISO9660/ISO9660.cs @@ -42,12 +42,14 @@ namespace DiscImageChef.Filesystems.ISO9660 // This is coded following ECMA-119. public partial class ISO9660 : IReadOnlyFilesystem { - bool debug; - IMediaImage image; - bool mounted; - Namespace @namespace; - List rootDirectory; - FileSystemInfo statfs; + bool cdi; + bool debug; + bool highSierra; + IMediaImage image; + bool mounted; + Namespace @namespace; + Dictionary rootDirectoryCache; + FileSystemInfo statfs; public FileSystemType XmlFsType { get; private set; } public Encoding Encoding { get; private set; } diff --git a/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs b/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs index 804468805..f3067a3a0 100644 --- a/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs +++ b/DiscImageChef.Filesystems/ISO9660/Structs/Internal.cs @@ -55,16 +55,16 @@ namespace DiscImageChef.Filesystems.ISO9660 public uint Blocks; } - struct DecodedDirectoryEntry + class DecodedDirectoryEntry { public uint Extent; - public uint Size; - public FileFlags Flags; public byte FileUnitSize; + public FileFlags Flags; public byte Interleave; - public ushort VolumeSequenceNumber; public string IsoFilename; + public uint Size; public DateTime? Timestamp; + public ushort VolumeSequenceNumber; public override string ToString() => IsoFilename; } diff --git a/DiscImageChef.Filesystems/ISO9660/Super.cs b/DiscImageChef.Filesystems/ISO9660/Super.cs index 9bd69802c..e045880f1 100644 --- a/DiscImageChef.Filesystems/ISO9660/Super.cs +++ b/DiscImageChef.Filesystems/ISO9660/Super.cs @@ -72,10 +72,10 @@ namespace DiscImageChef.Filesystems.ISO9660 byte[] vdSector = imagePlugin.ReadSector(16 + counter + partition.Start); int xaOff = vdSector.Length == 2336 ? 8 : 0; Array.Copy(vdSector, 0x009 + xaOff, hsMagic, 0, 5); - bool highSierra = Encoding.GetString(hsMagic) == HIGH_SIERRA_MAGIC; - int hsOff = 0; + highSierra = Encoding.GetString(hsMagic) == HIGH_SIERRA_MAGIC; + int hsOff = 0; if(highSierra) hsOff = 8; - bool cdi = false; + cdi = false; while(true) { @@ -184,7 +184,6 @@ namespace DiscImageChef.Filesystems.ISO9660 this.@namespace = Namespace.Normal; if(jolietvd is null) - { switch(this.@namespace) { case Namespace.Joliet: @@ -198,7 +197,6 @@ namespace DiscImageChef.Filesystems.ISO9660 this.@namespace = Namespace.RripNormal; break; } - } uint rootLocation = 0; uint rootSize = 0; @@ -235,11 +233,11 @@ namespace DiscImageChef.Filesystems.ISO9660 // TODO: Add volume descriptors to debug root directory // TODO: Decode Joliet directory - rootDirectory = cdi - ? DecodeCdiDirectory(rootDir) - : highSierra - ? DecodeHighSierraDirectory(rootDir) - : DecodeIsoDirectory(rootDir); + rootDirectoryCache = cdi + ? DecodeCdiDirectory(rootDir) + : highSierra + ? DecodeHighSierraDirectory(rootDir) + : DecodeIsoDirectory(rootDir); XmlFsType.Type = fsFormat; @@ -354,8 +352,10 @@ namespace DiscImageChef.Filesystems.ISO9660 Type = fsFormat }; - image = imagePlugin; - mounted = true; + directoryCache = new Dictionary>(); + image = imagePlugin; + mounted = true; + return Errno.NoError; } @@ -363,8 +363,9 @@ namespace DiscImageChef.Filesystems.ISO9660 { if(!mounted) return Errno.AccessDenied; - rootDirectory = null; - mounted = false; + rootDirectoryCache = null; + directoryCache = null; + mounted = false; return Errno.NoError; }