Implement ISO9660 readdir for subdirectories.

This commit is contained in:
2019-07-19 16:20:58 +01:00
parent f33d16d3c5
commit 27c9639573
4 changed files with 154 additions and 90 deletions

View File

@@ -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<string, Dictionary<string, DecodedDirectoryEntry>> directoryCache;
// TODO: Implement path table traversal
public Errno ReadDir(string path, out List<string> contents)
{
contents = null;
if(!mounted) return Errno.AccessDenied;
contents = new List<string>();
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<string, DecodedDirectoryEntry> currentDirectory))
{
contents = currentDirectory.Keys.ToList();
return Errno.NoError;
}
string[] pieces = cutPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
KeyValuePair<string, DecodedDirectoryEntry> 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<DecodedDirectoryEntry> DecodeCdiDirectory(byte[] data) => throw new NotImplementedException();
List<string> GetFilenames(Dictionary<string, DecodedDirectoryEntry> dirents)
{
List<string> contents = new List<string>();
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<DecodedDirectoryEntry> 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<string, DecodedDirectoryEntry> DecodeCdiDirectory(byte[] data) =>
throw new NotImplementedException();
Dictionary<string, DecodedDirectoryEntry> DecodeHighSierraDirectory(byte[] data) =>
throw new NotImplementedException();
// TODO: Implement system area
List<DecodedDirectoryEntry> DecodeIsoDirectory(byte[] data)
Dictionary<string, DecodedDirectoryEntry> DecodeIsoDirectory(byte[] data)
{
List<DecodedDirectoryEntry> entries = new List<DecodedDirectoryEntry>();
int entryOff = 0;
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
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;
}

View File

@@ -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<DecodedDirectoryEntry> rootDirectory;
FileSystemInfo statfs;
bool cdi;
bool debug;
bool highSierra;
IMediaImage image;
bool mounted;
Namespace @namespace;
Dictionary<string, DecodedDirectoryEntry> rootDirectoryCache;
FileSystemInfo statfs;
public FileSystemType XmlFsType { get; private set; }
public Encoding Encoding { get; private set; }

View File

@@ -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;
}

View File

@@ -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<string, Dictionary<string, DecodedDirectoryEntry>>();
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;
}