diff --git a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj index 366700ebe..b0f361553 100644 --- a/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj +++ b/DiscImageChef.Filesystems/DiscImageChef.Filesystems.csproj @@ -112,6 +112,7 @@ + diff --git a/DiscImageChef.Filesystems/UDF.cs b/DiscImageChef.Filesystems/UDF.cs index a2860df82..6bff0ea8b 100644 --- a/DiscImageChef.Filesystems/UDF.cs +++ b/DiscImageChef.Filesystems/UDF.cs @@ -5,11 +5,11 @@ // Filename : UDF.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : Universal Disk Format plugin. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Identifies the Universal Disk Format and shows information. // // --[ License ] -------------------------------------------------------------- // @@ -29,14 +29,467 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.Console; +using System.Linq; +using DiscImageChef.ImagePlugins; + namespace DiscImageChef.Filesystems { - public class UDF + // TODO: Detect bootable + class UDF : Filesystem { public UDF() { + Name = "Universal Disk Format"; + PluginUUID = new Guid("83976FEC-A91B-464B-9293-56C719461BAB"); + } + + public UDF(ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) + { + Name = "Universal Disk Format"; + PluginUUID = new Guid("83976FEC-A91B-464B-9293-56C719461BAB"); + } + + readonly byte[] UDF_Magic = { 0x2A, 0x4F, 0x53, 0x54, 0x41, 0x20, 0x55, 0x44, 0x46, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x6C, 0x69, 0x61, 0x6E, 0x74, 0x00, 0x00, 0x00, 0x00 }; + + [Flags] + enum EntityFlags : byte + { + Dirty = 0x01, + Protected = 0x02 + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct EntityIdentifier + { + /// + /// Entity flags + /// + public EntityFlags flags; + /// + /// Structure identifier + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 23)] + public byte[] identifier; + /// + /// Structure data + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] identifierSuffix; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Timestamp + { + public ushort typeAndZone; + public short year; + public byte month; + public byte day; + public byte hour; + public byte minute; + public byte second; + public byte centiseconds; + public byte hundredsMicroseconds; + public byte microseconds; + } + + enum TagIdentifier : ushort + { + PrimaryVolumeDescriptor = 1, + AnchorVolumeDescriptorPointer = 2, + VolumeDescriptorPointer = 3, + ImplementationUseVolumeDescriptor = 4, + PartitionDescriptor = 5, + LogicalVolumeDescriptor = 6, + UnallocatedSpaceDescriptor = 7, + TerminatingDescriptor = 8, + LogicalVolumeIntegrityDescriptor = 9 + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DescriptorTag + { + public TagIdentifier tagIdentifier; + public ushort descriptorVersion; + public byte tagChecksum; + public byte reserved; + public ushort tagSerialNumber; + public ushort descriptorCrc; + public ushort descriptorCrcLength; + public uint tagLocation; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ExtentDescriptor + { + public uint length; + public uint location; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CharacterSpecification + { + public byte type; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 63)] + public byte[] information; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct AnchorVolumeDescriptorPointer + { + public DescriptorTag tag; + public ExtentDescriptor mainVolumeDescriptorSequenceExtent; + public ExtentDescriptor reserveVolumeDescriptorSequenceExtent; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 480)] + public byte[] reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct PrimaryVolumeDescriptor + { + public DescriptorTag tag; + public uint volumeDescriptorSequenceNumber; + public uint primaryVolumeDescriptorNumber; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] volumeIdentifier; + public ushort volumeSequenceNumber; + public ushort maximumVolumeSequenceNumber; + public ushort interchangeLevel; + public ushort maximumInterchangeLevel; + public uint characterSetList; + public uint maximumCharacterSetList; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] volumeSetIdentifier; + public CharacterSpecification descriptorCharacterSet; + public CharacterSpecification explanatoryCharacterSet; + public ExtentDescriptor volumeAbstract; + public ExtentDescriptor volumeCopyright; + public EntityIdentifier applicationIdentifier; + public Timestamp recordingDateTime; + public EntityIdentifier implementationIdentifier; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] implementationUse; + public uint predecessorVolumeDescriptorSequenceLocation; + public ushort flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] + public byte[] reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct LogicalVolumeDescriptor + { + public DescriptorTag tag; + public uint volumeDescriptorSequenceNumber; + public CharacterSpecification descriptorCharacterSet; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] logicalVolumeIdentifier; + public uint logicalBlockSize; + public EntityIdentifier domainIdentifier; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] logicalVolumeContentsUse; + public uint mapTableLength; + public uint numberOfPartitionMaps; + public EntityIdentifier implementationIdentifier; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] implementationUse; + public ExtentDescriptor integritySequenceExtent; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct LogicalVolumeIntegrityDescriptor + { + public DescriptorTag tag; + public Timestamp recordingDateTime; + public uint integrityType; + public ExtentDescriptor nextIntegrityExtent; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] logicalVolumeContentsUse; + public uint numberOfPartitions; + public uint lengthOfImplementationUse; + // Follows uint[numberOfPartitions] freeSpaceTable; + // Follows uint[numberOfPartitions] sizeTable; + // Follows byte[lengthOfImplementationUse] implementationUse; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct LogicalVolumeIntegrityDescriptorImplementationUse + { + public EntityIdentifier implementationId; + public uint files; + public uint directories; + public ushort minimumReadUDF; + public ushort minimumWriteUDF; + public ushort maximumWriteUDF; + } + + public override bool Identify(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) + { + // UDF needs at least that + if(partitionEnd - partitionStart < 256) + return false; + + // UDF needs at least that + if(imagePlugin.ImageInfo.sectorSize < 512) + return false; + + byte[] sector; + AnchorVolumeDescriptorPointer anchor = new AnchorVolumeDescriptorPointer(); + // All positions where anchor may reside + ulong[] positions = { 256, 512, partitionEnd - 256, partitionEnd }; + bool anchorFound = false; + + foreach(ulong position in positions) + { + sector = imagePlugin.ReadSector(position); + anchor = new AnchorVolumeDescriptorPointer(); + IntPtr anchorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(anchor)); + Marshal.Copy(sector, 0, anchorPtr, Marshal.SizeOf(anchor)); + anchor = (AnchorVolumeDescriptorPointer)Marshal.PtrToStructure(anchorPtr, typeof(AnchorVolumeDescriptorPointer)); + Marshal.FreeHGlobal(anchorPtr); + + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.tagIdentifier = {0}", anchor.tag.tagIdentifier); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.descriptorVersion = {0}", anchor.tag.descriptorVersion); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.tagChecksum = 0x{0:X2}", anchor.tag.tagChecksum); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.reserved = {0}", anchor.tag.reserved); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.tagSerialNumber = {0}", anchor.tag.tagSerialNumber); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.descriptorCrc = 0x{0:X4}", anchor.tag.descriptorCrc); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.descriptorCrcLength = {0}", anchor.tag.descriptorCrcLength); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.tag.tagLocation = {0}", anchor.tag.tagLocation); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.mainVolumeDescriptorSequenceExtent.length = {0}", anchor.mainVolumeDescriptorSequenceExtent.length); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.mainVolumeDescriptorSequenceExtent.location = {0}", anchor.mainVolumeDescriptorSequenceExtent.location); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.reserveVolumeDescriptorSequenceExtent.length = {0}", anchor.reserveVolumeDescriptorSequenceExtent.length); + DicConsole.DebugWriteLine("UDF Plugin", "anchor.reserveVolumeDescriptorSequenceExtent.location = {0}", anchor.reserveVolumeDescriptorSequenceExtent.location); + + if(anchor.tag.tagIdentifier == TagIdentifier.AnchorVolumeDescriptorPointer && + anchor.tag.tagLocation == position && + (anchor.mainVolumeDescriptorSequenceExtent.location + partitionStart) < partitionEnd) + { + anchorFound = true; + break; + } + } + + if(!anchorFound) + return false; + + ulong count = 0; + + while(count < 256) + { + sector = imagePlugin.ReadSector(partitionStart + anchor.mainVolumeDescriptorSequenceExtent.location + count); + TagIdentifier tagId = (TagIdentifier)BitConverter.ToUInt16(sector, 0); + uint location = BitConverter.ToUInt32(sector, 0x0C); + + if(location == partitionStart + anchor.mainVolumeDescriptorSequenceExtent.location + count) + { + if(tagId == TagIdentifier.TerminatingDescriptor) + break; + + if(tagId == TagIdentifier.LogicalVolumeDescriptor) + { + LogicalVolumeDescriptor lvd = new LogicalVolumeDescriptor(); + IntPtr lvdPtr = Marshal.AllocHGlobal(Marshal.SizeOf(lvd)); + Marshal.Copy(sector, 0, lvdPtr, Marshal.SizeOf(lvd)); + lvd = (LogicalVolumeDescriptor)Marshal.PtrToStructure(lvdPtr, typeof(LogicalVolumeDescriptor)); + Marshal.FreeHGlobal(lvdPtr); + + return UDF_Magic.SequenceEqual(lvd.domainIdentifier.identifier); + } + } + else + break; + + count++; + } + + return false; + } + + public override void GetInformation(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd, out string information) + { + byte[] sector; + + StringBuilder sbInformation = new StringBuilder(); + + sbInformation.AppendLine("Universal Disk Format"); + + AnchorVolumeDescriptorPointer anchor = new AnchorVolumeDescriptorPointer(); + // All positions where anchor may reside + ulong[] positions = { 256, 512, partitionEnd - 256, partitionEnd }; + + foreach(ulong position in positions) + { + sector = imagePlugin.ReadSector(position); + anchor = new AnchorVolumeDescriptorPointer(); + IntPtr anchorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(anchor)); + Marshal.Copy(sector, 0, anchorPtr, Marshal.SizeOf(anchor)); + anchor = (AnchorVolumeDescriptorPointer)Marshal.PtrToStructure(anchorPtr, typeof(AnchorVolumeDescriptorPointer)); + Marshal.FreeHGlobal(anchorPtr); + + if(anchor.tag.tagIdentifier == TagIdentifier.AnchorVolumeDescriptorPointer && + anchor.tag.tagLocation == position && + (anchor.mainVolumeDescriptorSequenceExtent.location + partitionStart) < partitionEnd) + break; + } + + ulong count = 0; + + PrimaryVolumeDescriptor pvd = new PrimaryVolumeDescriptor(); + LogicalVolumeDescriptor lvd = new LogicalVolumeDescriptor(); + LogicalVolumeIntegrityDescriptor lvid = new LogicalVolumeIntegrityDescriptor(); + LogicalVolumeIntegrityDescriptorImplementationUse lvidiu = new LogicalVolumeIntegrityDescriptorImplementationUse(); + + while(count < 256) + { + sector = imagePlugin.ReadSector(partitionStart + anchor.mainVolumeDescriptorSequenceExtent.location + count); + TagIdentifier tagId = (TagIdentifier)BitConverter.ToUInt16(sector, 0); + uint location = BitConverter.ToUInt32(sector, 0x0C); + + if(location == partitionStart + anchor.mainVolumeDescriptorSequenceExtent.location + count) + { + if(tagId == TagIdentifier.TerminatingDescriptor) + break; + + if(tagId == TagIdentifier.LogicalVolumeDescriptor) + { + IntPtr lvdPtr = Marshal.AllocHGlobal(Marshal.SizeOf(lvd)); + Marshal.Copy(sector, 0, lvdPtr, Marshal.SizeOf(lvd)); + lvd = (LogicalVolumeDescriptor)Marshal.PtrToStructure(lvdPtr, typeof(LogicalVolumeDescriptor)); + Marshal.FreeHGlobal(lvdPtr); + } + + if(tagId == TagIdentifier.PrimaryVolumeDescriptor) + { + IntPtr pvdPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pvd)); + Marshal.Copy(sector, 0, pvdPtr, Marshal.SizeOf(pvd)); + pvd = (PrimaryVolumeDescriptor)Marshal.PtrToStructure(pvdPtr, typeof(PrimaryVolumeDescriptor)); + Marshal.FreeHGlobal(pvdPtr); + } + } + else + break; + + count++; + } + + sector = imagePlugin.ReadSector(lvd.integritySequenceExtent.location); + IntPtr lvidPtr = Marshal.AllocHGlobal(Marshal.SizeOf(lvid)); + Marshal.Copy(sector, 0, lvidPtr, Marshal.SizeOf(lvid)); + lvid = (LogicalVolumeIntegrityDescriptor)Marshal.PtrToStructure(lvidPtr, typeof(LogicalVolumeIntegrityDescriptor)); + Marshal.FreeHGlobal(lvidPtr); + + if(lvid.tag.tagIdentifier == TagIdentifier.LogicalVolumeIntegrityDescriptor && lvid.tag.tagLocation == lvd.integritySequenceExtent.location) + { + IntPtr lvidiuPtr = Marshal.AllocHGlobal(Marshal.SizeOf(lvidiu)); + Marshal.Copy(sector, (int)(lvid.numberOfPartitions * 8 + 80), lvidiuPtr, Marshal.SizeOf(lvidiu)); + lvidiu = (LogicalVolumeIntegrityDescriptorImplementationUse)Marshal.PtrToStructure(lvidiuPtr, typeof(LogicalVolumeIntegrityDescriptorImplementationUse)); + Marshal.FreeHGlobal(lvidiuPtr); + } + else + lvid = new LogicalVolumeIntegrityDescriptor(); + + sbInformation.AppendFormat("Volume is number {0} of {1}", pvd.volumeSequenceNumber, pvd.maximumVolumeSequenceNumber).AppendLine(); + sbInformation.AppendFormat("Volume set identifier: {0}", StringHandlers.DecompressUnicode(pvd.volumeSetIdentifier)).AppendLine(); + sbInformation.AppendFormat("Volume name: {0}", StringHandlers.DecompressUnicode(lvd.logicalVolumeIdentifier)).AppendLine(); + sbInformation.AppendFormat("Volume uses {0} bytes per block", lvd.logicalBlockSize).AppendLine(); + sbInformation.AppendFormat("Volume was las written in {0}", ECMAToDateTime(lvid.recordingDateTime)).AppendLine(); + sbInformation.AppendFormat("Volume contains {0} partitions", lvid.numberOfPartitions).AppendLine(); + sbInformation.AppendFormat("Volume contains {0} files and {1} directories", lvidiu.files, lvidiu.directories).AppendLine(); + sbInformation.AppendFormat("Volume conforms to {0}", Encoding.UTF8.GetString(lvd.domainIdentifier.identifier).TrimEnd(new char[] { '\u0000' })).AppendLine(); + sbInformation.AppendFormat("Volume was last written by: {0}", Encoding.UTF8.GetString(pvd.implementationIdentifier.identifier).TrimEnd(new char[]{ '\u0000' })).AppendLine(); + sbInformation.AppendFormat("Volume requires UDF version {0}.{1:D2} to be read", Convert.ToInt32(string.Format("{0}", (lvidiu.minimumReadUDF & 0xFF00) >> 8), 10), + Convert.ToInt32(string.Format("{0}", lvidiu.minimumReadUDF & 0xFF), 10)).AppendLine(); + sbInformation.AppendFormat("Volume requires UDF version {0}.{1:D2} to be written to", Convert.ToInt32(string.Format("{0}", (lvidiu.minimumWriteUDF & 0xFF00) >> 8), 10), + Convert.ToInt32(string.Format("{0}", lvidiu.minimumWriteUDF & 0xFF), 10)).AppendLine(); + sbInformation.AppendFormat("Volume cannot be written by any UDF version higher than {0}.{1:D2}", Convert.ToInt32(string.Format("{0}", (lvidiu.maximumWriteUDF & 0xFF00) >> 8), 10), + Convert.ToInt32(string.Format("{0}", lvidiu.maximumWriteUDF & 0xFF), 10)).AppendLine(); + + xmlFSType = new Schemas.FileSystemType(); + xmlFSType.Type = string.Format("UDF v{0}.{1:D2}", Convert.ToInt32(string.Format("{0}", (lvidiu.minimumReadUDF & 0xFF00) >> 8), 10), + Convert.ToInt32(string.Format("{0}", lvidiu.minimumReadUDF & 0xFF), 10)); + xmlFSType.ApplicationIdentifier = Encoding.UTF8.GetString(pvd.implementationIdentifier.identifier).TrimEnd(new char[] { '\u0000' }); + xmlFSType.ClusterSize = (int)lvd.logicalBlockSize; + xmlFSType.Clusters = (long)(((partitionEnd - partitionStart + 1) * imagePlugin.ImageInfo.sectorSize) / (ulong)xmlFSType.ClusterSize); + xmlFSType.ModificationDate = ECMAToDateTime(lvid.recordingDateTime); + xmlFSType.ModificationDateSpecified = true; + xmlFSType.Files = lvidiu.files; + xmlFSType.FilesSpecified = true; + xmlFSType.VolumeName = StringHandlers.DecompressUnicode(lvd.logicalVolumeIdentifier); + xmlFSType.VolumeSetIdentifier = StringHandlers.DecompressUnicode(pvd.volumeSetIdentifier); + + information = sbInformation.ToString(); + } + + static DateTime ECMAToDateTime(Timestamp timestamp) + { + return DateHandlers.ECMAToDateTime(timestamp.typeAndZone, timestamp.year, timestamp.month, timestamp.day, timestamp.hour, + timestamp.minute, timestamp.second, timestamp.centiseconds, timestamp.hundredsMicroseconds, + timestamp.microseconds); + } + + public override Errno Mount() + { + return Errno.NotImplemented; + } + + public override Errno Mount(bool debug) + { + return Errno.NotImplemented; + } + + public override Errno Unmount() + { + return Errno.NotImplemented; + } + + public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) + { + return Errno.NotImplemented; + } + + public override Errno GetAttributes(string path, ref FileAttributes attributes) + { + return Errno.NotImplemented; + } + + public override Errno ListXAttr(string path, ref List xattrs) + { + return Errno.NotImplemented; + } + + public override Errno GetXattr(string path, string xattr, ref byte[] buf) + { + return Errno.NotImplemented; + } + + public override Errno Read(string path, long offset, long size, ref byte[] buf) + { + return Errno.NotImplemented; + } + + public override Errno ReadDir(string path, ref List contents) + { + return Errno.NotImplemented; + } + + public override Errno StatFs(ref FileSystemInfo stat) + { + return Errno.NotImplemented; + } + + public override Errno Stat(string path, ref FileEntryInfo stat) + { + return Errno.NotImplemented; + } + + public override Errno ReadLink(string path, ref string dest) + { + return Errno.NotImplemented; } } -} - +} \ No newline at end of file diff --git a/README.md b/README.md index b9c34cad2..64f2d2921 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ Supported file systems for identification and information only * Squash file system * Cram file system * Commodore 1540/1541/1571/1581 filesystems +* Universal Disk Format (UDF) Supported checksums =================== diff --git a/TODO b/TODO index aa502be3e..23ab419f9 100644 --- a/TODO +++ b/TODO @@ -11,7 +11,6 @@ Filesystem plugins: --- Add support for Apple DOS filesystems --- Add support for ZFS ---- Add support for UDF --- Add support for NwFS --- Add support for X-Box filesystems --- Add support for ReFS