Files
Aaru/Aaru.Images/VHDX/Read.cs

592 lines
22 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Read.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Disk image plugins.
//
// --[ Description ] ----------------------------------------------------------
//
// Reads Microsoft Hyper-V disk images.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
using Aaru.Logging;
using Sentry;
namespace Aaru.Images;
public sealed partial class Vhdx
{
#region IMediaImage Members
/// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter)
{
Stream stream = imageFilter.GetDataForkStream();
stream.Seek(0, SeekOrigin.Begin);
if(stream.Length < 512) return ErrorNumber.InvalidArgument;
var vhdxIdB = new byte[Marshal.SizeOf<Identifier>()];
stream.EnsureRead(vhdxIdB, 0, Marshal.SizeOf<Identifier>());
_id = Marshal.ByteArrayToStructureLittleEndian<Identifier>(vhdxIdB);
if(_id.signature != VHDX_SIGNATURE) return ErrorNumber.InvalidArgument;
_imageInfo.Application = Encoding.Unicode.GetString(_id.creator);
stream.Seek(64 * 1024, SeekOrigin.Begin);
var vHdrB = new byte[Marshal.SizeOf<Header>()];
stream.EnsureRead(vHdrB, 0, Marshal.SizeOf<Header>());
_vHdr = Marshal.ByteArrayToStructureLittleEndian<Header>(vHdrB);
if(_vHdr.Signature != VHDX_HEADER_SIG)
{
stream.Seek(128 * 1024, SeekOrigin.Begin);
vHdrB = new byte[Marshal.SizeOf<Header>()];
stream.EnsureRead(vHdrB, 0, Marshal.SizeOf<Header>());
_vHdr = Marshal.ByteArrayToStructureLittleEndian<Header>(vHdrB);
if(_vHdr.Signature != VHDX_HEADER_SIG)
{
AaruLogging.Error(Localization.VHDX_header_not_found);
return ErrorNumber.InvalidArgument;
}
}
stream.Seek(192 * 1024, SeekOrigin.Begin);
var vRegTableB = new byte[Marshal.SizeOf<RegionTableHeader>()];
stream.EnsureRead(vRegTableB, 0, Marshal.SizeOf<RegionTableHeader>());
_vRegHdr = Marshal.ByteArrayToStructureLittleEndian<RegionTableHeader>(vRegTableB);
if(_vRegHdr.signature != VHDX_REGION_SIG)
{
stream.Seek(256 * 1024, SeekOrigin.Begin);
vRegTableB = new byte[Marshal.SizeOf<RegionTableHeader>()];
stream.EnsureRead(vRegTableB, 0, Marshal.SizeOf<RegionTableHeader>());
_vRegHdr = Marshal.ByteArrayToStructureLittleEndian<RegionTableHeader>(vRegTableB);
if(_vRegHdr.signature != VHDX_REGION_SIG)
{
AaruLogging.Error(Localization.VHDX_region_table_not_found);
return ErrorNumber.InvalidArgument;
}
}
_vRegs = new RegionTableEntry[_vRegHdr.entries];
for(var i = 0; i < _vRegs.Length; i++)
{
var vRegB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vRegs[i])];
stream.EnsureRead(vRegB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vRegs[i]));
_vRegs[i] = Marshal.ByteArrayToStructureLittleEndian<RegionTableEntry>(vRegB);
if(_vRegs[i].guid == _batGuid)
_batOffset = (long)_vRegs[i].offset;
else if(_vRegs[i].guid == _metadataGuid)
_metadataOffset = (long)_vRegs[i].offset;
else if((_vRegs[i].flags & REGION_FLAGS_REQUIRED) == REGION_FLAGS_REQUIRED)
{
AaruLogging.Error(string.Format(Localization
.Found_unsupported_and_required_region_Guid_0_not_proceeding_with_image,
_vRegs[i].guid));
return ErrorNumber.InvalidArgument;
}
}
if(_batOffset == 0)
{
AaruLogging.Error(Localization.BAT_not_found_cannot_continue);
return ErrorNumber.InvalidArgument;
}
if(_metadataOffset == 0)
{
AaruLogging.Error(Localization.Metadata_not_found_cannot_continue);
return ErrorNumber.InvalidArgument;
}
uint fileParamsOff = 0, vdSizeOff = 0, p83Off = 0, logOff = 0, physOff = 0, parentOff = 0;
stream.Seek(_metadataOffset, SeekOrigin.Begin);
var metTableB = new byte[Marshal.SizeOf<MetadataTableHeader>()];
stream.EnsureRead(metTableB, 0, Marshal.SizeOf<MetadataTableHeader>());
_vMetHdr = Marshal.ByteArrayToStructureLittleEndian<MetadataTableHeader>(metTableB);
_vMets = new MetadataTableEntry[_vMetHdr.entries];
for(var i = 0; i < _vMets.Length; i++)
{
var vMetB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vMets[i])];
stream.EnsureRead(vMetB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vMets[i]));
_vMets[i] = Marshal.ByteArrayToStructureLittleEndian<MetadataTableEntry>(vMetB);
if(_vMets[i].itemId == _fileParametersGuid)
fileParamsOff = _vMets[i].offset;
else if(_vMets[i].itemId == _virtualDiskSizeGuid)
vdSizeOff = _vMets[i].offset;
else if(_vMets[i].itemId == _page83DataGuid)
p83Off = _vMets[i].offset;
else if(_vMets[i].itemId == _logicalSectorSizeGuid)
logOff = _vMets[i].offset;
else if(_vMets[i].itemId == _physicalSectorSizeGuid)
physOff = _vMets[i].offset;
else if(_vMets[i].itemId == _parentLocatorGuid)
parentOff = _vMets[i].offset;
else if((_vMets[i].flags & METADATA_FLAGS_REQUIRED) == METADATA_FLAGS_REQUIRED)
{
AaruLogging.Error(string.Format(Localization
.Found_unsupported_and_required_metadata_Guid_0_not_proceeding_with_image,
_vMets[i].itemId));
return ErrorNumber.InvalidArgument;
}
}
byte[] tmp;
if(fileParamsOff != 0)
{
stream.Seek(fileParamsOff + _metadataOffset, SeekOrigin.Begin);
tmp = new byte[8];
stream.EnsureRead(tmp, 0, 8);
_vFileParms = new FileParameters
{
blockSize = BitConverter.ToUInt32(tmp, 0),
flags = BitConverter.ToUInt32(tmp, 4)
};
}
else
{
AaruLogging.Error(Localization.File_parameters_not_found);
return ErrorNumber.InvalidArgument;
}
if(vdSizeOff != 0)
{
stream.Seek(vdSizeOff + _metadataOffset, SeekOrigin.Begin);
tmp = new byte[8];
stream.EnsureRead(tmp, 0, 8);
_virtualDiskSize = BitConverter.ToUInt64(tmp, 0);
}
else
{
AaruLogging.Error(Localization.Virtual_disk_size_not_found);
return ErrorNumber.InvalidArgument;
}
if(p83Off != 0)
{
stream.Seek(p83Off + _metadataOffset, SeekOrigin.Begin);
tmp = new byte[16];
stream.EnsureRead(tmp, 0, 16);
_page83Data = new Guid(tmp);
}
if(logOff != 0)
{
stream.Seek(logOff + _metadataOffset, SeekOrigin.Begin);
tmp = new byte[4];
stream.EnsureRead(tmp, 0, 4);
_logicalSectorSize = BitConverter.ToUInt32(tmp, 0);
}
else
{
AaruLogging.Error(Localization.Logical_sector_size_not_found);
return ErrorNumber.InvalidArgument;
}
if(physOff != 0)
{
stream.Seek(physOff + _metadataOffset, SeekOrigin.Begin);
tmp = new byte[4];
stream.EnsureRead(tmp, 0, 4);
_physicalSectorSize = BitConverter.ToUInt32(tmp, 0);
}
else
{
AaruLogging.Error(Localization.Physical_sector_size_not_found);
return ErrorNumber.InvalidArgument;
}
if(parentOff != 0 && (_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT)
{
stream.Seek(parentOff + _metadataOffset, SeekOrigin.Begin);
var vParHdrB = new byte[Marshal.SizeOf<ParentLocatorHeader>()];
stream.EnsureRead(vParHdrB, 0, Marshal.SizeOf<ParentLocatorHeader>());
_vParHdr = Marshal.ByteArrayToStructureLittleEndian<ParentLocatorHeader>(vParHdrB);
if(_vParHdr.locatorType != _parentTypeVhdxGuid)
{
AaruLogging.Error(string.Format(Localization
.Found_unsupported_and_required_parent_locator_type_0_not_proceeding_with_image,
_vParHdr.locatorType));
return ErrorNumber.NotSupported;
}
_vPars = new ParentLocatorEntry[_vParHdr.keyValueCount];
for(var i = 0; i < _vPars.Length; i++)
{
var vParB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vPars[i])];
stream.EnsureRead(vParB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vPars[i]));
_vPars[i] = Marshal.ByteArrayToStructureLittleEndian<ParentLocatorEntry>(vParB);
}
}
else if((_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT)
{
AaruLogging.Error(Localization.Parent_locator_not_found);
return ErrorNumber.NoSuchFile;
}
if((_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT &&
_vParHdr.locatorType == _parentTypeVhdxGuid)
{
_parentImage = new Vhdx();
var parentWorks = false;
foreach(ParentLocatorEntry parentEntry in _vPars)
{
stream.Seek(parentEntry.keyOffset + _metadataOffset, SeekOrigin.Begin);
var tmpKey = new byte[parentEntry.keyLength];
stream.EnsureRead(tmpKey, 0, tmpKey.Length);
string entryType = Encoding.Unicode.GetString(tmpKey);
IFilter parentFilter;
if(string.Equals(entryType, RELATIVE_PATH_KEY, StringComparison.OrdinalIgnoreCase))
{
stream.Seek(parentEntry.valueOffset + _metadataOffset, SeekOrigin.Begin);
var tmpVal = new byte[parentEntry.valueLength];
stream.EnsureRead(tmpVal, 0, tmpVal.Length);
string entryValue = Encoding.Unicode.GetString(tmpVal);
try
{
parentFilter =
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, entryValue));
if(parentFilter != null && _parentImage.Open(parentFilter) == ErrorNumber.NoError)
{
parentWorks = true;
break;
}
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
parentWorks = false;
}
string relEntry = Path.Combine(Path.GetDirectoryName(imageFilter.Path), entryValue);
try
{
parentFilter =
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, relEntry));
if(parentFilter == null || _parentImage.Open(parentFilter) != ErrorNumber.NoError) continue;
parentWorks = true;
break;
}
catch(Exception ex)
{
// ignored
SentrySdk.CaptureException(ex);
}
}
else if(string.Equals(entryType, VOLUME_PATH_KEY, StringComparison.OrdinalIgnoreCase) ||
string.Equals(entryType, ABSOLUTE_WIN32_PATH_KEY, StringComparison.OrdinalIgnoreCase))
{
stream.Seek(parentEntry.valueOffset + _metadataOffset, SeekOrigin.Begin);
var tmpVal = new byte[parentEntry.valueLength];
stream.EnsureRead(tmpVal, 0, tmpVal.Length);
string entryValue = Encoding.Unicode.GetString(tmpVal);
try
{
parentFilter =
PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, entryValue));
if(parentFilter == null || _parentImage.Open(parentFilter) != ErrorNumber.NoError) continue;
parentWorks = true;
break;
}
catch(Exception ex)
{
// ignored
SentrySdk.CaptureException(ex);
}
}
}
if(!parentWorks)
{
AaruLogging.Error(Localization.Image_is_differential_but_parent_cannot_be_opened);
return ErrorNumber.InOutError;
}
_hasParent = true;
}
_chunkRatio = (long)(Math.Pow(2, 23) * _logicalSectorSize / _vFileParms.blockSize);
_dataBlocks = _virtualDiskSize / _vFileParms.blockSize;
if(_virtualDiskSize % _vFileParms.blockSize > 0) _dataBlocks++;
long batEntries;
if(_hasParent)
{
long sectorBitmapBlocks = (long)_dataBlocks / _chunkRatio;
if(_dataBlocks % (ulong)_chunkRatio > 0) sectorBitmapBlocks++;
_sectorBitmapPointers = new ulong[sectorBitmapBlocks];
batEntries = sectorBitmapBlocks * (_chunkRatio - 1);
}
else
batEntries = (long)(_dataBlocks + (_dataBlocks - 1) / (ulong)_chunkRatio);
AaruLogging.Debug(MODULE_NAME, Localization.Reading_BAT);
long readChunks = 0;
_blockAllocationTable = new ulong[_dataBlocks];
var batB = new byte[batEntries * 8];
stream.Seek(_batOffset, SeekOrigin.Begin);
stream.EnsureRead(batB, 0, batB.Length);
ulong skipSize = 0;
for(ulong i = 0; i < _dataBlocks; i++)
{
if(readChunks == _chunkRatio)
{
if(_hasParent)
_sectorBitmapPointers[skipSize / 8] = BitConverter.ToUInt64(batB, (int)(i * 8 + skipSize));
readChunks = 0;
skipSize += 8;
}
else
{
_blockAllocationTable[i] = BitConverter.ToUInt64(batB, (int)(i * 8 + skipSize));
readChunks++;
}
}
if(_hasParent)
{
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Sector_Bitmap);
var sectorBmpMs = new MemoryStream();
foreach(ulong pt in _sectorBitmapPointers)
{
switch(pt & BAT_FLAGS_MASK)
{
case SECTOR_BITMAP_NOT_PRESENT:
sectorBmpMs.Write(new byte[1048576], 0, 1048576);
break;
case SECTOR_BITMAP_PRESENT:
stream.Seek((long)((pt & BAT_FILE_OFFSET_MASK) * 1048576), SeekOrigin.Begin);
var bmp = new byte[1048576];
stream.EnsureRead(bmp, 0, bmp.Length);
sectorBmpMs.Write(bmp, 0, bmp.Length);
break;
default:
if((pt & BAT_FLAGS_MASK) != 0)
{
AaruLogging.Error(string.Format(Localization
.Unsupported_sector_bitmap_block_flags_0_found_not_proceeding,
pt & BAT_FLAGS_MASK));
return ErrorNumber.InvalidArgument;
}
break;
}
}
_sectorBitmap = sectorBmpMs.ToArray();
sectorBmpMs.Close();
}
_maxBlockCache = (int)(MAX_CACHE_SIZE / _vFileParms.blockSize);
_maxSectorCache = (int)(MAX_CACHE_SIZE / _logicalSectorSize);
_imageStream = stream;
_sectorCache = new Dictionary<ulong, byte[]>();
_blockCache = new Dictionary<ulong, byte[]>();
_imageInfo.CreationTime = imageFilter.CreationTime;
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
_imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename);
_imageInfo.SectorSize = _logicalSectorSize;
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
_imageInfo.MediaType = MediaType.GENERIC_HDD;
_imageInfo.ImageSize = _virtualDiskSize;
_imageInfo.Sectors = _imageInfo.ImageSize / _imageInfo.SectorSize;
_imageInfo.DriveSerialNumber = _page83Data.ToString();
// TODO: Separate image application from version, need several samples.
_imageInfo.Cylinders = (uint)(_imageInfo.Sectors / 16 / 63);
_imageInfo.Heads = 16;
_imageInfo.SectorsPerTrack = 63;
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{
buffer = null;
sectorStatus = SectorStatus.NotDumped;
if(negative) return ErrorNumber.NotSupported;
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
if(_sectorCache.TryGetValue(sectorAddress, out buffer)) return ErrorNumber.NoError;
sectorStatus = SectorStatus.Dumped;
ulong index = sectorAddress * _logicalSectorSize / _vFileParms.blockSize;
ulong secOff = sectorAddress * _logicalSectorSize % _vFileParms.blockSize;
ulong blkPtr = _blockAllocationTable[index];
ulong blkFlags = blkPtr & BAT_FLAGS_MASK;
if((blkPtr & BAT_RESERVED_MASK) != 0)
{
AaruLogging.Error($"Unknown flags (0x{blkPtr & BAT_RESERVED_MASK:X16}) set in block pointer");
return ErrorNumber.InvalidArgument;
}
switch(blkFlags & BAT_FLAGS_MASK)
{
case PAYLOAD_BLOCK_NOT_PRESENT:
if(_hasParent) return _parentImage.ReadSector(sectorAddress, false, out buffer, out sectorStatus);
buffer = new byte[_logicalSectorSize];
return ErrorNumber.NoError;
case PAYLOAD_BLOCK_UNDEFINED:
case PAYLOAD_BLOCK_ZERO:
case PAYLOAD_BLOCK_UNMAPPER:
buffer = new byte[_logicalSectorSize];
return ErrorNumber.NoError;
}
bool partialBlock = (blkFlags & BAT_FLAGS_MASK) == PAYLOAD_BLOCK_PARTIALLY_PRESENT;
if(partialBlock && _hasParent && !CheckBitmap(sectorAddress))
return _parentImage.ReadSector(sectorAddress, false, out buffer, out sectorStatus);
if(!_blockCache.TryGetValue(blkPtr & BAT_FILE_OFFSET_MASK, out byte[] block))
{
block = new byte[_vFileParms.blockSize];
_imageStream.Seek((long)(blkPtr & BAT_FILE_OFFSET_MASK), SeekOrigin.Begin);
_imageStream.EnsureRead(block, 0, block.Length);
if(_blockCache.Count >= _maxBlockCache) _blockCache.Clear();
_blockCache.Add(blkPtr & BAT_FILE_OFFSET_MASK, block);
}
buffer = new byte[_logicalSectorSize];
Array.Copy(block, (int)secOff, buffer, 0, buffer.Length);
if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear();
_sectorCache.Add(sectorAddress, buffer);
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus)
{
buffer = null;
sectorStatus = null;
if(negative) return ErrorNumber.NotSupported;
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange;
var ms = new MemoryStream();
sectorStatus = new SectorStatus[length];
for(uint i = 0; i < length; i++)
{
ErrorNumber errno = ReadSector(sectorAddress + i, false, out byte[] sector, out SectorStatus status);
if(errno != ErrorNumber.NoError) return errno;
ms.Write(sector, 0, sector.Length);
sectorStatus[i] = status;
}
buffer = ms.ToArray();
return ErrorNumber.NoError;
}
#endregion
}