Fix reading ISO9660, CD-i and HSF directories that span several sectors.

This commit is contained in:
2019-07-31 22:21:03 +01:00
parent d6da209fdc
commit 9aac84c702
2 changed files with 198 additions and 192 deletions

View File

@@ -94,35 +94,17 @@ namespace DiscImageChef.Filesystems.ISO9660
if(entry.Value.Extents.Count == 0) return Errno.InvalidArgument; if(entry.Value.Extents.Count == 0) return Errno.InvalidArgument;
byte[] directoryBuffer; uint dirSizeInSectors = entry.Value.Extents[0].size / 2048;
if(entry.Value.Extents.Count == 1) if(entry.Value.Size % 2048 > 0) dirSizeInSectors++;
{
uint dirSizeInSectors = entry.Value.Extents[0].size / 2048;
if(entry.Value.Size % 2048 > 0) dirSizeInSectors++;
directoryBuffer = ReadSectors(entry.Value.Extents[0].extent, dirSizeInSectors);
}
else
{
MemoryStream ms = new MemoryStream();
foreach((uint extent, uint size) extent in entry.Value.Extents)
{
uint extentSizeInSectors = extent.size / 2048;
if(extent.size % 2048 > 0) extentSizeInSectors++;
byte[] extentData = ReadSectors(extent.extent, extentSizeInSectors);
ms.Write(extentData, 0, extentData.Length);
}
directoryBuffer = ms.ToArray();
}
currentDirectory = cdi currentDirectory = cdi
? DecodeCdiDirectory(directoryBuffer, entry.Value.XattrLength) ? DecodeCdiDirectory(entry.Value.Extents[0].extent, dirSizeInSectors,
entry.Value.XattrLength)
: highSierra : highSierra
? DecodeHighSierraDirectory(directoryBuffer, entry.Value.XattrLength) ? DecodeHighSierraDirectory(entry.Value.Extents[0].extent, dirSizeInSectors,
: DecodeIsoDirectory(directoryBuffer, entry.Value.XattrLength); entry.Value.XattrLength)
: DecodeIsoDirectory(entry.Value.Extents[0].extent, dirSizeInSectors,
entry.Value.XattrLength);
if(usePathTable) if(usePathTable)
foreach(DecodedDirectoryEntry subDirectory in cdi foreach(DecodedDirectoryEntry subDirectory in cdi
@@ -163,107 +145,125 @@ namespace DiscImageChef.Filesystems.ISO9660
return contents; return contents;
} }
Dictionary<string, DecodedDirectoryEntry> DecodeCdiDirectory(byte[] data, byte xattrLength) Dictionary<string, DecodedDirectoryEntry> DecodeCdiDirectory(ulong start, uint count, byte xattrLength)
{ {
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>(); Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
int entryOff = xattrLength; int entryOff = xattrLength;
while(entryOff + CdiDirectoryRecordSize < data.Length) for(ulong sector = start; sector < start + count; sector++)
{ {
CdiDirectoryRecord record = byte[] data = ReadSectors(sector, 1);
Marshal.ByteArrayToStructureBigEndian<CdiDirectoryRecord>(data, entryOff, CdiDirectoryRecordSize);
if(record.length == 0) break; while(entryOff + CdiDirectoryRecordSize < data.Length)
{
CdiDirectoryRecord record =
Marshal.ByteArrayToStructureBigEndian<CdiDirectoryRecord>(data, entryOff,
CdiDirectoryRecordSize);
// Special entries for current and parent directories, skip them if(record.length == 0) break;
if(record.name_len == 1)
if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1) // 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
{ {
entryOff += record.length; Size = record.size,
Filename =
Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
VolumeSequenceNumber = record.volume_sequence_number,
Timestamp = DecodeHighSierraDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0)
entry.Extents = new List<(uint extent, uint size)> {(record.start_lbn, record.size)};
if(record.flags.HasFlag(CdiFileFlags.Hidden))
{
entry.Flags |= FileFlags.Hidden;
continue; continue;
} }
DecodedDirectoryEntry entry = new DecodedDirectoryEntry entry.CdiSystemArea =
{ Marshal.ByteArrayToStructureBigEndian<CdiSystemArea>(data,
Size = record.size, entryOff + record.name_len +
Filename = Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len), CdiDirectoryRecordSize, CdiSystemAreaSize);
VolumeSequenceNumber = record.volume_sequence_number,
Timestamp = DecodeHighSierraDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0) if(entry.CdiSystemArea.Value.attributes.HasFlag(CdiAttributes.Directory))
entry.Extents = new List<(uint extent, uint size)> {(record.start_lbn, record.size)}; {
entry.Flags |= FileFlags.Directory;
continue;
}
if(record.flags.HasFlag(CdiFileFlags.Hidden)) if(!entry.CdiSystemArea.Value.attributes.HasFlag(CdiAttributes.Directory) || !usePathTable)
{ entries[entry.Filename] = entry;
entry.Flags |= FileFlags.Hidden;
continue; entryOff += record.length;
} }
entry.CdiSystemArea = entryOff = 0;
Marshal.ByteArrayToStructureBigEndian<CdiSystemArea>(data,
entryOff + record.name_len +
CdiDirectoryRecordSize, CdiSystemAreaSize);
if(entry.CdiSystemArea.Value.attributes.HasFlag(CdiAttributes.Directory))
{
entry.Flags |= FileFlags.Directory;
continue;
}
if(!entry.CdiSystemArea.Value.attributes.HasFlag(CdiAttributes.Directory) || !usePathTable)
entries[entry.Filename] = entry;
entryOff += record.length;
} }
return entries; return entries;
} }
Dictionary<string, DecodedDirectoryEntry> DecodeHighSierraDirectory(byte[] data, byte xattrLength) Dictionary<string, DecodedDirectoryEntry> DecodeHighSierraDirectory(ulong start, uint count, byte xattrLength)
{ {
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>(); Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
int entryOff = xattrLength; int entryOff = xattrLength;
while(entryOff + DirectoryRecordSize < data.Length) for(ulong sector = start; sector < start + count; sector++)
{ {
HighSierraDirectoryRecord record = byte[] data = ReadSectors(sector, 1);
Marshal.ByteArrayToStructureLittleEndian<HighSierraDirectoryRecord>(data, entryOff,
HighSierraDirectoryRecordSize);
if(record.length == 0) break; while(entryOff + DirectoryRecordSize < data.Length)
{
HighSierraDirectoryRecord record =
Marshal.ByteArrayToStructureLittleEndian<HighSierraDirectoryRecord>(data, entryOff,
HighSierraDirectoryRecordSize);
// Special entries for current and parent directories, skip them if(record.length == 0) break;
if(record.name_len == 1)
if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1) // 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
{
Size = record.size,
Flags = record.flags,
Interleave = record.interleave,
VolumeSequenceNumber = record.volume_sequence_number,
Filename =
Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
Timestamp = DecodeHighSierraDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0)
entry.Extents = new List<(uint extent, uint size)> {(record.extent, record.size)};
if(entry.Flags.HasFlag(FileFlags.Directory) && usePathTable)
{ {
entryOff += record.length; entryOff += record.length;
continue; continue;
} }
DecodedDirectoryEntry entry = new DecodedDirectoryEntry if(!entries.ContainsKey(entry.Filename)) entries.Add(entry.Filename, entry);
{
Size = record.size,
Flags = record.flags,
Interleave = record.interleave,
VolumeSequenceNumber = record.volume_sequence_number,
Filename = Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
Timestamp = DecodeHighSierraDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0) entry.Extents = new List<(uint extent, uint size)> {(record.extent, record.size)};
if(entry.Flags.HasFlag(FileFlags.Directory) && usePathTable)
{
entryOff += record.length; entryOff += record.length;
continue;
} }
if(!entries.ContainsKey(entry.Filename)) entries.Add(entry.Filename, entry); entryOff = 0;
entryOff += record.length;
} }
if(useTransTbl) DecodeTransTable(entries); if(useTransTbl) DecodeTransTable(entries);
@@ -271,134 +271,142 @@ namespace DiscImageChef.Filesystems.ISO9660
return entries; return entries;
} }
Dictionary<string, DecodedDirectoryEntry> DecodeIsoDirectory(byte[] data, byte xattrLength) Dictionary<string, DecodedDirectoryEntry> DecodeIsoDirectory(ulong start, uint count, byte xattrLength)
{ {
Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>(); Dictionary<string, DecodedDirectoryEntry> entries = new Dictionary<string, DecodedDirectoryEntry>();
int entryOff = xattrLength; int entryOff = xattrLength;
while(entryOff + DirectoryRecordSize < data.Length) for(ulong sector = start; sector < start + count; sector++)
{ {
DirectoryRecord record = byte[] data = ReadSectors(sector, 1);
Marshal.ByteArrayToStructureLittleEndian<DirectoryRecord>(data, entryOff, DirectoryRecordSize);
if(record.length == 0) break; while(entryOff + DirectoryRecordSize < data.Length)
{
DirectoryRecord record =
Marshal.ByteArrayToStructureLittleEndian<DirectoryRecord>(data, entryOff, DirectoryRecordSize);
// Special entries for current and parent directories, skip them if(record.length == 0) break;
if(record.name_len == 1)
if(data[entryOff + DirectoryRecordSize] == 0 || data[entryOff + DirectoryRecordSize] == 1) // 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
{
Size = record.size,
Flags = record.flags,
Filename =
joliet
? Encoding.BigEndianUnicode.GetString(data, entryOff + DirectoryRecordSize,
record.name_len)
: Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
FileUnitSize = record.file_unit_size,
Interleave = record.interleave,
VolumeSequenceNumber = record.volume_sequence_number,
Timestamp = DecodeIsoDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0)
entry.Extents = new List<(uint extent, uint size)> {(record.extent, record.size)};
if(entry.Flags.HasFlag(FileFlags.Directory) && usePathTable)
{ {
entryOff += record.length; entryOff += record.length;
continue; continue;
} }
DecodedDirectoryEntry entry = new DecodedDirectoryEntry // Mac OS can use slashes, we cannot
{ entry.Filename = entry.Filename.Replace('/', '\u2215');
Size = record.size,
Flags = record.flags,
Filename =
joliet
? Encoding.BigEndianUnicode.GetString(data, entryOff + DirectoryRecordSize,
record.name_len)
: Encoding.GetString(data, entryOff + DirectoryRecordSize, record.name_len),
FileUnitSize = record.file_unit_size,
Interleave = record.interleave,
VolumeSequenceNumber = record.volume_sequence_number,
Timestamp = DecodeIsoDateTime(record.date),
XattrLength = record.xattr_len
};
if(record.size != 0) entry.Extents = new List<(uint extent, uint size)> {(record.extent, record.size)}; // Tailing '.' is only allowed on RRIP. If present it will be recreated below with the alternate name
if(entry.Filename.EndsWith(".", StringComparison.Ordinal))
entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 1);
if(entry.Flags.HasFlag(FileFlags.Directory) && usePathTable) if(entry.Filename.EndsWith(".;1", StringComparison.Ordinal))
{ entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 3) + ";1";
entryOff += record.length;
continue;
}
// Mac OS can use slashes, we cannot // This is a legal Joliet name, different from VMS version fields, but Nero MAX incorrectly creates these filenames
entry.Filename = entry.Filename.Replace('/', '\u2215'); if(joliet && entry.Filename.EndsWith(";1", StringComparison.Ordinal))
entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 2);
// Tailing '.' is only allowed on RRIP. If present it will be recreated below with the alternate name int systemAreaStart = entryOff + record.name_len + DirectoryRecordSize;
if(entry.Filename.EndsWith(".", StringComparison.Ordinal)) int systemAreaLength = record.length - record.name_len - DirectoryRecordSize;
entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 1);
if(entry.Filename.EndsWith(".;1", StringComparison.Ordinal)) if(systemAreaStart % 2 != 0)
entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 3) + ";1";
// This is a legal Joliet name, different from VMS version fields, but Nero MAX incorrectly creates these filenames
if(joliet && entry.Filename.EndsWith(";1", StringComparison.Ordinal))
entry.Filename = entry.Filename.Substring(0, entry.Filename.Length - 2);
int systemAreaStart = entryOff + record.name_len + DirectoryRecordSize;
int systemAreaLength = record.length - record.name_len - DirectoryRecordSize;
if(systemAreaStart % 2 != 0)
{
systemAreaStart++;
systemAreaLength--;
}
DecodeSystemArea(data, systemAreaStart, systemAreaStart + systemAreaLength, ref entry,
out bool hasResourceFork);
if(entry.Flags.HasFlag(FileFlags.Associated))
{
if(entries.ContainsKey(entry.Filename))
{ {
if(hasResourceFork) systemAreaStart++;
systemAreaLength--;
}
DecodeSystemArea(data, systemAreaStart, systemAreaStart + systemAreaLength, ref entry,
out bool hasResourceFork);
if(entry.Flags.HasFlag(FileFlags.Associated))
{
if(entries.ContainsKey(entry.Filename))
{ {
entries[entry.Filename].ResourceFork.Size += entry.Size; if(hasResourceFork)
entries[entry.Filename].ResourceFork.Extents.Add(entry.Extents[0]); {
entries[entry.Filename].ResourceFork.Size += entry.Size;
entries[entry.Filename].ResourceFork.Extents.Add(entry.Extents[0]);
}
else
{
entries[entry.Filename].AssociatedFile.Size += entry.Size;
entries[entry.Filename].AssociatedFile.Extents.Add(entry.Extents[0]);
}
} }
else else
{ {
entries[entry.Filename].AssociatedFile.Size += entry.Size; entries[entry.Filename] = new DecodedDirectoryEntry
entries[entry.Filename].AssociatedFile.Extents.Add(entry.Extents[0]); {
Size = 0,
Flags = record.flags ^ FileFlags.Associated,
FileUnitSize = 0,
Interleave = 0,
VolumeSequenceNumber = record.volume_sequence_number,
Filename = entry.Filename,
Timestamp = DecodeIsoDateTime(record.date),
XattrLength = 0
};
if(hasResourceFork) entries[entry.Filename].ResourceFork = entry;
else entries[entry.Filename].AssociatedFile = entry;
} }
} }
else else
{ {
entries[entry.Filename] = new DecodedDirectoryEntry if(entries.ContainsKey(entry.Filename))
{ {
Size = 0, entries[entry.Filename].Size += entry.Size;
Flags = record.flags ^ FileFlags.Associated,
FileUnitSize = 0,
Interleave = 0,
VolumeSequenceNumber = record.volume_sequence_number,
Filename = entry.Filename,
Timestamp = DecodeIsoDateTime(record.date),
XattrLength = 0
};
if(hasResourceFork) entries[entry.Filename].ResourceFork = entry; // Can appear after an associated file
else entries[entry.Filename].AssociatedFile = entry; if(entries[entry.Filename].Extents is null)
} {
} entries[entry.Filename].Extents = new List<(uint extent, uint size)>();
else entries[entry.Filename].Flags = entry.Flags;
{ entries[entry.Filename].FileUnitSize = entry.FileUnitSize;
if(entries.ContainsKey(entry.Filename)) entries[entry.Filename].Interleave = entry.Interleave;
{ entries[entry.Filename].VolumeSequenceNumber = entry.VolumeSequenceNumber;
entries[entry.Filename].Size += entry.Size; entries[entry.Filename].Filename = entry.Filename;
entries[entry.Filename].Timestamp = entry.Timestamp;
entries[entry.Filename].XattrLength = entry.XattrLength;
}
// Can appear after an associated file entries[entry.Filename].Extents.Add(entry.Extents[0]);
if(entries[entry.Filename].Extents is null)
{
entries[entry.Filename].Extents = new List<(uint extent, uint size)>();
entries[entry.Filename].Flags = entry.Flags;
entries[entry.Filename].FileUnitSize = entry.FileUnitSize;
entries[entry.Filename].Interleave = entry.Interleave;
entries[entry.Filename].VolumeSequenceNumber = entry.VolumeSequenceNumber;
entries[entry.Filename].Filename = entry.Filename;
entries[entry.Filename].Timestamp = entry.Timestamp;
entries[entry.Filename].XattrLength = entry.XattrLength;
} }
else entries[entry.Filename] = entry;
entries[entry.Filename].Extents.Add(entry.Extents[0]);
} }
else entries[entry.Filename] = entry;
entryOff += record.length;
} }
entryOff += record.length; entryOff = 0;
} }
if(useTransTbl) DecodeTransTable(entries); if(useTransTbl) DecodeTransTable(entries);

View File

@@ -348,10 +348,10 @@ namespace DiscImageChef.Filesystems.ISO9660
if(this.@namespace != Namespace.Joliet) if(this.@namespace != Namespace.Joliet)
rootDirectoryCache = cdi rootDirectoryCache = cdi
? DecodeCdiDirectory(rootDir, rootXattrLength) ? DecodeCdiDirectory(rootLocation, rootSize, rootXattrLength)
: highSierra : highSierra
? DecodeHighSierraDirectory(rootDir, rootXattrLength) ? DecodeHighSierraDirectory(rootLocation, rootSize, rootXattrLength)
: DecodeIsoDirectory(rootDir, rootXattrLength); : DecodeIsoDirectory(rootLocation, rootSize, rootXattrLength);
XmlFsType.Type = fsFormat; XmlFsType.Type = fsFormat;
@@ -500,9 +500,7 @@ namespace DiscImageChef.Filesystems.ISO9660
joliet = true; joliet = true;
rootDir = ReadSectors(rootLocation, rootSize); rootDirectoryCache = DecodeIsoDirectory(rootLocation, rootSize, rootXattrLength);
rootDirectoryCache = DecodeIsoDirectory(rootDir, rootXattrLength);
XmlFsType.VolumeName = decodedJolietVd.VolumeIdentifier; XmlFsType.VolumeName = decodedJolietVd.VolumeIdentifier;