From 880462f7a3b768b620e8eb780d3fe9a7bb2395ce Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sat, 30 Dec 2017 23:58:39 +0000 Subject: [PATCH] Add writing support to VMware disk image. --- DiscImageChef.DiscImages/VMware.cs | 698 ++++++++++++++++++++--------- 1 file changed, 484 insertions(+), 214 deletions(-) diff --git a/DiscImageChef.DiscImages/VMware.cs b/DiscImageChef.DiscImages/VMware.cs index 08533b83d..f19c64a0f 100644 --- a/DiscImageChef.DiscImages/VMware.cs +++ b/DiscImageChef.DiscImages/VMware.cs @@ -35,6 +35,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; using DiscImageChef.CommonTypes; using DiscImageChef.Console; @@ -42,110 +43,114 @@ using DiscImageChef.Filters; namespace DiscImageChef.DiscImages { - public class VMware : IMediaImage + public class VMware : IWritableImage { const uint VMWARE_EXTENT_MAGIC = 0x564D444B; - const uint VMWARE_COW_MAGIC = 0x44574F43; + const uint VMWARE_COW_MAGIC = 0x44574F43; - const string VM_TYPE_CUSTOM = "custom"; - const string VM_TYPE_MONO_SPARSE = "monolithicSparse"; - const string VM_TYPE_MONO_FLAT = "monolithicFlat"; + const string VM_TYPE_CUSTOM = "custom"; + const string VM_TYPE_MONO_SPARSE = "monolithicSparse"; + const string VM_TYPE_MONO_FLAT = "monolithicFlat"; const string VM_TYPE_SPLIT_SPARSE = "twoGbMaxExtentSparse"; - const string VM_TYPE_SPLIT_FLAT = "twoGbMaxExtentFlat"; - const string VM_TYPE_FULL_DEVICE = "fullDevice"; - const string VM_TYPE_PART_DEVICE = "partitionedDevice"; - const string VMFS_TYPE_FLAT = "vmfsPreallocated"; - const string VMFS_TYPE_ZERO = "vmfsEagerZeroedThick"; - const string VMFS_TYPE_THIN = "vmfsThin"; - const string VMFS_TYPE_SPARSE = "vmfsSparse"; - const string VMFS_TYPE_RDM = "vmfsRDM"; - const string VMFS_TYPE_RDM_OLD = "vmfsRawDeviceMap"; - const string VMFS_TYPE_RDMP = "vmfsRDMP"; - const string VMFS_TYPE_RDMP_OLD = "vmfsPassthroughRawDeviceMap"; - const string VMFS_TYPE_RAW = "vmfsRaw"; - const string VMFS_TYPE = "vmfs"; - const string VM_TYPE_STREAM = "streamOptimized"; + const string VM_TYPE_SPLIT_FLAT = "twoGbMaxExtentFlat"; + const string VM_TYPE_FULL_DEVICE = "fullDevice"; + const string VM_TYPE_PART_DEVICE = "partitionedDevice"; + const string VMFS_TYPE_FLAT = "vmfsPreallocated"; + const string VMFS_TYPE_ZERO = "vmfsEagerZeroedThick"; + const string VMFS_TYPE_THIN = "vmfsThin"; + const string VMFS_TYPE_SPARSE = "vmfsSparse"; + const string VMFS_TYPE_RDM = "vmfsRDM"; + const string VMFS_TYPE_RDM_OLD = "vmfsRawDeviceMap"; + const string VMFS_TYPE_RDMP = "vmfsRDMP"; + const string VMFS_TYPE_RDMP_OLD = "vmfsPassthroughRawDeviceMap"; + const string VMFS_TYPE_RAW = "vmfsRaw"; + const string VMFS_TYPE = "vmfs"; + const string VM_TYPE_STREAM = "streamOptimized"; const string DDF_MAGIC = "# Disk DescriptorFile"; - const string REGEX_VERSION = @"^\s*version\s*=\s*(?\d+)$"; - const string REGEX_CID = @"^\s*CID\s*=\s*(?[0123456789abcdef]{8})$"; + const string REGEX_VERSION = @"^\s*version\s*=\s*(?\d+)$"; + const string REGEX_CID = @"^\s*CID\s*=\s*(?[0123456789abcdef]{8})$"; const string REGEX_CID_PARENT = @"^\s*parentCID\s*=\s*(?[0123456789abcdef]{8})$"; - const string REGEX_TYPE = - @"^\s*createType\s*=\s*\""(?custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$" - ; + const string REGEX_TYPE = + @"^\s*createType\s*=\s*\""(?custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$"; const string REGEX_EXTENT = - @"^\s*(?(RW|RDONLY|NOACCESS))\s+(?\d+)\s+(?(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?.+)\""(\s*(?\d+))?$" - ; - const string REGEX_DDB_TYPE = @"^\s*ddb\.adapterType\s*=\s*\""(?ide|buslogic|lsilogic|legacyESX)\""$"; - const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?\d+)\""$"; - const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?\d+)\""$"; + @"^\s*(?(RW|RDONLY|NOACCESS))\s+(?\d+)\s+(?(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?.+)\""(\s*(?\d+))?$"; + const string REGEX_DDB_TYPE = + @"^\s*ddb\.adapterType\s*=\s*\""(?ide|buslogic|lsilogic|legacyESX)\""$"; + const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?\d+)\""$"; + const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?\d+)\""$"; const string REGEX_DDB_CYLINDERS = @"^\s*ddb\.geometry\.cylinders\s*=\s*\""(?\d+)\""$"; - const string PARENT_REGEX = @"^\s*parentFileNameHint\s*=\s*\""(?.+)\""$"; + const string PARENT_REGEX = @"^\s*parentFileNameHint\s*=\s*\""(?.+)\""$"; - const uint FLAGS_VALID_NEW_LINE = 0x01; + const uint FLAGS_VALID_NEW_LINE = 0x01; const uint FLAGS_USE_REDUNDANT_TABLE = 0x02; - const uint FLAGS_ZERO_GRAIN_GTE = 0x04; - const uint FLAGS_COMPRESSION = 0x10000; - const uint FLAGS_MARKERS = 0x20000; + const uint FLAGS_ZERO_GRAIN_GTE = 0x04; + const uint FLAGS_COMPRESSION = 0x10000; + const uint FLAGS_MARKERS = 0x20000; - const ushort COMPRESSION_NONE = 0; + const ushort COMPRESSION_NONE = 0; const ushort COMPRESSION_DEFLATE = 1; - const uint MAX_CACHE_SIZE = 16777216; - const uint SECTOR_SIZE = 512; - const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; - readonly byte[] ddfMagicBytes = + const uint MAX_CACHE_SIZE = 16777216; + const uint SECTOR_SIZE = 512; + const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / SECTOR_SIZE; + readonly byte[] ddfMagicBytes = { 0x23, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6F, 0x72, 0x46, 0x69, 0x6C, 0x65 }; + string adapter_type; - uint cid; + uint cid; + StreamWriter descriptorStream; Dictionary extents; - IFilter gdFilter; - Dictionary grainCache; + IFilter gdFilter; + Dictionary grainCache; - ulong grainSize; - uint[] gTable; - bool hasParent; + ulong grainSize; + uint[] gTable; + bool hasParent; + int hwversion; ImageInfo imageInfo; - string imageType; - uint maxCachedGrains; - uint parentCid; + string imageType; + uint maxCachedGrains; + uint parentCid; IMediaImage parentImage; - string parentName; + string parentName; Dictionary sectorCache; - uint version; - VMwareCowHeader vmCHdr; + uint version; + VMwareCowHeader vmCHdr; VMwareExtentHeader vmEHdr; + string writingBaseName; + FileStream writingStream; public VMware() { imageInfo = new ImageInfo { - ReadableSectorTags = new List(), - ReadableMediaTags = new List(), - HasPartitions = false, - HasSessions = false, - Version = null, - Application = "VMware", - ApplicationVersion = null, - Creator = null, - Comments = null, - MediaManufacturer = null, - MediaModel = null, - MediaSerialNumber = null, - MediaBarcode = null, - MediaPartNumber = null, - MediaSequence = 0, - LastMediaSequence = 0, - DriveManufacturer = null, - DriveModel = null, - DriveSerialNumber = null, + ReadableSectorTags = new List(), + ReadableMediaTags = new List(), + HasPartitions = false, + HasSessions = false, + Version = null, + Application = "VMware", + ApplicationVersion = null, + Creator = null, + Comments = null, + MediaManufacturer = null, + MediaModel = null, + MediaSerialNumber = null, + MediaBarcode = null, + MediaPartNumber = null, + MediaSequence = 0, + LastMediaSequence = 0, + DriveManufacturer = null, + DriveModel = null, + DriveSerialNumber = null, DriveFirmwareRevision = null }; } @@ -153,7 +158,7 @@ namespace DiscImageChef.DiscImages public ImageInfo Info => imageInfo; public string Name => "VMware disk image"; - public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); + public Guid Id => new Guid("E314DE35-C103-48A3-AD36-990F68523C46"); public string Format => "VMware"; @@ -177,7 +182,7 @@ namespace DiscImageChef.DiscImages stream.Seek(0, SeekOrigin.Begin); byte[] vmEHdrB = new byte[Marshal.SizeOf(vmEHdr)]; stream.Read(vmEHdrB, 0, Marshal.SizeOf(vmEHdr)); - vmEHdr = new VMwareExtentHeader(); + vmEHdr = new VMwareExtentHeader(); IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vmEHdr)); Marshal.Copy(vmEHdrB, 0, headerPtr, Marshal.SizeOf(vmEHdr)); vmEHdr = (VMwareExtentHeader)Marshal.PtrToStructure(headerPtr, typeof(VMwareExtentHeader)); @@ -187,9 +192,9 @@ namespace DiscImageChef.DiscImages stream.Read(ddfMagic, 0, 0x15); vmCHdr = new VMwareCowHeader(); - if(stream.Length <= Marshal.SizeOf(vmCHdr)) + if(stream.Length <= Marshal.SizeOf(vmCHdr)) return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || - vmCHdr.magic == VMWARE_COW_MAGIC; + vmCHdr.magic == VMWARE_COW_MAGIC; stream.Seek(0, SeekOrigin.Begin); byte[] vmCHdrB = new byte[Marshal.SizeOf(vmCHdr)]; @@ -200,7 +205,7 @@ namespace DiscImageChef.DiscImages Marshal.FreeHGlobal(headerPtr); return ddfMagicBytes.SequenceEqual(ddfMagic) || vmEHdr.magic == VMWARE_EXTENT_MAGIC || - vmCHdr.magic == VMWARE_COW_MAGIC; + vmCHdr.magic == VMWARE_COW_MAGIC; } stream.Seek(0, SeekOrigin.Begin); @@ -213,8 +218,8 @@ namespace DiscImageChef.DiscImages { Stream stream = imageFilter.GetDataForkStream(); - vmEHdr = new VMwareExtentHeader(); - vmCHdr = new VMwareCowHeader(); + vmEHdr = new VMwareExtentHeader(); + vmCHdr = new VMwareCowHeader(); bool embedded = false; if(stream.Length > Marshal.SizeOf(vmEHdr)) @@ -240,13 +245,13 @@ namespace DiscImageChef.DiscImages } MemoryStream ddfStream = new MemoryStream(); - bool vmEHdrSet = false; - bool cowD = false; + bool vmEHdrSet = false; + bool cowD = false; if(vmEHdr.magic == VMWARE_EXTENT_MAGIC) { vmEHdrSet = true; - gdFilter = imageFilter; + gdFilter = imageFilter; if(vmEHdr.descriptorOffset == 0 || vmEHdr.descriptorSize == 0) throw new Exception("Please open VMDK descriptor."); @@ -262,7 +267,7 @@ namespace DiscImageChef.DiscImages else if(vmCHdr.magic == VMWARE_COW_MAGIC) { gdFilter = imageFilter; - cowD = true; + cowD = true; } else { @@ -278,26 +283,26 @@ namespace DiscImageChef.DiscImages ddfStream.Write(ddfExternal, 0, ddfExternal.Length); } - extents = new Dictionary(); + extents = new Dictionary(); ulong currentSector = 0; bool matchedCyls = false, matchedHds = false, matchedSpt = false; if(cowD) { - int cowCount = 1; + int cowCount = 1; string basePath = Path.GetFileNameWithoutExtension(imageFilter.GetBasePath()); while(true) { string curPath; if(cowCount == 1) curPath = basePath + ".vmdk"; - else curPath = $"{basePath}-{cowCount:D2}.vmdk"; + else curPath = $"{basePath}-{cowCount:D2}.vmdk"; if(!File.Exists(curPath)) break; IFilter extentFilter = new FiltersList().GetFilter(curPath); - Stream extentStream = extentFilter.GetDataForkStream(); + Stream extentStream = extentFilter.GetDataForkStream(); if(stream.Length > Marshal.SizeOf(vmCHdr)) { @@ -314,12 +319,12 @@ namespace DiscImageChef.DiscImages VMwareExtent newExtent = new VMwareExtent { - Access = "RW", - Filter = extentFilter, + Access = "RW", + Filter = extentFilter, Filename = extentFilter.GetFilename(), - Offset = 0, - Sectors = extHdrCow.sectors, - Type = "SPARSE" + Offset = 0, + Sectors = extHdrCow.sectors, + Type = "SPARSE" }; DicConsole.DebugWriteLine("VMware plugin", "{0} {1} {2} \"{3}\" {4}", newExtent.Access, @@ -340,15 +345,15 @@ namespace DiscImageChef.DiscImages { ddfStream.Seek(0, SeekOrigin.Begin); - Regex regexVersion = new Regex(REGEX_VERSION); - Regex regexCid = new Regex(REGEX_CID); + Regex regexVersion = new Regex(REGEX_VERSION); + Regex regexCid = new Regex(REGEX_CID); Regex regexParentCid = new Regex(REGEX_CID_PARENT); - Regex regexType = new Regex(REGEX_TYPE); - Regex regexExtent = new Regex(REGEX_EXTENT); - Regex regexParent = new Regex(PARENT_REGEX); + Regex regexType = new Regex(REGEX_TYPE); + Regex regexExtent = new Regex(REGEX_EXTENT); + Regex regexParent = new Regex(PARENT_REGEX); Regex regexCylinders = new Regex(REGEX_DDB_CYLINDERS); - Regex regexHeads = new Regex(REGEX_DDB_HEADS); - Regex regexSectors = new Regex(REGEX_DDB_SECTORS); + Regex regexHeads = new Regex(REGEX_DDB_HEADS); + Regex regexSectors = new Regex(REGEX_DDB_SECTORS); StreamReader ddfStreamRdr = new StreamReader(ddfStream); @@ -356,15 +361,15 @@ namespace DiscImageChef.DiscImages { string line = ddfStreamRdr.ReadLine(); - Match matchVersion = regexVersion.Match(line); - Match matchCid = regexCid.Match(line); + Match matchVersion = regexVersion.Match(line); + Match matchCid = regexCid.Match(line); Match matchParentCid = regexParentCid.Match(line); - Match matchType = regexType.Match(line); - Match matchExtent = regexExtent.Match(line); - Match matchParent = regexParent.Match(line); + Match matchType = regexType.Match(line); + Match matchExtent = regexExtent.Match(line); + Match matchParent = regexParent.Match(line); Match matchCylinders = regexCylinders.Match(line); - Match matchHeads = regexHeads.Match(line); - Match matchSectors = regexSectors.Match(line); + Match matchHeads = regexHeads.Match(line); + Match matchSectors = regexSectors.Match(line); if(matchVersion.Success) { @@ -392,10 +397,10 @@ namespace DiscImageChef.DiscImages if(!embedded) newExtent.Filter = new FiltersList() - .GetFilter(Path.Combine(Path.GetDirectoryName(imageFilter.GetBasePath()), - matchExtent.Groups["filename"].Value)); + .GetFilter(Path.Combine(Path.GetDirectoryName(imageFilter.GetBasePath()), + matchExtent.Groups["filename"].Value)); else newExtent.Filter = imageFilter; - uint.TryParse(matchExtent.Groups["offset"].Value, out newExtent.Offset); + uint.TryParse(matchExtent.Groups["offset"].Value, out newExtent.Offset); uint.TryParse(matchExtent.Groups["sectors"].Value, out newExtent.Sectors); newExtent.Type = matchExtent.Groups["type"].Value; DicConsole.DebugWriteLine("VMware plugin", "{0} {1} {2} \"{3}\" {4}", newExtent.Access, @@ -433,24 +438,24 @@ namespace DiscImageChef.DiscImages switch(imageType) { - case VM_TYPE_MONO_SPARSE: //"monolithicSparse"; - case VM_TYPE_MONO_FLAT: //"monolithicFlat"; + case VM_TYPE_MONO_SPARSE: //"monolithicSparse"; + case VM_TYPE_MONO_FLAT: //"monolithicFlat"; case VM_TYPE_SPLIT_SPARSE: //"twoGbMaxExtentSparse"; - case VM_TYPE_SPLIT_FLAT: //"twoGbMaxExtentFlat"; - case VMFS_TYPE_FLAT: //"vmfsPreallocated"; - case VMFS_TYPE_ZERO: //"vmfsEagerZeroedThick"; - case VMFS_TYPE_THIN: //"vmfsThin"; - case VMFS_TYPE_SPARSE: //"vmfsSparse"; - case VMFS_TYPE: //"vmfs"; - case VM_TYPE_STREAM: //"streamOptimized"; + case VM_TYPE_SPLIT_FLAT: //"twoGbMaxExtentFlat"; + case VMFS_TYPE_FLAT: //"vmfsPreallocated"; + case VMFS_TYPE_ZERO: //"vmfsEagerZeroedThick"; + case VMFS_TYPE_THIN: //"vmfsThin"; + case VMFS_TYPE_SPARSE: //"vmfsSparse"; + case VMFS_TYPE: //"vmfs"; + case VM_TYPE_STREAM: //"streamOptimized"; break; case VM_TYPE_FULL_DEVICE: //"fullDevice"; case VM_TYPE_PART_DEVICE: //"partitionedDevice"; - case VMFS_TYPE_RDM: //"vmfsRDM"; - case VMFS_TYPE_RDM_OLD: //"vmfsRawDeviceMap"; - case VMFS_TYPE_RDMP: //"vmfsRDMP"; - case VMFS_TYPE_RDMP_OLD: //"vmfsPassthroughRawDeviceMap"; - case VMFS_TYPE_RAW: //"vmfsRaw"; + case VMFS_TYPE_RDM: //"vmfsRDM"; + case VMFS_TYPE_RDM_OLD: //"vmfsRawDeviceMap"; + case VMFS_TYPE_RDMP: //"vmfsRDMP"; + case VMFS_TYPE_RDMP_OLD: //"vmfsPassthroughRawDeviceMap"; + case VMFS_TYPE_RAW: //"vmfsRaw"; throw new ImageNotSupportedException("Raw device image files are not supported, try accessing the device directly."); default: throw new ImageNotSupportedException($"Dunno how to handle \"{imageType}\" extents."); @@ -471,8 +476,8 @@ namespace DiscImageChef.DiscImages if(extentStream.Length < SECTOR_SIZE) throw new Exception($"Extent {extent.Filename} is too small."); - VMwareExtentHeader extentHdr = new VMwareExtentHeader(); - byte[] extentHdrB = new byte[Marshal.SizeOf(extentHdr)]; + VMwareExtentHeader extentHdr = new VMwareExtentHeader(); + byte[] extentHdrB = new byte[Marshal.SizeOf(extentHdr)]; extentStream.Read(extentHdrB, 0, Marshal.SizeOf(extentHdr)); IntPtr extentHdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(extentHdr)); Marshal.Copy(extentHdrB, 0, extentHdrPtr, Marshal.SizeOf(extentHdr)); @@ -492,8 +497,8 @@ namespace DiscImageChef.DiscImages if(!vmEHdrSet) { - vmEHdr = extentHdr; - gdFilter = extent.Filter; + vmEHdr = extentHdr; + gdFilter = extent.Filter; vmEHdrSet = true; } @@ -506,25 +511,25 @@ namespace DiscImageChef.DiscImages imageInfo.Sectors = currentSector; - uint grains = 0; + uint grains = 0; uint gdEntries = 0; - long gdOffset = 0; + long gdOffset = 0; uint gtEsPerGt = 0; if(oneNoFlat && !cowD) { - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.magic = 0x{0:X8}", vmEHdr.magic); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.version = {0}", vmEHdr.version); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.flags = 0x{0:X8}", vmEHdr.flags); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.capacity = {0}", vmEHdr.capacity); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.grainSize = {0}", vmEHdr.grainSize); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.magic = 0x{0:X8}", vmEHdr.magic); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.version = {0}", vmEHdr.version); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.flags = 0x{0:X8}", vmEHdr.flags); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.capacity = {0}", vmEHdr.capacity); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.grainSize = {0}", vmEHdr.grainSize); DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.descriptorOffset = {0}", vmEHdr.descriptorOffset); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.descriptorSize = {0}", vmEHdr.descriptorSize); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.GTEsPerGT = {0}", vmEHdr.GTEsPerGT); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.rgdOffset = {0}", vmEHdr.rgdOffset); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.gdOffset = {0}", vmEHdr.gdOffset); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.overhead = {0}", vmEHdr.overhead); - DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.uncleanShutdown = {0}", vmEHdr.uncleanShutdown); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.descriptorSize = {0}", vmEHdr.descriptorSize); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.GTEsPerGT = {0}", vmEHdr.GTEsPerGT); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.rgdOffset = {0}", vmEHdr.rgdOffset); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.gdOffset = {0}", vmEHdr.gdOffset); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.overhead = {0}", vmEHdr.overhead); + DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.uncleanShutdown = {0}", vmEHdr.uncleanShutdown); DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.singleEndLineChar = 0x{0:X2}", vmEHdr.singleEndLineChar); DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.nonEndLineChar = 0x{0:X2}", vmEHdr.nonEndLineChar); @@ -535,42 +540,43 @@ namespace DiscImageChef.DiscImages DicConsole.DebugWriteLine("VMware plugin", "vmEHdr.compression = 0x{0:X4}", vmEHdr.compression); grainSize = vmEHdr.grainSize; - grains = (uint)(imageInfo.Sectors / vmEHdr.grainSize) + 1; - gdEntries = grains / vmEHdr.GTEsPerGT; + grains = (uint)(imageInfo.Sectors / vmEHdr.grainSize) + 1; + gdEntries = grains / vmEHdr.GTEsPerGT; gtEsPerGt = vmEHdr.GTEsPerGT; if((vmEHdr.flags & FLAGS_USE_REDUNDANT_TABLE) == FLAGS_USE_REDUNDANT_TABLE) - gdOffset = (long)vmEHdr.rgdOffset; + gdOffset = (long)vmEHdr.rgdOffset; else gdOffset = (long)vmEHdr.gdOffset; } else if(oneNoFlat && cowD) { - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.magic = 0x{0:X8}", vmCHdr.magic); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.version = {0}", vmCHdr.version); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.flags = 0x{0:X8}", vmCHdr.flags); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.sectors = {0}", vmCHdr.sectors); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.grainSize = {0}", vmCHdr.grainSize); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.gdOffset = {0}", vmCHdr.gdOffset); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.magic = 0x{0:X8}", vmCHdr.magic); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.version = {0}", vmCHdr.version); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.flags = 0x{0:X8}", vmCHdr.flags); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.sectors = {0}", vmCHdr.sectors); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.grainSize = {0}", vmCHdr.grainSize); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.gdOffset = {0}", vmCHdr.gdOffset); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.numGDEntries = {0}", vmCHdr.numGDEntries); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.freeSector = {0}", vmCHdr.freeSector); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.cylinders = {0}", vmCHdr.cylinders); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.heads = {0}", vmCHdr.heads); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.spt = {0}", vmCHdr.spt); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.generation = {0}", vmCHdr.generation); - DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.name = {0}", StringHandlers.CToString(vmCHdr.name)); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.freeSector = {0}", vmCHdr.freeSector); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.cylinders = {0}", vmCHdr.cylinders); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.heads = {0}", vmCHdr.heads); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.spt = {0}", vmCHdr.spt); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.generation = {0}", vmCHdr.generation); + DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.name = {0}", + StringHandlers.CToString(vmCHdr.name)); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.description = {0}", StringHandlers.CToString(vmCHdr.description)); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.savedGeneration = {0}", vmCHdr.savedGeneration); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.uncleanShutdown = {0}", vmCHdr.uncleanShutdown); - grainSize = vmCHdr.grainSize; - grains = (uint)(imageInfo.Sectors / vmCHdr.grainSize) + 1; - gdEntries = vmCHdr.numGDEntries; - gdOffset = vmCHdr.gdOffset; - gtEsPerGt = grains / gdEntries; + grainSize = vmCHdr.grainSize; + grains = (uint)(imageInfo.Sectors / vmCHdr.grainSize) + 1; + gdEntries = vmCHdr.numGDEntries; + gdOffset = vmCHdr.gdOffset; + gtEsPerGt = grains / gdEntries; imageInfo.MediaTitle = StringHandlers.CToString(vmCHdr.name); - imageInfo.Comments = StringHandlers.CToString(vmCHdr.description); - version = vmCHdr.version; + imageInfo.Comments = StringHandlers.CToString(vmCHdr.description); + version = vmCHdr.version; } if(oneNoFlat) @@ -585,18 +591,18 @@ namespace DiscImageChef.DiscImages gdStream.Seek(gdOffset * SECTOR_SIZE, SeekOrigin.Begin); DicConsole.DebugWriteLine("VMware plugin", "Reading grain directory"); - uint[] gd = new uint[gdEntries]; + uint[] gd = new uint[gdEntries]; byte[] gdBytes = new byte[gdEntries * 4]; gdStream.Read(gdBytes, 0, gdBytes.Length); for(int i = 0; i < gdEntries; i++) gd[i] = BitConverter.ToUInt32(gdBytes, i * 4); DicConsole.DebugWriteLine("VMware plugin", "Reading grain tables"); uint currentGrain = 0; - gTable = new uint[grains]; + gTable = new uint[grains]; foreach(uint gtOff in gd) { byte[] gtBytes = new byte[gtEsPerGt * 4]; - gdStream.Seek(gtOff * SECTOR_SIZE, SeekOrigin.Begin); + gdStream.Seek(gtOff * SECTOR_SIZE, SeekOrigin.Begin); gdStream.Read(gtBytes, 0, gtBytes.Length); for(int i = 0; i < gtEsPerGt; i++) { @@ -623,26 +629,26 @@ namespace DiscImageChef.DiscImages sectorCache = new Dictionary(); - imageInfo.CreationTime = imageFilter.GetCreationTime(); + imageInfo.CreationTime = imageFilter.GetCreationTime(); imageInfo.LastModificationTime = imageFilter.GetLastWriteTime(); - imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); - imageInfo.SectorSize = SECTOR_SIZE; - imageInfo.XmlMediaType = XmlMediaType.BlockMedia; - imageInfo.MediaType = MediaType.GENERIC_HDD; - imageInfo.ImageSize = imageInfo.Sectors * SECTOR_SIZE; + imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + imageInfo.SectorSize = SECTOR_SIZE; + imageInfo.XmlMediaType = XmlMediaType.BlockMedia; + imageInfo.MediaType = MediaType.GENERIC_HDD; + imageInfo.ImageSize = imageInfo.Sectors * SECTOR_SIZE; // VMDK version 1 started on VMware 4, so there is a previous version, "COWD" imageInfo.Version = cowD ? $"{version}" : $"{version + 3}"; if(cowD) { - imageInfo.Cylinders = vmCHdr.cylinders; - imageInfo.Heads = vmCHdr.heads; + imageInfo.Cylinders = vmCHdr.cylinders; + imageInfo.Heads = vmCHdr.heads; imageInfo.SectorsPerTrack = vmCHdr.spt; } else if(!matchedCyls || !matchedHds || !matchedSpt) { - imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); - imageInfo.Heads = 16; + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; imageInfo.SectorsPerTrack = 63; } @@ -657,14 +663,14 @@ namespace DiscImageChef.DiscImages if(sectorCache.TryGetValue(sectorAddress, out byte[] sector)) return sector; - VMwareExtent currentExtent = new VMwareExtent(); - bool extentFound = false; - ulong extentStartSector = 0; + VMwareExtent currentExtent = new VMwareExtent(); + bool extentFound = false; + ulong extentStartSector = 0; foreach(KeyValuePair kvp in extents.Where(kvp => sectorAddress >= kvp.Key)) { - currentExtent = kvp.Value; - extentFound = true; + currentExtent = kvp.Value; + extentFound = true; extentStartSector = kvp.Key; } @@ -697,7 +703,7 @@ namespace DiscImageChef.DiscImages return sector; } - ulong index = sectorAddress / grainSize; + ulong index = sectorAddress / grainSize; ulong secOff = sectorAddress % grainSize * SECTOR_SIZE; uint grainOff = gTable[index]; @@ -716,7 +722,7 @@ namespace DiscImageChef.DiscImages if(!grainCache.TryGetValue(grainOff, out byte[] grain)) { - grain = new byte[SECTOR_SIZE * grainSize]; + grain = new byte[SECTOR_SIZE * grainSize]; dataStream = currentExtent.Filter.GetDataForkStream(); dataStream.Seek((long)((grainOff - extentStartSector) * SECTOR_SIZE), SeekOrigin.Begin); dataStream.Read(grain, 0, grain.Length); @@ -832,7 +838,7 @@ namespace DiscImageChef.DiscImages } public bool? VerifySectors(ulong sectorAddress, uint length, out List failingLbas, - out List unknownLbas) + out List unknownLbas) { failingLbas = new List(); unknownLbas = new List(); @@ -842,7 +848,7 @@ namespace DiscImageChef.DiscImages } public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List failingLbas, - out List unknownLbas) + out List unknownLbas) { throw new FeatureUnsupportedImageException("Feature not supported by image format"); } @@ -852,27 +858,286 @@ namespace DiscImageChef.DiscImages return null; } + public IEnumerable SupportedMediaTags => new MediaTagType[] { }; + public IEnumerable SupportedSectorTags => new SectorTagType[] { }; + public IEnumerable SupportedMediaTypes => + new[] {MediaType.GENERIC_HDD, MediaType.Unknown}; + public IEnumerable<(string name, Type type, string description)> SupportedOptions => + new[] + { + ("adapter_type", typeof(string), + "Type of adapter type. Possible values: ide, lsilogic, buslogic, legacyESX."), + ("hwversion", typeof(int), "VDMK hardware version."), ("sparse", typeof(bool), "Use sparse extents."), + ("split", typeof(bool), "Split data file at 2GiB.") + }; + public IEnumerable KnownExtensions => new[] {".vmdk"}; + public bool IsWriting { get; private set; } + public string ErrorMessage { get; private set; } + + public bool Create(string path, MediaType mediaType, Dictionary options, ulong sectors, + uint sectorSize) + { + if(options != null) + { + if(options.TryGetValue("adapter", out adapter_type)) + switch(adapter_type.ToLowerInvariant()) + { + case "ide": + case "lsilogic": + case "buslogic": + adapter_type = adapter_type.ToLowerInvariant(); + break; + case "legacyesx": + adapter_type = "legacyESX"; + break; + default: + ErrorMessage = $"Invalid adapter type {adapter_type}"; + return false; + } + else adapter_type = "ide"; + + if(options.TryGetValue("hwversion", out string tmpValue)) + { + if(!int.TryParse(tmpValue, out hwversion)) + { + ErrorMessage = "Invalid value for hwversion option"; + return false; + } + } + else hwversion = 4; + + if(options.TryGetValue("split", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out bool tmpBool)) + { + ErrorMessage = "Invalid value for split option"; + return false; + } + + if(tmpBool) + { + ErrorMessage = "Splitted images not yet implemented"; + return false; + } + } + + if(options.TryGetValue("sparse", out tmpValue)) + { + if(!bool.TryParse(tmpValue, out bool tmpBool)) + { + ErrorMessage = "Invalid value for sparse option"; + return false; + } + + if(tmpBool) + { + ErrorMessage = "Sparse images not yet implemented"; + return false; + } + } + } + else + { + adapter_type = "ide"; + hwversion = 4; + } + + if(sectorSize != 512) + { + ErrorMessage = "Unsupported sector size"; + return false; + } + + if(!SupportedMediaTypes.Contains(mediaType)) + { + ErrorMessage = $"Unsupport media format {mediaType}"; + return false; + } + + imageInfo = new ImageInfo {MediaType = mediaType, SectorSize = sectorSize, Sectors = sectors}; + + try + { + writingBaseName = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); + descriptorStream = new StreamWriter(path, false, Encoding.ASCII); + // TODO: Support split + writingStream = new FileStream(writingBaseName + "-flat.vmdk", FileMode.CreateNew, FileAccess.ReadWrite, + FileShare.None); + } + catch(IOException e) + { + ErrorMessage = $"Could not create new image file, exception {e.Message}"; + return false; + } + + IsWriting = true; + ErrorMessage = null; + return true; + } + + public bool WriteMediaTag(byte[] data, MediaTagType tag) + { + ErrorMessage = "Writing media tags is not supported."; + return false; + } + + public bool WriteSector(byte[] data, ulong sectorAddress) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length != 512) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress >= imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + // TODO: Implement sparse and split + public bool WriteSectors(byte[] data, ulong sectorAddress, uint length) + { + if(!IsWriting) + { + ErrorMessage = "Tried to write on a non-writable image"; + return false; + } + + if(data.Length % 512 != 0) + { + ErrorMessage = "Incorrect data size"; + return false; + } + + if(sectorAddress + length > imageInfo.Sectors) + { + ErrorMessage = "Tried to write past image size"; + return false; + } + + writingStream.Seek((long)(sectorAddress * 512), SeekOrigin.Begin); + writingStream.Write(data, 0, data.Length); + + ErrorMessage = ""; + return true; + } + + public bool WriteSectorLong(byte[] data, ulong sectorAddress) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool WriteSectorsLong(byte[] data, ulong sectorAddress, uint length) + { + ErrorMessage = "Writing sectors with tags is not supported."; + return false; + } + + public bool SetTracks(List tracks) + { + ErrorMessage = "Unsupported feature"; + return false; + } + + // TODO: Implement sparse and split + public bool Close() + { + if(!IsWriting) + { + ErrorMessage = "Image is not opened for writing"; + return false; + } + + writingStream.Flush(); + writingStream.Close(); + + // TODO: Interface to set geometry + imageInfo.Cylinders = (uint)(imageInfo.Sectors / 16 / 63); + imageInfo.Heads = 16; + imageInfo.SectorsPerTrack = 63; + + while(imageInfo.Cylinders == 0) + { + imageInfo.Heads--; + + if(imageInfo.Heads == 0) + { + imageInfo.SectorsPerTrack--; + imageInfo.Heads = 16; + } + + imageInfo.Cylinders = (uint)(imageInfo.Sectors / imageInfo.Heads / imageInfo.SectorsPerTrack); + + if(imageInfo.Sectors == 0 && imageInfo.Heads == 0 && imageInfo.SectorsPerTrack == 0) break; + } + + descriptorStream.WriteLine("# Disk DescriptorFile"); + descriptorStream.WriteLine("version=1"); + descriptorStream.WriteLine($"CID={new Random().Next(int.MinValue, int.MaxValue):x8}"); + descriptorStream.WriteLine("parentCID=ffffffff"); + descriptorStream.WriteLine("createType=\"monolithicFlat\""); + descriptorStream.WriteLine(); + descriptorStream.WriteLine("# Extent description"); + descriptorStream.WriteLine($"RW {imageInfo.Sectors} FLAT \"{writingBaseName + "-flat.vmdk"}\" 0"); + descriptorStream.WriteLine(); + descriptorStream.WriteLine("# The Disk Data Base"); + descriptorStream.WriteLine("#DDB"); + descriptorStream.WriteLine(); + descriptorStream.WriteLine($"ddb.virtualHWVersion = \"{hwversion}\""); + descriptorStream.WriteLine($"ddb.geometry.cylinders = \"{imageInfo.Cylinders}\""); + descriptorStream.WriteLine($"ddb.geometry.heads = \"{imageInfo.Heads}\""); + descriptorStream.WriteLine($"ddb.geometry.sectors = \"{imageInfo.SectorsPerTrack}\""); + descriptorStream.WriteLine($"ddb.adapterType = \"{adapter_type}\""); + + descriptorStream.Flush(); + descriptorStream.Close(); + IsWriting = false; + + return true; + } + + public bool SetMetadata(ImageInfo metadata) + { + return true; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] struct VMwareExtentHeader { - public uint magic; - public uint version; - public uint flags; - public ulong capacity; - public ulong grainSize; - public ulong descriptorOffset; - public ulong descriptorSize; - public uint GTEsPerGT; - public ulong rgdOffset; - public ulong gdOffset; - public ulong overhead; - [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; - public byte singleEndLineChar; - public byte nonEndLineChar; - public byte doubleEndLineChar1; - public byte doubleEndLineChar2; - public ushort compression; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)] public byte[] padding; + public uint magic; + public uint version; + public uint flags; + public ulong capacity; + public ulong grainSize; + public ulong descriptorOffset; + public ulong descriptorSize; + public uint GTEsPerGT; + public ulong rgdOffset; + public ulong gdOffset; + public ulong overhead; + [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; + public byte singleEndLineChar; + public byte nonEndLineChar; + public byte doubleEndLineChar1; + public byte doubleEndLineChar2; + public ushort compression; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)] + public byte[] padding; } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -890,25 +1155,30 @@ namespace DiscImageChef.DiscImages public uint heads; public uint spt; // It stats on cylinders, above, but, don't care - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)] public byte[] parentFileName; - public uint parentGeneration; - public uint generation; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] public byte[] name; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] public byte[] description; - public uint savedGeneration; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] reserved; - [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)] public byte[] padding; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)] + public byte[] parentFileName; + public uint parentGeneration; + public uint generation; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] + public byte[] name; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] description; + public uint savedGeneration; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] reserved; + [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)] + public byte[] padding; } struct VMwareExtent { - public string Access; - public uint Sectors; - public string Type; + public string Access; + public uint Sectors; + public string Type; public IFilter Filter; - public string Filename; - public uint Offset; + public string Filename; + public uint Offset; } } } \ No newline at end of file