// VhdxHandler.cpp #include "StdAfx.h" // #include #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 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 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 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 ®ion = 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 openVolumeCallback; openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); if (!openVolumeCallback) return S_FALSE; { CMyComPtr 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 streamTemp = this; *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( "VHDX", "vhdx avhdx", NULL, 0xc4, kSignature, 0, 0, NULL) }}