Files
easy7zip/CPP/7zip/Archive/VhdxHandler.cpp
Igor Pavlov c3529a41f5 21.07
2022-01-22 18:43:09 +00:00

2063 lines
48 KiB
C++

// VhdxHandler.cpp
#include "StdAfx.h"
// #include <stdio.h>
#include "../../../C/CpuArch.h"
#include "../../Common/ComTry.h"
#include "../../Common/MyBuffer.h"
#include "../../Windows/PropVariant.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"
#include "HandlerCont.h"
#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
#define Get64(p) GetUi64(p)
#define G32(_offs_, dest) dest = Get32(p + (_offs_));
#define G64(_offs_, dest) dest = Get64(p + (_offs_));
using namespace NWindows;
EXTERN_C_BEGIN
// CRC-32C (Castagnoli) : reversed for poly 0x1EDC6F41
#define k_Crc32c_Poly 0x82f63b78
static UInt32 g_Crc32c_Table[256];
static void MY_FAST_CALL Crc32c_GenerateTable()
{
UInt32 i;
for (i = 0; i < 256; i++)
{
UInt32 r = i;
unsigned j;
for (j = 0; j < 8; j++)
r = (r >> 1) ^ (k_Crc32c_Poly & ((UInt32)0 - (r & 1)));
g_Crc32c_Table[i] = r;
}
}
UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);
#define CRC32C_INIT_VAL 0xFFFFFFFF
static UInt32 MY_FAST_CALL Crc32c_Calc(const void *data, size_t size)
{
return CrcUpdateT1(CRC32C_INIT_VAL, data, size, g_Crc32c_Table) ^ CRC32C_INIT_VAL;
}
EXTERN_C_END
namespace NArchive {
namespace NVhdx {
static struct C_CRC32c_TableInit { C_CRC32c_TableInit() { Crc32c_GenerateTable(); } } g__CRC32c_TableInit;
#define SIGNATURE { 'v', 'h', 'd', 'x', 'f', 'i', 'l', 'e' }
static const unsigned kSignatureSize = 8;
static const Byte kSignature[kSignatureSize] = SIGNATURE;
static const unsigned kBitmapSize_Log = 20;
static const size_t kBitmapSize = (size_t)1 << kBitmapSize_Log;
static bool IsZeroArr(const Byte *p, size_t size)
{
for (size_t i = 0; i < size; i++)
if (p[i] != 0)
return false;
return true;
}
#define ValToHex(t) ((char)(((t) < 10) ? ('0' + (t)) : ('a' + ((t) - 10))))
static void AddByteToHex2(unsigned val, UString &s)
{
unsigned t;
t = val >> 4;
s += ValToHex(t);
t = val & 0xF;
s += ValToHex(t);
}
static int HexToVal(const wchar_t c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'z') return c - 'a' + 10;
if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
return -1;
}
static int DecodeFrom2HexChars(const wchar_t *s)
{
const int v0 = HexToVal(s[0]); if (v0 < 0) return -1;
const int v1 = HexToVal(s[1]); if (v1 < 0) return -1;
return ((unsigned)v0 << 4) | (unsigned)v1;
}
struct CGuid
{
Byte Data[16];
bool IsZero() const { return IsZeroArr(Data, 16); }
bool IsEqualTo(const Byte *a) const { return memcmp(Data, a, 16) == 0; }
bool IsEqualTo(const CGuid &g) const { return IsEqualTo(g.Data); }
void AddHexToString(UString &s) const;
void SetFrom(const Byte *p) { memcpy(Data, p, 16); }
bool ParseFromFormatedHexString(const UString &s)
{
const unsigned kLen = 16 * 2 + 4 + 2;
if (s.Len() != kLen || s[0] != '{' || s[kLen - 1] != '}')
return false;
unsigned pos = 0;
for (unsigned i = 1; i < kLen - 1;)
{
if (i == 9 || i == 14 || i == 19 || i == 24)
{
if (s[i] != '-')
return false;
i++;
continue;
}
const int v = DecodeFrom2HexChars(s.Ptr(i));
if (v < 0)
return false;
unsigned pos2 = pos;
if (pos < 8)
pos2 ^= (pos < 4 ? 3 : 1);
Data[pos2] = (Byte)v;
pos++;
i += 2;
}
return true; // pos == 16;
}
};
void CGuid::AddHexToString(UString &s) const
{
for (unsigned i = 0; i < 16; i++)
AddByteToHex2(Data[i], s);
}
#define IS_NON_ALIGNED(v) (((v) & 0xFFFFF) != 0)
static const unsigned kHeader_GUID_Index_FileWriteGuid = 0;
static const unsigned kHeader_GUID_Index_DataWriteGuid = 1;
static const unsigned kHeader_GUID_Index_LogGuid = 2;
struct CHeader
{
UInt64 SequenceNumber;
// UInt16 LogVersion;
// UInt16 Version;
UInt32 LogLength;
UInt64 LogOffset;
CGuid Guids[3];
bool Parse(Byte *p);
};
static const unsigned kHeader2Size = 1 << 12;
bool CHeader::Parse(Byte *p)
{
if (Get32(p) != 0x64616568) // "head"
return false;
const UInt32 crc = Get32(p + 4);
SetUi32(p + 4, 0);
if (Crc32c_Calc(p, kHeader2Size) != crc)
return false;
G64(8, SequenceNumber);
for (unsigned i = 0; i < 3; i++)
Guids[i].SetFrom(p + 0x10 + 0x10 * i);
// LogVersion = Get16(p + 0x40);
/* LogVersion MUST be set to zero, for known log format
but we don't parse log so we ignore it */
G32(0x44, LogLength);
G64(0x48, LogOffset);
if (Get16(p + 0x42) != 1) // Header format Version
return false;
if (IS_NON_ALIGNED(LogLength))
return false;
if (IS_NON_ALIGNED(LogOffset))
return false;
return true;
// return IsZeroArr(p + 0x50, kHeader2Size - 0x50);
}
static const Byte kBat[16] =
{ 0x66,0x77,0xC2,0x2D,0x23,0xF6,0x00,0x42,0x9D,0x64,0x11,0x5E,0x9B,0xFD,0x4A,0x08 };
static const Byte kMetadataRegion[16] =
{ 0x06,0xA2,0x7C,0x8B,0x90,0x47,0x9A,0x4B,0xB8,0xFE,0x57,0x5F,0x05,0x0F,0x88,0x6E };
struct CRegionEntry
{
// CGuid Guid;
UInt64 Offset;
UInt32 Len;
UInt32 Required;
UInt64 GetEndPos() const { return Offset + Len; }
bool Parse(const Byte *p);
};
bool CRegionEntry::Parse(const Byte *p)
{
// Guid.SetFrom(p);
G64(0x10, Offset);
G32(0x18, Len);
G32(0x1c, Required);
if (IS_NON_ALIGNED(Offset))
return false;
if (IS_NON_ALIGNED(Len))
return false;
if (Offset + Len < Offset)
return false;
return true;
}
struct CRegion
{
bool Bat_Defined;
bool Meta_Defined;
UInt64 EndPos;
UInt64 DataSize;
CRegionEntry BatEntry;
CRegionEntry MetaEntry;
bool Parse(Byte *p);
};
static const unsigned kRegionSize = 1 << 16;
static const unsigned kNumRegionEntriesMax = (1 << 11) - 1;
bool CRegion::Parse(Byte *p)
{
Bat_Defined = false;
Meta_Defined = false;
EndPos = 0;
DataSize = 0;
if (Get32(p) != 0x69676572) // "regi"
return false;
const UInt32 crc = Get32(p + 4);
SetUi32(p + 4, 0);
const UInt32 crc_calced = Crc32c_Calc(p, kRegionSize);
if (crc_calced != crc)
return false;
const UInt32 EntryCount = Get32(p + 8);
if (Get32(p + 12) != 0) // reserved field must be set to 0.
return false;
if (EntryCount > kNumRegionEntriesMax)
return false;
for (UInt32 i = 0; i < EntryCount; i++)
{
CRegionEntry e;
const Byte *p2 = p + 0x10 + 0x20 * (size_t)i;
if (!e.Parse(p2))
return false;
DataSize += e.Len;
const UInt64 endPos = e.GetEndPos();
if (EndPos < endPos)
EndPos = endPos;
CGuid Guid;
Guid.SetFrom(p2);
if (Guid.IsEqualTo(kBat))
{
if (Bat_Defined)
return false;
BatEntry = e;
Bat_Defined = true;
}
else if (Guid.IsEqualTo(kMetadataRegion))
{
if (Meta_Defined)
return false;
MetaEntry = e;
Meta_Defined = true;
}
else
{
if (e.Required != 0)
return false;
// it's allowed to ignore unknown non-required region entries
}
}
/*
const size_t k = 0x10 + 0x20 * EntryCount;
return IsZeroArr(p + k, kRegionSize - k);
*/
return true;
}
struct CMetaEntry
{
CGuid Guid;
UInt32 Offset;
UInt32 Len;
UInt32 Flags0;
// UInt32 Flags1;
bool IsUser() const { return (Flags0 & 1) != 0; }
bool IsVirtualDisk() const { return (Flags0 & 2) != 0; }
bool IsRequired() const { return (Flags0 & 4) != 0; }
bool CheckLimit(size_t regionSize) const
{
return Offset <= regionSize && Len <= regionSize - Offset;
}
bool Parse(const Byte *p);
};
bool CMetaEntry::Parse(const Byte *p)
{
Guid.SetFrom(p);
G32(0x10, Offset);
G32(0x14, Len);
G32(0x18, Flags0);
UInt32 Flags1;
G32(0x1C, Flags1);
if (Offset != 0 && Offset < (1 << 16))
return false;
if (Len > (1 << 20))
return false;
if (Len == 0 && Offset != 0)
return false;
if ((Flags0 >> 3) != 0) // Reserved
return false;
if ((Flags1 & 3) != 0) // Reserved2
return false;
return true;
};
struct CParentPair
{
UString Key;
UString Value;
};
struct CMetaHeader
{
// UInt16 EntryCount;
bool Guid_Defined;
bool VirtualDiskSize_Defined;
bool Locator_Defined;
unsigned BlockSize_Log;
unsigned LogicalSectorSize_Log;
unsigned PhysicalSectorSize_Log;
UInt32 Flags;
UInt64 VirtualDiskSize;
CGuid Guid;
// CGuid LocatorType;
CObjectVector<CParentPair> ParentPairs;
int FindParentKey(const char *name) const
{
FOR_VECTOR (i, ParentPairs)
{
const CParentPair &pair = ParentPairs[i];
if (pair.Key.IsEqualTo(name))
return i;
}
return -1;
}
bool Is_LeaveBlockAllocated() const { return (Flags & 1) != 0; }
bool Is_HasParent() const { return (Flags & 2) != 0; }
void Clear()
{
Guid_Defined = false;
VirtualDiskSize_Defined = false;
Locator_Defined = false;
BlockSize_Log = 0;
LogicalSectorSize_Log = 0;
PhysicalSectorSize_Log = 0;
Flags = 0;
VirtualDiskSize = 0;
ParentPairs.Clear();
}
bool Parse(const Byte *p, size_t size);
};
static unsigned GetLogSize(UInt32 size)
{
unsigned k;
for (k = 0; k < 32; k++)
if (((UInt32)1 << k) == size)
return k;
return k;
}
static const unsigned kMetadataSize = 8;
static const Byte kMetadata[kMetadataSize] =
{ 'm','e','t','a','d','a','t','a' };
static const unsigned k_Num_MetaEntries_Max = (1 << 11) - 1;
static const Byte kFileParameters[16] =
{ 0x37,0x67,0xa1,0xca,0x36,0xfa,0x43,0x4d,0xb3,0xb6,0x33,0xf0,0xaa,0x44,0xe7,0x6b };
static const Byte kVirtualDiskSize[16] =
{ 0x24,0x42,0xa5,0x2f,0x1b,0xcd,0x76,0x48,0xb2,0x11,0x5d,0xbe,0xd8,0x3b,0xf4,0xb8 };
static const Byte kVirtualDiskID[16] =
{ 0xab,0x12,0xca,0xbe,0xe6,0xb2,0x23,0x45,0x93,0xef,0xc3,0x09,0xe0,0x00,0xc7,0x46 };
static const Byte kLogicalSectorSize[16] =
{ 0x1d,0xbf,0x41,0x81,0x6f,0xa9,0x09,0x47,0xba,0x47,0xf2,0x33,0xa8,0xfa,0xab,0x5f };
static const Byte kPhysicalSectorSize[16] =
{ 0xc7,0x48,0xa3,0xcd,0x5d,0x44,0x71,0x44,0x9c,0xc9,0xe9,0x88,0x52,0x51,0xc5,0x56 };
static const Byte kParentLocator[16] =
{ 0x2d,0x5f,0xd3,0xa8,0x0b,0xb3,0x4d,0x45,0xab,0xf7,0xd3,0xd8,0x48,0x34,0xab,0x0c };
static bool GetString16(UString &s, const Byte *p, size_t size)
{
s.Empty();
if (size & 1)
return false;
for (size_t i = 0; i < size; i += 2)
{
const wchar_t c = Get16(p + i);
if (c == 0)
return false;
s += c;
}
return true;
}
bool CMetaHeader::Parse(const Byte *p, size_t size)
{
if (memcmp(p, kMetadata, kMetadataSize) != 0)
return false;
if (Get16(p + 8) != 0) // Reserved
return false;
const UInt32 EntryCount = Get16(p + 10);
if (EntryCount > k_Num_MetaEntries_Max)
return false;
if (!IsZeroArr(p + 12, 20)) // Reserved
return false;
for (unsigned i = 0; i < EntryCount; i++)
{
CMetaEntry e;
if (!e.Parse(p + 32 + 32 * (size_t)i))
return false;
if (!e.CheckLimit(size))
return false;
const Byte *p2 = p + e.Offset;
if (e.Guid.IsEqualTo(kFileParameters))
{
if (BlockSize_Log != 0)
return false;
if (e.Len != 8)
return false;
const UInt32 v = Get32(p2);
Flags = Get32(p2 + 4);
BlockSize_Log = GetLogSize(v);
if (BlockSize_Log < 20 || BlockSize_Log > 28) // specification from 1 MB to 256 MB
return false;
if ((Flags >> 2) != 0) // reserved
return false;
}
else if (e.Guid.IsEqualTo(kVirtualDiskSize))
{
if (VirtualDiskSize_Defined)
return false;
if (e.Len != 8)
return false;
VirtualDiskSize = Get64(p2);
VirtualDiskSize_Defined = true;
}
else if (e.Guid.IsEqualTo(kVirtualDiskID))
{
if (e.Len != 16)
return false;
Guid.SetFrom(p2);
Guid_Defined = true;
}
else if (e.Guid.IsEqualTo(kLogicalSectorSize))
{
if (LogicalSectorSize_Log != 0)
return false;
if (e.Len != 4)
return false;
const UInt32 v = Get32(p2);
LogicalSectorSize_Log = GetLogSize(v);
if (LogicalSectorSize_Log != 9 && LogicalSectorSize_Log != 12)
return false;
}
else if (e.Guid.IsEqualTo(kPhysicalSectorSize))
{
if (PhysicalSectorSize_Log != 0)
return false;
if (e.Len != 4)
return false;
const UInt32 v = Get32(p2);
PhysicalSectorSize_Log = GetLogSize(v);
if (PhysicalSectorSize_Log != 9 && PhysicalSectorSize_Log != 12)
return false;
}
else if (e.Guid.IsEqualTo(kParentLocator))
{
if (Locator_Defined)
return false;
if (e.Len < 20)
return false;
// LocatorType.SetFrom(p2);
/* Specifies the type of the parent virtual disk.
is different for each type: VHDX, VHD or iSCSI.
only "B04AEFB7-D19E-4A81-B789-25B8E9445913" (for VHDX) is supported now
*/
Locator_Defined = true;
if (Get16(p2 + 16) != 0) // reserved
return false;
const UInt32 KeyValueCount = Get16(p2 + 18);
if (20 + (UInt32)KeyValueCount * 12 > e.Len)
return false;
for (unsigned k = 0; k < KeyValueCount; k++)
{
const Byte *p3 = p2 + 20 + (size_t)k * 12;
const UInt32 KeyOffset = Get32(p3);
const UInt32 ValueOffset = Get32(p3 + 4);
const UInt32 KeyLength = Get16(p3 + 8);
const UInt32 ValueLength = Get16(p3 + 10);
if (KeyOffset > e.Len || KeyLength > e.Len - KeyOffset)
return false;
if (ValueOffset > e.Len || ValueLength > e.Len - ValueOffset)
return false;
CParentPair pair;
if (!GetString16(pair.Key, p2 + KeyOffset, KeyLength))
return false;
if (!GetString16(pair.Value, p2 + ValueOffset, ValueLength))
return false;
ParentPairs.Add(pair);
}
}
else
{
if (e.IsRequired())
return false;
// return false; // unknown metadata;
}
}
// some properties are required for correct processing
if (BlockSize_Log == 0)
return false;
if (LogicalSectorSize_Log == 0)
return false;
if (!VirtualDiskSize_Defined)
return false;
if (((UInt32)VirtualDiskSize & ((UInt32)1 << LogicalSectorSize_Log)) != 0)
return false;
// vhdx specification sets limit for 64 TB.
// do we need to check over same limit ?
const UInt64 kVirtualDiskSize_Max = (UInt64)1 << 46;
if (VirtualDiskSize > kVirtualDiskSize_Max)
return false;
return true;
}
struct CBat
{
CByteBuffer Data;
void Clear() { Data.Free(); }
UInt64 GetItem(size_t n) const
{
return Get64(Data + n * 8);
}
};
class CHandler: public CHandlerImg
{
UInt64 _phySize;
CBat Bat;
CObjectVector<CByteBuffer> BitMaps;
unsigned ChunkRatio_Log;
size_t ChunkRatio;
size_t TotalBatEntries;
CMetaHeader Meta;
CHeader Header;
UInt32 NumUsedBlocks;
UInt32 NumUsedBitMaps;
UInt64 HeadersSize;
UInt32 NumLevels;
UInt64 PackSize_Total;
/*
UInt64 NumUsed_1MB_Blocks; // data and bitmaps
bool NumUsed_1MB_Blocks_Defined;
*/
CMyComPtr<IInStream> ParentStream;
CHandler *Parent;
UString _errorMessage;
UString _Creator;
bool _nonEmptyLog;
bool _isDataContiguous;
// bool _BatOverlap;
CGuid _parentGuid;
bool _parentGuid_IsDefined;
UStringVector ParentNames;
UString ParentName_Used;
const CHandler *_child;
unsigned _level;
bool _isCyclic;
bool _isCyclic_or_CyclicParent;
void AddErrorMessage(const char *message);
void AddErrorMessage(const char *message, const wchar_t *name);
void UpdatePhySize(UInt64 value)
{
if (_phySize < value)
_phySize = value;
}
HRESULT Seek2(UInt64 offset);
HRESULT Read_FALSE(Byte *data, size_t size)
{
return ReadStream_FALSE(Stream, data, size);
}
HRESULT ReadToBuf_FALSE(CByteBuffer &buf, size_t size)
{
buf.Alloc(size);
return ReadStream_FALSE(Stream, buf, size);
}
void InitSeekPositions();
HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);
bool IsDiff() const
{
// here we suppose that only HasParent() flag is mandatory for Diff archive type
return Meta.Is_HasParent();
// return _parentGuid_IsDefined;
}
void AddTypeString(AString &s) const
{
if (IsDiff())
s += "Differencing";
else
{
if (Meta.Is_LeaveBlockAllocated())
s += _isDataContiguous ? "fixed" : "fixed-non-cont";
else
s += "dynamic";
}
}
void AddComment(UString &s) const;
UInt64 GetPackSize() const
{
return (UInt64)NumUsedBlocks << Meta.BlockSize_Log;
}
UString GetParentSequence() const
{
const CHandler *p = this;
UString res;
while (p && p->IsDiff())
{
if (!res.IsEmpty())
res += " -> ";
res += ParentName_Used;
p = p->Parent;
}
return res;
}
bool AreParentsOK() const
{
if (_isCyclic_or_CyclicParent)
return false;
const CHandler *p = this;
while (p->IsDiff())
{
p = p->Parent;
if (!p)
return false;
}
return true;
}
// bool ParseLog(CByteBuffer &log);
bool ParseBat();
bool CheckBat();
HRESULT Open3();
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback);
HRESULT OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen);
virtual void CloseAtError();
public:
INTERFACE_IInArchive_Img(;)
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
CHandler():
_child(NULL),
_level(0),
_isCyclic(false),
_isCyclic_or_CyclicParent(false)
{}
};
HRESULT CHandler::Seek2(UInt64 offset)
{
return Stream->Seek(offset, STREAM_SEEK_SET, NULL);
}
void CHandler::InitSeekPositions()
{
/* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()).
So we must reset these variables before first call of Read() */
Reset_VirtPos();
Reset_PosInArc();
if (ParentStream)
Parent->InitSeekPositions();
}
HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed)
{
processed = 0;
if (offset > _phySize
|| offset + size > _phySize)
{
// we don't expect these cases, if (_phySize) was set correctly.
return S_FALSE;
}
if (offset != _posInArc)
{
const HRESULT res = Seek2(offset);
if (res != S_OK)
{
Reset_PosInArc(); // we don't trust seek_pos in case of error
return res;
}
_posInArc = offset;
}
{
size_t size2 = size;
const HRESULT res = ReadStream(Stream, data, &size2);
processed = (UInt32)size2;
_posInArc += size2;
if (res != S_OK)
Reset_PosInArc(); // we don't trust seek_pos in case of reading error
return res;
}
}
#define PAYLOAD_BLOCK_NOT_PRESENT 0
#define PAYLOAD_BLOCK_UNDEFINED 1
#define PAYLOAD_BLOCK_ZERO 2
#define PAYLOAD_BLOCK_UNMAPPED 3
#define PAYLOAD_BLOCK_FULLY_PRESENT 6
#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7
#define SB_BLOCK_NOT_PRESENT 0
#define SB_BLOCK_PRESENT 6
#define BAT_GET_OFFSET(v) ((v) & ~(UInt64)0xFFFFF);
#define BAT_GET_STATE(v) ((UInt32)(v) & 7);
/* The log contains only updates to metadata, bat and region tables
The log doesn't contain updates to start header, and 2 headers (first 192 KB of file).
The log is array of 4 KB blocks and each block has 4-byte signature.
So it's possible to scan whole log to find the latest entry sequence (and header for replay).
*/
/*
struct CLogEntry
{
UInt32 EntryLength;
UInt32 Tail;
UInt64 SequenceNumber;
CGuid LogGuid;
UInt32 DescriptorCount;
UInt64 FlushedFileOffset;
UInt64 LastFileOffset;
bool Parse(const Byte *p);
};
bool CLogEntry::Parse(const Byte *p)
{
G32 (8, EntryLength);
G32 (12,Tail);
G64 (16, SequenceNumber);
G32 (24, DescriptorCount); // it's 32-bit, but specification says 64-bit
if (Get32(p + 28) != 0) // reserved
return false;
LogGuid.SetFrom(p + 32);
G64 (48, FlushedFileOffset);
G64 (56, LastFileOffset);
if (SequenceNumber == 0)
return false;
if ((Tail & 0xfff) != 0)
return false;
if (IS_NON_ALIGNED(FlushedFileOffset))
return false;
if (IS_NON_ALIGNED(LastFileOffset))
return false;
return true;
}
bool CHandler::ParseLog(CByteBuffer &log)
{
CLogEntry lastEntry;
lastEntry.SequenceNumber = 0;
bool lastEntry_found = false;
size_t lastEntry_Offset = 0;
for (size_t i = 0; i < log.Size(); i += 1 << 12)
{
Byte *p = (Byte *)(log + i);
if (Get32(p) != 0x65676F6C) // "loge"
continue;
const UInt32 crc = Get32(p + 4);
CLogEntry e;
if (!e.Parse(p))
{
return false;
continue;
}
const UInt32 entryLength = Get32(p + 8);
if (e.EntryLength > log.Size() || (e.EntryLength & 0xFFF) != 0 || e.EntryLength == 0)
{
return false;
continue;
}
SetUi32(p + 4, 0);
const UInt32 crc_calced = Crc32c_Calc(p, entryLength);
SetUi32(p + 4, crc); // we must restore crc if we want same data in log
if (crc_calced != crc)
continue;
if (!lastEntry_found || lastEntry.SequenceNumber < e.SequenceNumber)
{
lastEntry = e;
lastEntry_found = true;
lastEntry_Offset = i;
}
}
return true;
}
*/
bool CHandler::ParseBat()
{
ChunkRatio_Log = kBitmapSize_Log + 3 + Meta.LogicalSectorSize_Log - Meta.BlockSize_Log;
ChunkRatio = (size_t)1 << (ChunkRatio_Log);
UInt64 totalBatEntries64;
const bool isDiff = IsDiff();
const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
{
const UInt64 up = Meta.VirtualDiskSize + blockSize - 1;
if (up < Meta.VirtualDiskSize)
return false;
const UInt64 numDataBlocks = up >> Meta.BlockSize_Log;
if (isDiff)
{
// differencing table must be finished with bitmap entry
const UInt64 numBitmaps = (numDataBlocks + ChunkRatio - 1) >> ChunkRatio_Log;
totalBatEntries64 = numBitmaps * (ChunkRatio + 1);
}
else
{
// we don't need last Bitmap entry
totalBatEntries64 = numDataBlocks + ((numDataBlocks - 1) >> ChunkRatio_Log);
}
}
if (totalBatEntries64 > Bat.Data.Size() / 8)
return false;
const size_t totalBatEntries = (size_t)totalBatEntries64;
TotalBatEntries = totalBatEntries;
bool isCont = (!isDiff && Meta.Is_LeaveBlockAllocated());
UInt64 prevBlockOffset = 0;
UInt64 maxBlockOffset = 0;
size_t remEntries = ChunkRatio + 1;
size_t i;
for (i = 0; i < totalBatEntries; i++)
{
const UInt64 v = Bat.GetItem(i);
if ((v & 0xFFFF8) != 0)
return false;
const UInt64 offset = BAT_GET_OFFSET(v);
const unsigned state = BAT_GET_STATE(v);
/*
UInt64 index64 = v >> 20;
printf("\n%7d", i);
printf("%10d, ", (unsigned)index64);
printf("%4x, ", (unsigned)state);
*/
remEntries--;
if (remEntries == 0)
{
// printf(" ========");
// printf("\n");
remEntries = ChunkRatio + 1;
if (state == SB_BLOCK_PRESENT)
{
isCont = false;
if (!isDiff)
return false;
if (offset == 0)
return false;
const UInt64 lim = offset + kBitmapSize;
if (lim < offset)
return false;
if (_phySize < lim)
_phySize = lim;
NumUsedBitMaps++;
}
else if (state != SB_BLOCK_NOT_PRESENT)
return false;
}
else
{
if (state == PAYLOAD_BLOCK_FULLY_PRESENT
|| state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
{
if (offset == 0)
return false;
if (maxBlockOffset < offset)
maxBlockOffset = offset;
if (state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
{
isCont = false;
if (!isDiff)
return false;
}
else if (isCont)
{
if (prevBlockOffset != 0 && prevBlockOffset + blockSize != offset)
isCont = false;
else
prevBlockOffset = offset;
}
NumUsedBlocks++;
}
else if (state == PAYLOAD_BLOCK_UNMAPPED)
{
isCont = false;
// non-empty (offset) is allowed
}
else if (state == PAYLOAD_BLOCK_NOT_PRESENT
|| state == PAYLOAD_BLOCK_UNDEFINED
|| state == PAYLOAD_BLOCK_ZERO)
{
isCont = false;
/* (offset) is reserved and (offset == 0) is expected here,
but we ignore (offset) here */
// if (offset != 0) return false;
}
else
return false;
}
}
_isDataContiguous = isCont;
if (maxBlockOffset != 0)
{
const UInt64 lim = maxBlockOffset + blockSize;
if (lim < maxBlockOffset)
return false;
if (_phySize < lim)
_phySize = lim;
const UInt64 kPhyLimit = (UInt64)1 << 62;
if (maxBlockOffset >= kPhyLimit)
return false;
}
return true;
}
bool CHandler::CheckBat()
{
const UInt64 upSize = _phySize + kBitmapSize * 8 - 1;
if (upSize < _phySize)
return false;
const UInt64 useMapSize64 = upSize >> (kBitmapSize_Log + 3);
const size_t useMapSize = (size_t)useMapSize64;
const UInt32 blockSizeMB = (UInt32)1 << (Meta.BlockSize_Log - kBitmapSize_Log);
// we don't check useMap, if it's too big.
if (useMapSize != useMapSize64)
return true;
if (useMapSize == 0 || useMapSize > ((size_t)1 << 28))
return true;
CByteArr useMap;
useMap.Alloc(useMapSize);
memset(useMap, 0, useMapSize);
// useMap[0] = (Byte)(1 << 0); // first 1 MB is used by headers
// we can also update useMap for log, and region data.
const size_t totalBatEntries = TotalBatEntries;
size_t remEntries = ChunkRatio + 1;
size_t i;
for (i = 0; i < totalBatEntries; i++)
{
const UInt64 v = Bat.GetItem(i);
const UInt64 offset = BAT_GET_OFFSET(v);
const unsigned state = BAT_GET_STATE(v);
const UInt64 index = offset >> kBitmapSize_Log;
UInt32 numBlocks = 1;
remEntries--;
if (remEntries == 0)
{
remEntries = ChunkRatio + 1;
if (state != SB_BLOCK_PRESENT)
continue;
}
else
{
if (state != PAYLOAD_BLOCK_FULLY_PRESENT &&
state != PAYLOAD_BLOCK_PARTIALLY_PRESENT)
continue;
numBlocks = blockSizeMB;
}
for (unsigned k = 0; k < numBlocks; k++)
{
const UInt64 index2 = index + k;
const unsigned flag = (unsigned)1 << ((unsigned)index2 & 7);
const size_t byteIndex = (size_t)(index2 >> 3);
if (byteIndex >= useMapSize)
return false;
const unsigned m = useMap[byteIndex];
if (m & flag)
return false;
useMap[byteIndex] = (Byte)(m | flag);
}
}
/*
UInt64 num = 0;
for (i = 0; i < useMapSize; i++)
{
Byte b = useMap[i];
unsigned t = 0;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1); b >>= 1;
t += (b & 1);
num += t;
}
NumUsed_1MB_Blocks = num;
NumUsed_1MB_Blocks_Defined = true;
*/
return true;
}
HRESULT CHandler::Open3()
{
{
static const unsigned kHeaderSize = 512; // + 8
Byte header[kHeaderSize];
RINOK(Read_FALSE(header, kHeaderSize));
if (memcmp(header, kSignature, kSignatureSize) != 0)
return S_FALSE;
const Byte *p = &header[0];
for (unsigned i = kSignatureSize; i < kHeaderSize; i += 2)
{
const wchar_t c = Get16(p + i);
if (c < 0x20 || c > 0x7F)
break;
_Creator += c;
}
}
HeadersSize = (UInt32)1 << 20;
CHeader headers[2];
{
Byte header[kHeader2Size];
for (unsigned i = 0; i < 2; i++)
{
RINOK(Seek2((1 << 16) * (1 + i)));
RINOK(Read_FALSE(header, kHeader2Size));
bool headerIsOK = headers[i].Parse(header);
if (!headerIsOK)
return S_FALSE;
}
}
unsigned mainIndex;
if (headers[0].SequenceNumber > headers[1].SequenceNumber) mainIndex = 0;
else if (headers[0].SequenceNumber < headers[1].SequenceNumber) mainIndex = 1;
else return S_FALSE;
const CHeader &h = headers[mainIndex];
Header = h;
if (h.LogLength != 0)
{
HeadersSize += h.LogLength;
UpdatePhySize(h.LogOffset + h.LogLength);
if (!h.Guids[kHeader_GUID_Index_LogGuid].IsZero())
{
_nonEmptyLog = true;
AddErrorMessage("non-empty LOG was not replayed");
/*
if (h.LogVersion != 0)
AddErrorMessage("unknown LogVresion");
else
{
CByteBuffer log;
RINOK(Seek2(h.LogOffset));
RINOK(ReadToBuf_FALSE(log, h.LogLength));
if (!ParseLog(log))
{
return S_FALSE;
}
}
*/
}
}
CRegion regions[2];
int correctRegionIndex = -1;
{
CByteBuffer temp;
temp.Alloc(kRegionSize * 2);
RINOK(Seek2((1 << 16) * 3));
RINOK(Read_FALSE(temp, kRegionSize * 2));
unsigned numTables = 1;
if (memcmp(temp, temp + kRegionSize, kRegionSize) != 0)
{
AddErrorMessage("Region tables mismatch");
numTables = 2;
}
for (unsigned i = 0; i < numTables; i++)
{
// RINOK(Seek2((1 << 16) * (3 + i)));
// RINOK(Read_FALSE(temp, kRegionSize));
if (regions[i].Parse(temp))
{
if (correctRegionIndex < 0)
correctRegionIndex = i;
}
else
{
AddErrorMessage("Incorrect region table");
}
}
if (correctRegionIndex < 0)
return S_FALSE;
/*
if (!regions[0].IsEqualTo(regions[1]))
return S_FALSE;
*/
}
// UpdatePhySize((1 << 16) * 5);
UpdatePhySize(1 << 20);
{
const CRegion &region = regions[correctRegionIndex];
HeadersSize += region.DataSize;
UpdatePhySize(region.EndPos);
{
if (!region.Meta_Defined)
return S_FALSE;
const CRegionEntry &e = region.MetaEntry;
if (e.Len == 0)
return S_FALSE;
{
// static const kMetaTableSize = 1 << 16;
CByteBuffer temp;
{
RINOK(Seek2(e.Offset));
RINOK(ReadToBuf_FALSE(temp, e.Len));
}
if (!Meta.Parse(temp, temp.Size()))
return S_FALSE;
}
// UpdatePhySize(e.GetEndPos());
}
{
if (!region.Bat_Defined)
return S_FALSE;
const CRegionEntry &e = region.BatEntry;
if (e.Len == 0)
return S_FALSE;
// UpdatePhySize(e.GetEndPos());
{
RINOK(Seek2(e.Offset));
RINOK(ReadToBuf_FALSE(Bat.Data, e.Len));
}
if (!ParseBat())
return S_FALSE;
if (!CheckBat())
{
AddErrorMessage("BAT overlap");
// _BatOverlap = true;
// return S_FALSE;
}
}
}
{
// do we need to check "parent_linkage2" also?
FOR_VECTOR (i, Meta.ParentPairs)
{
const CParentPair &pair = Meta.ParentPairs[i];
if (pair.Key.IsEqualTo("parent_linkage"))
{
_parentGuid_IsDefined = _parentGuid.ParseFromFormatedHexString(pair.Value);
break;
}
}
}
{
// absolute paths for parent stream can be rejected later in client callback
// the order of check by specification:
static const char * const g_ParentKeys[] =
{
"relative_path" // "..\..\path2\sub3\parent.vhdx"
, "volume_path" // "\\?\Volume{26A21BDA-A627-11D7-9931-806E6F6E6963}\path2\sub3\parent.vhdx")
, "absolute_win32_path" // "d:\path2\sub3\parent.vhdx"
};
for (unsigned i = 0; i < ARRAY_SIZE(g_ParentKeys); i++)
{
const int index = Meta.FindParentKey(g_ParentKeys[i]);
if (index < 0)
continue;
ParentNames.Add(Meta.ParentPairs[index].Value);
}
}
if (Meta.Is_HasParent())
{
if (!Meta.Locator_Defined)
AddErrorMessage("Parent locator is not defined");
else
{
if (!_parentGuid_IsDefined)
AddErrorMessage("Parent GUID is not defined");
if (ParentNames.IsEmpty())
AddErrorMessage("Parent VHDX file name is not defined");
}
}
else
{
if (Meta.Locator_Defined)
AddErrorMessage("Unexpected parent locator");
}
// here we suppose that and locator can be used only with HasParent flag
// return S_FALSE;
_size = Meta.VirtualDiskSize; // CHandlerImg
// _posInArc = 0;
// Reset_PosInArc();
// RINOK(Stream->Seek(0, STREAM_SEEK_SET, NULL));
return S_OK;
}
/*
static UInt32 g_NumCalls = 0;
static UInt32 g_NumCalls2 = 0;
static struct CCounter { ~CCounter()
{
printf("\nNumCalls = %10u\n", g_NumCalls);
printf("NumCalls2 = %10u\n", g_NumCalls2);
} } g_Counter;
*/
STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)
{
// g_NumCalls++;
if (processedSize)
*processedSize = 0;
if (_virtPos >= Meta.VirtualDiskSize)
return S_OK;
{
const UInt64 rem = Meta.VirtualDiskSize - _virtPos;
if (size > rem)
size = (UInt32)rem;
}
if (size == 0)
return S_OK;
const size_t blockIndex = (size_t)(_virtPos >> Meta.BlockSize_Log);
const size_t chunkIndex = blockIndex >> ChunkRatio_Log;
const size_t chunkRatio = (size_t)1 << ChunkRatio_Log;
const size_t blockIndex2 = chunkIndex * (chunkRatio + 1) + (blockIndex & (chunkRatio - 1));
const UInt64 blockSectVal = Bat.GetItem(blockIndex2);
const UInt64 blockOffset = BAT_GET_OFFSET(blockSectVal);
const UInt32 blockState = BAT_GET_STATE(blockSectVal);
const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
const UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
size = MyMin(blockSize - offsetInBlock, size);
bool needParent = false;
bool needRead = false;
if (blockState == PAYLOAD_BLOCK_FULLY_PRESENT)
needRead = true;
else if (blockState == PAYLOAD_BLOCK_NOT_PRESENT)
{
/* for a differencing VHDX: parent virtual disk SHOULD be
inspected to determine the associated contents (SPECIFICATION).
we suppose that we should not check BitMap.
for fixed or dynamic VHDX files: the block contents are undefined and
can contain arbitrary data (SPECIFICATION). NTFS::pagefile.sys can use such state. */
if (IsDiff())
needParent = true;
}
else if (blockState == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
{
// only allowed for differencing VHDX files.
// associated sector bitmap block MUST be valid
if (chunkIndex >= BitMaps.Size())
return S_FALSE;
// else
{
const CByteBuffer &bitmap = BitMaps[(unsigned)chunkIndex];
const Byte *p = (const Byte *)bitmap;
if (!p)
return S_FALSE;
// else
{
// g_NumCalls2++;
const UInt64 sectorIndex = _virtPos >> Meta.LogicalSectorSize_Log;
#define BIT_MAP_UNIT_LOG 3 // it's for small block (4 KB)
// #define BIT_MAP_UNIT_LOG 5 // speed optimization for large blocks (16 KB)
const size_t offs = (size_t)(sectorIndex >> 3) &
(
(kBitmapSize - 1)
& ~(((UInt32)1 << (BIT_MAP_UNIT_LOG - 3)) - 1)
);
unsigned sector2 = (unsigned)sectorIndex & ((1 << BIT_MAP_UNIT_LOG) - 1);
#if BIT_MAP_UNIT_LOG == 5
UInt32 v = GetUi32(p + offs) >> sector2;
#else
unsigned v = (unsigned)p[offs] >> sector2;
#endif
// UInt32 v = GetUi32(p + offs) >> sector2;
const UInt32 sectorSize = (UInt32)1 << Meta.LogicalSectorSize_Log;
const UInt32 offsetInSector = (UInt32)_virtPos & (sectorSize - 1);
const unsigned bit = (unsigned)(v & 1);
if (bit)
needRead = true;
else
needParent = true; // zero - from the parent VHDX file
UInt32 rem = sectorSize - offsetInSector;
for (sector2++; sector2 < (1 << BIT_MAP_UNIT_LOG); sector2++)
{
v >>= 1;
if (bit != (v & 1))
break;
rem += sectorSize;
}
if (size > rem)
size = rem;
}
}
}
bool needZero = true;
HRESULT res = S_OK;
if (needParent)
{
if (!ParentStream)
return S_FALSE;
// if (ParentStream)
{
RINOK(ParentStream->Seek(_virtPos, STREAM_SEEK_SET, NULL));
size_t processed = size;
res = ReadStream(ParentStream, (Byte *)data, &processed);
size = (UInt32)processed;
needZero = false;
}
}
else if (needRead)
{
UInt32 processed = 0;
res = ReadPhy(blockOffset + offsetInBlock, data, size, processed);
size = processed;
needZero = false;
}
if (needZero)
memset(data, 0, size);
if (processedSize)
*processedSize = size;
_virtPos += size;
return res;
}
enum
{
kpidParent = kpidUserDefined
};
static const CStatProp kArcProps[] =
{
{ NULL, kpidClusterSize, VT_UI4},
{ NULL, kpidSectorSize, VT_UI4},
{ NULL, kpidMethod, VT_BSTR},
{ NULL, kpidNumVolumes, VT_UI4},
{ NULL, kpidTotalPhySize, VT_UI8},
{ "Parent", kpidParent, VT_BSTR},
{ NULL, kpidCreatorApp, VT_BSTR},
{ NULL, kpidComment, VT_BSTR},
{ NULL, kpidId, VT_BSTR}
};
static const Byte kProps[] =
{
kpidSize,
kpidPackSize
};
IMP_IInArchive_Props
IMP_IInArchive_ArcProps_WITH_NAME
void CHandler::AddErrorMessage(const char *message)
{
if (!_errorMessage.IsEmpty())
_errorMessage.Add_LF();
_errorMessage += message;
}
void CHandler::AddErrorMessage(const char *message, const wchar_t *name)
{
AddErrorMessage(message);
_errorMessage += name;
}
static void AddComment_Name(UString &s, const char *name)
{
s += name;
s += ": ";
}
static void AddComment_Bool(UString &s, const char *name, bool val)
{
AddComment_Name(s, name);
s += val ? "+" : "-";
s.Add_LF();
}
static void AddComment_UInt64(UString &s, const char *name, UInt64 v, bool showMB = false)
{
AddComment_Name(s, name);
s.Add_UInt64(v);
if (showMB)
{
s += " (";
s.Add_UInt64(v >> 20);
s += " MiB)";
}
s.Add_LF();
}
static void AddComment_BlockSize(UString &s, const char *name, unsigned logSize)
{
if (logSize != 0)
AddComment_UInt64(s, name, ((UInt64)1 << logSize));
}
void CHandler::AddComment(UString &s) const
{
AddComment_UInt64(s, "PhysicalSize", _phySize);
if (!_errorMessage.IsEmpty())
{
AddComment_Name(s, "Error");
s += _errorMessage;
s.Add_LF();
}
if (Meta.Guid_Defined)
{
AddComment_Name(s, "Id");
Meta.Guid.AddHexToString(s);
s.Add_LF();
}
AddComment_UInt64(s, "SequenceNumber", Header.SequenceNumber);
AddComment_UInt64(s, "LogLength", Header.LogLength, true);
for (unsigned i = 0; i < 3; i++)
{
const CGuid &g = Header.Guids[i];
if (g.IsZero())
continue;
if (i == 0)
s += "FileWrite";
else if (i == 1)
s += "DataWrite";
else
s += "Log";
AddComment_Name(s, "Guid");
g.AddHexToString(s);
s.Add_LF();
}
AddComment_Bool(s, "HasParent", Meta.Is_HasParent());
AddComment_Bool(s, "Fixed", Meta.Is_LeaveBlockAllocated());
if (Meta.Is_LeaveBlockAllocated())
AddComment_Bool(s, "DataContiguous", _isDataContiguous);
AddComment_BlockSize(s, "BlockSize", Meta.BlockSize_Log);
AddComment_BlockSize(s, "LogicalSectorSize", Meta.LogicalSectorSize_Log);
AddComment_BlockSize(s, "PhysicalSectorSize", Meta.PhysicalSectorSize_Log);
{
const UInt64 packSize = GetPackSize();
AddComment_UInt64(s, "PackSize", packSize, true);
const UInt64 headersSize = HeadersSize + ((UInt64)NumUsedBitMaps << kBitmapSize_Log);
AddComment_UInt64(s, "HeadersSize", headersSize, true);
AddComment_UInt64(s, "FreeSpace", _phySize - packSize - headersSize, true);
/*
if (NumUsed_1MB_Blocks_Defined)
AddComment_UInt64(s, "used2", (NumUsed_1MB_Blocks << 20));
*/
}
if (Meta.ParentPairs.Size() != 0)
{
s += "Parent:";
s.Add_LF();
FOR_VECTOR(i, Meta.ParentPairs)
{
const CParentPair &pair = Meta.ParentPairs[i];
s += " ";
s += pair.Key;
s += ": ";
s += pair.Value;
s.Add_LF();
}
s.Add_LF();
}
}
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
switch (propID)
{
case kpidMainSubfile: prop = (UInt32)0; break;
case kpidClusterSize: prop = (UInt32)1 << Meta.BlockSize_Log; break;
case kpidSectorSize: prop = (UInt32)1 << Meta.LogicalSectorSize_Log; break;
case kpidShortComment:
case kpidMethod:
{
AString s;
AddTypeString(s);
if (IsDiff())
{
s += " -> ";
const CHandler *p = this;
while (p && p->IsDiff())
p = p->Parent;
if (!p)
s += '?';
else
p->AddTypeString(s);
}
prop = s;
break;
}
case kpidComment:
{
UString s;
{
if (NumLevels > 1)
{
AddComment_UInt64(s, "NumVolumeLevels", NumLevels);
AddComment_UInt64(s, "PackSizeTotal", PackSize_Total, true);
s += "----";
s.Add_LF();
}
const CHandler *p = this;
for (;;)
{
if (p->_level != 0 || p->Parent)
AddComment_UInt64(s, "VolumeLevel", p->_level + 1);
p->AddComment(s);
if (!p->Parent)
break;
s += "----";
s.Add_LF();
{
s.Add_LF();
if (!p->ParentName_Used.IsEmpty())
{
AddComment_Name(s, "Name");
s += p->ParentName_Used;
s.Add_LF();
}
}
p = p->Parent;
}
}
prop = s;
break;
}
case kpidCreatorApp:
{
if (!_Creator.IsEmpty())
prop = _Creator;
break;
}
case kpidId:
{
if (Meta.Guid_Defined)
{
UString s;
Meta.Guid.AddHexToString(s);
prop = s;
}
break;
}
case kpidName:
{
if (Meta.Guid_Defined)
{
UString s;
Meta.Guid.AddHexToString(s);
s += ".vhdx";
prop = s;
}
break;
}
case kpidParent: if (IsDiff()) prop = GetParentSequence(); break;
case kpidPhySize: prop = _phySize; break;
case kpidTotalPhySize:
{
const CHandler *p = this;
UInt64 sum = 0;
do
{
sum += p->_phySize;
p = p->Parent;
}
while (p);
prop = sum;
break;
}
case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break;
case kpidError:
{
UString s;
const CHandler *p = this;
do
{
if (!p->_errorMessage.IsEmpty())
{
if (!s.IsEmpty())
s.Add_LF();
s += p->_errorMessage;
}
p = p->Parent;
}
while (p);
if (!s.IsEmpty())
prop = s;
break;
}
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback)
{
Stream = stream;
if (_level >= (1 << 20))
return S_FALSE;
RINOK(Open3());
NumLevels = 1;
PackSize_Total = GetPackSize();
if (_child)
{
if (!_child->_parentGuid.IsEqualTo(Header.Guids[kHeader_GUID_Index_DataWriteGuid]))
return S_FALSE;
const CHandler *child = _child;
do
{
/* We suppose that only FileWriteGuid is unique.
Another IDs must be identical in in difference and parent archives. */
if (Header.Guids[kHeader_GUID_Index_FileWriteGuid].IsEqualTo(
child->Header.Guids[kHeader_GUID_Index_FileWriteGuid])
&& _phySize == child->_phySize)
{
_isCyclic = true;
_isCyclic_or_CyclicParent = true;
AddErrorMessage("Cyclic parent archive was blocked");
return S_OK;
}
child = child->_child;
}
while (child);
}
if (!Meta.Is_HasParent())
return S_OK;
if (!Meta.Locator_Defined
|| !_parentGuid_IsDefined
|| ParentNames.IsEmpty())
{
return S_OK;
}
ParentName_Used = ParentNames.Front();
HRESULT res;
const unsigned kNumLevelsMax = (1 << 8); // Maybe we need to increase that limit
if (_level >= kNumLevelsMax - 1)
{
AddErrorMessage("Too many parent levels");
return S_OK;
}
bool _parentFileWasOpen = false;
if (!openArchiveCallback)
res = S_FALSE;
else
res = OpenParent(openArchiveCallback, _parentFileWasOpen);
if (res != S_OK)
{
if (res != S_FALSE)
return res;
if (_parentFileWasOpen)
AddErrorMessage("Can't parse parent VHDX file : ", ParentName_Used);
else
AddErrorMessage("Missing parent VHDX file : ", ParentName_Used);
}
return S_OK;
}
HRESULT CHandler::OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen)
{
_parentFileWasOpen = false;
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
if (!openVolumeCallback)
return S_FALSE;
{
CMyComPtr<IInStream> nextStream;
HRESULT res = S_FALSE;
UString name;
FOR_VECTOR (i, ParentNames)
{
name = ParentNames[i];
// we remove prefix ".\\', but client already can support any variant
if (name[0] == L'.' && name[1] == L'\\')
name.DeleteFrontal(2);
res = openVolumeCallback->GetStream(name, &nextStream);
if (res == S_OK && nextStream)
break;
if (res != S_OK && res != S_FALSE)
return res;
}
if (res == S_FALSE || !nextStream)
return S_FALSE;
ParentName_Used = name;
_parentFileWasOpen = true;
Parent = new CHandler;
ParentStream = Parent;
try
{
Parent->_level = _level + 1;
Parent->_child = this;
/* we could call CHandlerImg::Open() here.
but we don't need (_imgExt) in (Parent). So we call Open2() here */
Parent->Close();
res = Parent->Open2(nextStream, openArchiveCallback);
}
catch(...)
{
Parent = NULL;
ParentStream.Release();
res = S_FALSE;
throw;
}
if (res != S_OK)
{
Parent = NULL;
ParentStream.Release();
if (res == E_ABORT)
return res;
if (res != S_FALSE)
{
// we must show that error code
}
}
if (res == S_OK)
{
if (Parent->_isCyclic_or_CyclicParent)
_isCyclic_or_CyclicParent = true;
NumLevels = Parent->NumLevels + 1;
PackSize_Total += Parent->GetPackSize();
// we read BitMaps only if Parent was open
UInt64 numBytes = (UInt64)NumUsedBitMaps << kBitmapSize_Log;
if (openArchiveCallback && numBytes != 0)
{
RINOK(openArchiveCallback->SetTotal(NULL, &numBytes));
}
numBytes = 0;
for (size_t i = ChunkRatio; i < TotalBatEntries; i += ChunkRatio + 1)
{
const UInt64 v = Bat.GetItem(i);
const UInt64 offset = BAT_GET_OFFSET(v);
const unsigned state = BAT_GET_STATE(v);
CByteBuffer &buf = BitMaps.AddNew();
if (state == SB_BLOCK_PRESENT)
{
if (openArchiveCallback)
{
RINOK(openArchiveCallback->SetCompleted(NULL, &numBytes));
}
numBytes += kBitmapSize;
buf.Alloc(kBitmapSize);
RINOK(Seek2(offset));
RINOK(Read_FALSE(buf, kBitmapSize));
/*
for (unsigned i = 0; i < (1 << 20); i+=4)
{
UInt32 v = GetUi32(buf + i);
if (v != 0 && v != (UInt32)(Int32)-1)
printf("\n%7d %8x", i, v);
}
*/
}
}
}
}
return S_OK;
}
void CHandler::CloseAtError()
{
// CHandlerImg
Clear_HandlerImg_Vars();
Stream.Release();
_phySize = 0;
Bat.Clear();
BitMaps.Clear();
NumUsedBlocks = 0;
NumUsedBitMaps = 0;
HeadersSize = 0;
/*
NumUsed_1MB_Blocks = 0;
NumUsed_1MB_Blocks_Defined = false;
*/
Parent = NULL;
ParentStream.Release();
_errorMessage.Empty();
_Creator.Empty();
_nonEmptyLog = false;
_parentGuid_IsDefined = false;
_isDataContiguous = false;
// _BatOverlap = false;
ParentNames.Clear();
ParentName_Used.Empty();
Meta.Clear();
ChunkRatio_Log = 0;
ChunkRatio = 0;
TotalBatEntries = 0;
NumLevels = 0;
PackSize_Total = 0;
_isCyclic = false;
_isCyclic_or_CyclicParent = false;
}
STDMETHODIMP CHandler::Close()
{
CloseAtError();
return S_OK;
}
STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
switch (propID)
{
case kpidSize: prop = Meta.VirtualDiskSize; break;
case kpidPackSize: prop = PackSize_Total; break;
case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
{
COM_TRY_BEGIN
*stream = NULL;
// if some prarent is not OK, we don't create stream
if (!AreParentsOK())
return S_FALSE;
InitSeekPositions();
CMyComPtr<ISequentialInStream> streamTemp = this;
*stream = streamTemp.Detach();
return S_OK;
COM_TRY_END
}
REGISTER_ARC_I(
"VHDX", "vhdx avhdx", NULL, 0xc4,
kSignature,
0,
0,
NULL)
}}