2019-07-19 12:14:30 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-07-19 16:20:58 +01:00
|
|
|
using System.Globalization;
|
2019-07-19 14:26:02 +01:00
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
2019-07-19 12:14:30 +01:00
|
|
|
using DiscImageChef.CommonTypes.Structs;
|
2019-07-19 14:26:02 +01:00
|
|
|
using DiscImageChef.Helpers;
|
2019-07-19 12:14:30 +01:00
|
|
|
|
|
|
|
|
namespace DiscImageChef.Filesystems.ISO9660
|
|
|
|
|
{
|
|
|
|
|
public partial class ISO9660
|
|
|
|
|
{
|
2019-07-19 16:20:58 +01:00
|
|
|
Dictionary<string, Dictionary<string, DecodedDirectoryEntry>> directoryCache;
|
|
|
|
|
|
2019-07-19 15:44:40 +01:00
|
|
|
// TODO: Implement path table traversal
|
|
|
|
|
public Errno ReadDir(string path, out List<string> contents)
|
|
|
|
|
{
|
|
|
|
|
contents = null;
|
|
|
|
|
if(!mounted) return Errno.AccessDenied;
|
|
|
|
|
|
|
|
|
|
if(string.IsNullOrWhiteSpace(path) || path == "/")
|
|
|
|
|
{
|
2019-07-19 16:20:58 +01:00
|
|
|
contents = GetFilenames(rootDirectoryCache);
|
|
|
|
|
return Errno.NoError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string cutPath = path.StartsWith("/", StringComparison.Ordinal)
|
|
|
|
|
? path.Substring(1).ToLower(CultureInfo.CurrentUICulture)
|
|
|
|
|
: path.ToLower(CultureInfo.CurrentUICulture);
|
2019-07-19 15:44:40 +01:00
|
|
|
|
2019-07-19 16:20:58 +01:00
|
|
|
if(directoryCache.TryGetValue(cutPath, out Dictionary<string, DecodedDirectoryEntry> currentDirectory))
|
|
|
|
|
{
|
|
|
|
|
contents = currentDirectory.Keys.ToList();
|
2019-07-19 15:44:40 +01:00
|
|
|
return Errno.NoError;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 16:20:58 +01:00
|
|
|
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<string> GetFilenames(Dictionary<string, DecodedDirectoryEntry> dirents)
|
|
|
|
|
{
|
|
|
|
|
List<string> contents = new List<string>();
|
|
|
|
|
foreach(DecodedDirectoryEntry entry in dirents.Values)
|
|
|
|
|
switch(@namespace)
|
|
|
|
|
{
|
|
|
|
|
case Namespace.Normal:
|
2019-07-22 02:58:56 +01:00
|
|
|
contents.Add(entry.Filename.EndsWith(";1", StringComparison.Ordinal)
|
|
|
|
|
? entry.Filename.Substring(0, entry.Filename.Length - 2)
|
|
|
|
|
: entry.Filename);
|
2019-07-19 16:20:58 +01:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case Namespace.Vms:
|
2019-07-22 02:58:56 +01:00
|
|
|
contents.Add(entry.Filename);
|
2019-07-19 16:20:58 +01:00
|
|
|
break;
|
|
|
|
|
case Namespace.Joliet:
|
|
|
|
|
// TODO: Implement Joliet
|
|
|
|
|
break;
|
|
|
|
|
case Namespace.Rrip:
|
|
|
|
|
// TODO: Implement RRIP
|
|
|
|
|
break;
|
|
|
|
|
break;
|
|
|
|
|
default: throw new ArgumentOutOfRangeException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return contents;
|
2019-07-19 15:44:40 +01:00
|
|
|
}
|
2019-07-19 14:26:02 +01:00
|
|
|
|
2019-07-19 16:20:58 +01:00
|
|
|
Dictionary<string, DecodedDirectoryEntry> DecodeCdiDirectory(byte[] data) =>
|
|
|
|
|
throw new NotImplementedException();
|
2019-07-19 14:26:02 +01:00
|
|
|
|
2019-07-20 01:42:01 +01:00
|
|
|
Dictionary<string, DecodedDirectoryEntry> DecodeHighSierraDirectory(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
|
|
|
|
|
int entryOff = 0;
|
|
|
|
|
|
|
|
|
|
while(entryOff + DirectoryRecordSize < data.Length)
|
|
|
|
|
{
|
|
|
|
|
HighSierraDirectoryRecord record =
|
|
|
|
|
Marshal.ByteArrayToStructureLittleEndian<HighSierraDirectoryRecord>(data, entryOff,
|
|
|
|
|
Marshal
|
|
|
|
|
.SizeOf<DirectoryRecord>());
|
|
|
|
|
|
|
|
|
|
if(record.length == 0) break;
|
|
|
|
|
|
|
|
|
|
// Special entries for current and parent directories, skip them
|
|
|
|
|
if(record.name_len == 1)
|
|
|
|
|
if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1)
|
|
|
|
|
{
|
|
|
|
|
entryOff += record.length;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DecodedDirectoryEntry entry = new DecodedDirectoryEntry
|
|
|
|
|
{
|
|
|
|
|
Extent = record.size == 0 ? 0 : record.extent,
|
|
|
|
|
Size = record.size,
|
|
|
|
|
Flags = record.flags,
|
|
|
|
|
Interleave = record.interleave,
|
|
|
|
|
VolumeSequenceNumber = record.volume_sequence_number,
|
2019-07-22 02:58:56 +01:00
|
|
|
Filename =
|
2019-07-20 01:45:40 +01:00
|
|
|
Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
|
|
|
|
|
Timestamp = DecodeHighSierraDateTime(record.date)
|
2019-07-20 01:42:01 +01:00
|
|
|
};
|
|
|
|
|
|
2019-07-22 02:58:56 +01:00
|
|
|
if(!entries.ContainsKey(entry.Filename)) entries.Add(entry.Filename, entry);
|
2019-07-20 01:42:01 +01:00
|
|
|
|
|
|
|
|
entryOff += record.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
|
}
|
2019-07-19 14:26:02 +01:00
|
|
|
|
|
|
|
|
// TODO: Implement system area
|
2019-07-22 02:58:56 +01:00
|
|
|
Dictionary<string, DecodedDirectoryEntry> DecodeIsoDirectory(byte[] data)
|
2019-07-19 14:26:02 +01:00
|
|
|
{
|
2019-07-19 16:20:58 +01:00
|
|
|
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
|
|
|
|
|
int entryOff = 0;
|
2019-07-19 14:26:02 +01:00
|
|
|
|
|
|
|
|
while(entryOff + DirectoryRecordSize < data.Length)
|
|
|
|
|
{
|
|
|
|
|
DirectoryRecord record =
|
|
|
|
|
Marshal.ByteArrayToStructureLittleEndian<DirectoryRecord>(data, entryOff,
|
|
|
|
|
Marshal.SizeOf<DirectoryRecord>());
|
|
|
|
|
|
|
|
|
|
if(record.length == 0) break;
|
|
|
|
|
|
|
|
|
|
// Special entries for current and parent directories, skip them
|
|
|
|
|
if(record.name_len == 1)
|
|
|
|
|
if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1)
|
|
|
|
|
{
|
|
|
|
|
entryOff += record.length;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 16:20:58 +01:00
|
|
|
DecodedDirectoryEntry entry = new DecodedDirectoryEntry
|
|
|
|
|
{
|
2019-07-22 02:58:56 +01:00
|
|
|
Extent = record.size == 0 ? 0 : record.extent,
|
|
|
|
|
Size = record.size,
|
|
|
|
|
Flags = record.flags,
|
|
|
|
|
Filename =
|
|
|
|
|
joliet
|
|
|
|
|
? Encoding.BigEndianUnicode.GetString(data, entryOff + DirectoryRecordSize,
|
|
|
|
|
record.name_len)
|
|
|
|
|
: Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
|
2019-07-19 16:20:58 +01:00
|
|
|
FileUnitSize = record.file_unit_size,
|
|
|
|
|
Interleave = record.interleave,
|
|
|
|
|
VolumeSequenceNumber = record.volume_sequence_number,
|
2019-07-22 02:28:54 +01:00
|
|
|
Timestamp = DecodeIsoDateTime(record.date)
|
2019-07-19 16:20:58 +01:00
|
|
|
};
|
2019-07-19 14:26:02 +01:00
|
|
|
|
|
|
|
|
// TODO: Multi-extent files
|
2019-07-22 01:08:05 +01:00
|
|
|
if(entry.Flags.HasFlag(FileFlags.Associated))
|
|
|
|
|
{
|
|
|
|
|
// TODO: Detect if Apple extensions, as associated files contain the resource fork there
|
|
|
|
|
|
2019-07-22 02:58:56 +01:00
|
|
|
if(entries.ContainsKey(entry.Filename)) entries[entry.Filename].AssociatedFile = entry;
|
2019-07-22 01:08:05 +01:00
|
|
|
else
|
2019-07-22 02:58:56 +01:00
|
|
|
entries[entry.Filename] = new DecodedDirectoryEntry
|
2019-07-22 01:08:05 +01:00
|
|
|
{
|
|
|
|
|
Extent = 0,
|
|
|
|
|
Size = 0,
|
|
|
|
|
Flags = record.flags ^ FileFlags.Associated,
|
|
|
|
|
FileUnitSize = 0,
|
|
|
|
|
Interleave = 0,
|
|
|
|
|
VolumeSequenceNumber = record.volume_sequence_number,
|
2019-07-22 02:58:56 +01:00
|
|
|
Filename = joliet
|
|
|
|
|
? Encoding.BigEndianUnicode.GetString(data,
|
|
|
|
|
entryOff + DirectoryRecordSize,
|
|
|
|
|
record.name_len)
|
|
|
|
|
: Encoding.ASCII.GetString(data, entryOff + DirectoryRecordSize,
|
|
|
|
|
record.name_len),
|
2019-07-22 01:08:05 +01:00
|
|
|
Timestamp = DecodeIsoDateTime(record.date),
|
|
|
|
|
AssociatedFile = entry
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-07-22 02:58:56 +01:00
|
|
|
if(entries.ContainsKey(entry.Filename))
|
|
|
|
|
entry.AssociatedFile = entries[entry.Filename].AssociatedFile;
|
|
|
|
|
entries[entry.Filename] = entry;
|
2019-07-22 01:08:05 +01:00
|
|
|
}
|
2019-07-19 14:26:02 +01:00
|
|
|
|
|
|
|
|
entryOff += record.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
|
}
|
2019-07-19 12:14:30 +01:00
|
|
|
}
|
|
|
|
|
}
|