Add writing support to VMware disk image.

This commit is contained in:
2017-12-30 23:58:39 +00:00
parent 8e4810516a
commit 880462f7a3

View File

@@ -35,6 +35,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes;
using DiscImageChef.Console; using DiscImageChef.Console;
@@ -42,7 +43,7 @@ using DiscImageChef.Filters;
namespace DiscImageChef.DiscImages namespace DiscImageChef.DiscImages
{ {
public class VMware : IMediaImage public class VMware : IWritableImage
{ {
const uint VMWARE_EXTENT_MAGIC = 0x564D444B; const uint VMWARE_EXTENT_MAGIC = 0x564D444B;
const uint VMWARE_COW_MAGIC = 0x44574F43; const uint VMWARE_COW_MAGIC = 0x44574F43;
@@ -72,12 +73,11 @@ namespace DiscImageChef.DiscImages
const string REGEX_CID = @"^\s*CID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; const string REGEX_CID = @"^\s*CID\s*=\s*(?<cid>[0123456789abcdef]{8})$";
const string REGEX_CID_PARENT = @"^\s*parentCID\s*=\s*(?<cid>[0123456789abcdef]{8})$"; const string REGEX_CID_PARENT = @"^\s*parentCID\s*=\s*(?<cid>[0123456789abcdef]{8})$";
const string REGEX_TYPE = const string REGEX_TYPE =
@"^\s*createType\s*=\s*\""(?<type>custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$" @"^\s*createType\s*=\s*\""(?<type>custom|monolithicSparse|monolithicFlat|twoGbMaxExtentSparse|twoGbMaxExtentFlat|fullDevice|partitionedDevice|vmfs|vmfsPreallocated|vmfsEagerZeroedThick|vmfsThin|vmfsSparse|vmfsRDM|vmfsRawDeviceMap|vmfsRDMP|vmfsPassthroughRawDeviceMap|vmfsRaw|streamOptimized)\""$";
;
const string REGEX_EXTENT = const string REGEX_EXTENT =
@"^\s*(?<access>(RW|RDONLY|NOACCESS))\s+(?<sectors>\d+)\s+(?<type>(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?<filename>.+)\""(\s*(?<offset>\d+))?$" @"^\s*(?<access>(RW|RDONLY|NOACCESS))\s+(?<sectors>\d+)\s+(?<type>(FLAT|SPARSE|ZERO|VMFS|VMFSSPARSE|VMFSRDM|VMFSRAW))\s+\""(?<filename>.+)\""(\s*(?<offset>\d+))?$";
; const string REGEX_DDB_TYPE =
const string REGEX_DDB_TYPE = @"^\s*ddb\.adapterType\s*=\s*\""(?<type>ide|buslogic|lsilogic|legacyESX)\""$"; @"^\s*ddb\.adapterType\s*=\s*\""(?<type>ide|buslogic|lsilogic|legacyESX)\""$";
const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?<sectors>\d+)\""$"; const string REGEX_DDB_SECTORS = @"^\s*ddb\.geometry\.sectors\s*=\s*\""(?<sectors>\d+)\""$";
const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?<heads>\d+)\""$"; const string REGEX_DDB_HEADS = @"^\s*ddb\.geometry\.heads\s*=\s*\""(?<heads>\d+)\""$";
const string REGEX_DDB_CYLINDERS = @"^\s*ddb\.geometry\.cylinders\s*=\s*\""(?<cylinders>\d+)\""$"; const string REGEX_DDB_CYLINDERS = @"^\s*ddb\.geometry\.cylinders\s*=\s*\""(?<cylinders>\d+)\""$";
@@ -100,8 +100,10 @@ namespace DiscImageChef.DiscImages
0x23, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6F, 0x72, 0x46, 0x23, 0x20, 0x44, 0x69, 0x73, 0x6B, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6F, 0x72, 0x46,
0x69, 0x6C, 0x65 0x69, 0x6C, 0x65
}; };
string adapter_type;
uint cid; uint cid;
StreamWriter descriptorStream;
Dictionary<ulong, VMwareExtent> extents; Dictionary<ulong, VMwareExtent> extents;
IFilter gdFilter; IFilter gdFilter;
Dictionary<ulong, byte[]> grainCache; Dictionary<ulong, byte[]> grainCache;
@@ -109,6 +111,7 @@ namespace DiscImageChef.DiscImages
ulong grainSize; ulong grainSize;
uint[] gTable; uint[] gTable;
bool hasParent; bool hasParent;
int hwversion;
ImageInfo imageInfo; ImageInfo imageInfo;
string imageType; string imageType;
uint maxCachedGrains; uint maxCachedGrains;
@@ -122,6 +125,8 @@ namespace DiscImageChef.DiscImages
VMwareCowHeader vmCHdr; VMwareCowHeader vmCHdr;
VMwareExtentHeader vmEHdr; VMwareExtentHeader vmEHdr;
string writingBaseName;
FileStream writingStream;
public VMware() public VMware()
{ {
@@ -557,7 +562,8 @@ namespace DiscImageChef.DiscImages
DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.heads = {0}", vmCHdr.heads); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.heads = {0}", vmCHdr.heads);
DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.spt = {0}", vmCHdr.spt); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.spt = {0}", vmCHdr.spt);
DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.generation = {0}", vmCHdr.generation); 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.name = {0}",
StringHandlers.CToString(vmCHdr.name));
DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.description = {0}", DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.description = {0}",
StringHandlers.CToString(vmCHdr.description)); StringHandlers.CToString(vmCHdr.description));
DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.savedGeneration = {0}", vmCHdr.savedGeneration); DicConsole.DebugWriteLine("VMware plugin", "vmCHdr.savedGeneration = {0}", vmCHdr.savedGeneration);
@@ -852,6 +858,264 @@ namespace DiscImageChef.DiscImages
return null; return null;
} }
public IEnumerable<MediaTagType> SupportedMediaTags => new MediaTagType[] { };
public IEnumerable<SectorTagType> SupportedSectorTags => new SectorTagType[] { };
public IEnumerable<MediaType> 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<string> KnownExtensions => new[] {".vmdk"};
public bool IsWriting { get; private set; }
public string ErrorMessage { get; private set; }
public bool Create(string path, MediaType mediaType, Dictionary<string, string> 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<Track> 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)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct VMwareExtentHeader struct VMwareExtentHeader
{ {
@@ -872,7 +1136,8 @@ namespace DiscImageChef.DiscImages
public byte doubleEndLineChar1; public byte doubleEndLineChar1;
public byte doubleEndLineChar2; public byte doubleEndLineChar2;
public ushort compression; public ushort compression;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)] public byte[] padding; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 433)]
public byte[] padding;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -890,15 +1155,20 @@ namespace DiscImageChef.DiscImages
public uint heads; public uint heads;
public uint spt; public uint spt;
// It stats on cylinders, above, but, don't care // It stats on cylinders, above, but, don't care
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)] public byte[] parentFileName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024 - 12)]
public byte[] parentFileName;
public uint parentGeneration; public uint parentGeneration;
public uint generation; public uint generation;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] public byte[] name; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] public byte[] description; public byte[] name;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
public byte[] description;
public uint savedGeneration; public uint savedGeneration;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] reserved; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] reserved;
[MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown; [MarshalAs(UnmanagedType.U1)] public bool uncleanShutdown;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)] public byte[] padding; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 396)]
public byte[] padding;
} }
struct VMwareExtent struct VMwareExtent