1 // VhdxHandler.cpp
2
3 #include "StdAfx.h"
4
5 // #include <stdio.h>
6
7 #include "../../../C/CpuArch.h"
8
9 #include "../../Common/ComTry.h"
10 #include "../../Common/IntToString.h"
11 #include "../../Common/StringToInt.h"
12 #include "../../Common/MyBuffer.h"
13
14 #include "../../Windows/PropVariant.h"
15
16 #include "../Common/RegisterArc.h"
17 #include "../Common/StreamUtils.h"
18
19 #include "HandlerCont.h"
20
21 #define Get16(p) GetUi16(p)
22 #define Get32(p) GetUi32(p)
23 #define Get64(p) GetUi64(p)
24
25 #define G32(_offs_, dest) dest = Get32(p + (_offs_))
26 #define G64(_offs_, dest) dest = Get64(p + (_offs_))
27
28 using namespace NWindows;
29
30
31 EXTERN_C_BEGIN
32
33 // CRC-32C (Castagnoli) : reversed for poly 0x1EDC6F41
34 #define k_Crc32c_Poly 0x82f63b78
35
36 MY_ALIGN(64)
37 static UInt32 g_Crc32c_Table[256];
38
Crc32c_GenerateTable()39 static void Z7_FASTCALL Crc32c_GenerateTable()
40 {
41 UInt32 i;
42 for (i = 0; i < 256; i++)
43 {
44 UInt32 r = i;
45 unsigned j;
46 for (j = 0; j < 8; j++)
47 r = (r >> 1) ^ (k_Crc32c_Poly & ((UInt32)0 - (r & 1)));
48 g_Crc32c_Table[i] = r;
49 }
50 }
51
52
53 #define CRC32C_INIT_VAL 0xFFFFFFFF
54
55 #define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
56
57 // UInt32 Z7_FASTCALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);
CrcUpdateT1_vhdx(UInt32 v,const void * data,size_t size,const UInt32 * table)58 static UInt32 Z7_FASTCALL CrcUpdateT1_vhdx(UInt32 v, const void *data, size_t size, const UInt32 *table)
59 {
60 const Byte *p = (const Byte *)data;
61 const Byte *pEnd = p + size;
62 for (; p != pEnd; p++)
63 v = CRC_UPDATE_BYTE_2(v, *p);
64 return v;
65 }
66
Crc32c_Calc(const void * data,size_t size)67 static UInt32 Z7_FASTCALL Crc32c_Calc(const void *data, size_t size)
68 {
69 return CrcUpdateT1_vhdx(CRC32C_INIT_VAL, data, size, g_Crc32c_Table) ^ CRC32C_INIT_VAL;
70 }
71
72 EXTERN_C_END
73
74
75 namespace NArchive {
76 namespace NVhdx {
77
C_CRC32c_TableInitNArchive::NVhdx::C_CRC32c_TableInit78 static struct C_CRC32c_TableInit { C_CRC32c_TableInit() { Crc32c_GenerateTable(); } } g_CRC32c_TableInit;
79
80 static const unsigned kSignatureSize = 8;
81 static const Byte kSignature[kSignatureSize] =
82 { 'v', 'h', 'd', 'x', 'f', 'i', 'l', 'e' };
83
84 static const unsigned kBitmapSize_Log = 20;
85 static const size_t kBitmapSize = (size_t)1 << kBitmapSize_Log;
86
87
IsZeroArr(const Byte * p,size_t size)88 static bool IsZeroArr(const Byte *p, size_t size)
89 {
90 for (size_t i = 0; i < size; i++)
91 if (p[i] != 0)
92 return false;
93 return true;
94 }
95
96
97
98 Z7_FORCE_INLINE
DecodeFrom2HexChars(const wchar_t * s)99 static int DecodeFrom2HexChars(const wchar_t *s)
100 {
101 unsigned v0 = (unsigned)s[0]; Z7_PARSE_HEX_DIGIT(v0, return -1;)
102 unsigned v1 = (unsigned)s[1]; Z7_PARSE_HEX_DIGIT(v1, return -1;)
103 return (int)((v0 << 4) | v1);
104 }
105
106
107 struct CGuid
108 {
109 Byte Data[16];
110
IsZeroNArchive::NVhdx::CGuid111 bool IsZero() const { return IsZeroArr(Data, 16); }
IsEqualToNArchive::NVhdx::CGuid112 bool IsEqualTo(const Byte *a) const { return memcmp(Data, a, 16) == 0; }
IsEqualToNArchive::NVhdx::CGuid113 bool IsEqualTo(const CGuid &g) const { return IsEqualTo(g.Data); }
114 void AddHexToString(UString &s) const;
115
SetFromNArchive::NVhdx::CGuid116 void SetFrom(const Byte *p) { memcpy(Data, p, 16); }
117
ParseFromFormatedHexStringNArchive::NVhdx::CGuid118 bool ParseFromFormatedHexString(const UString &s)
119 {
120 const unsigned kLen = 16 * 2 + 4 + 2;
121 if (s.Len() != kLen || s[0] != '{' || s[kLen - 1] != '}')
122 return false;
123 unsigned pos = 0;
124 for (unsigned i = 1; i < kLen - 1;)
125 {
126 if (i == 9 || i == 14 || i == 19 || i == 24)
127 {
128 if (s[i] != '-')
129 return false;
130 i++;
131 continue;
132 }
133 const int v = DecodeFrom2HexChars(s.Ptr(i));
134 if (v < 0)
135 return false;
136 unsigned pos2 = pos;
137 if (pos < 8)
138 pos2 ^= (pos < 4 ? 3 : 1);
139 Data[pos2] = (Byte)v;
140 pos++;
141 i += 2;
142 }
143 return true; // pos == 16;
144 }
145 };
146
AddHexToString(UString & s) const147 void CGuid::AddHexToString(UString &s) const
148 {
149 char temp[sizeof(Data) * 2 + 2];
150 ConvertDataToHex_Lower(temp, Data, sizeof(Data));
151 s += temp;
152 }
153
154
155 #define IS_NON_ALIGNED(v) (((v) & 0xFFFFF) != 0)
156
157 static const unsigned kHeader_GUID_Index_FileWriteGuid = 0;
158 static const unsigned kHeader_GUID_Index_DataWriteGuid = 1;
159 static const unsigned kHeader_GUID_Index_LogGuid = 2;
160
161 struct CHeader
162 {
163 UInt64 SequenceNumber;
164 // UInt16 LogVersion;
165 // UInt16 Version;
166 UInt32 LogLength;
167 UInt64 LogOffset;
168 CGuid Guids[3];
169
IsEqualToNArchive::NVhdx::CHeader170 bool IsEqualTo(const CHeader &h) const
171 {
172 if (SequenceNumber != h.SequenceNumber)
173 return false;
174 if (LogLength != h.LogLength)
175 return false;
176 if (LogOffset != h.LogOffset)
177 return false;
178 for (unsigned i = 0; i < 3; i++)
179 if (!Guids[i].IsEqualTo(h.Guids[i]))
180 return false;
181 return true;
182 }
183
184 bool Parse(Byte *p);
185 };
186
187 static const unsigned kHeader2Size = 1 << 12;
188
Parse(Byte * p)189 bool CHeader::Parse(Byte *p)
190 {
191 if (Get32(p) != 0x64616568) // "head"
192 return false;
193 const UInt32 crc = Get32(p + 4);
194 SetUi32(p + 4, 0)
195 if (Crc32c_Calc(p, kHeader2Size) != crc)
196 return false;
197 G64(8, SequenceNumber);
198 for (unsigned i = 0; i < 3; i++)
199 Guids[i].SetFrom(p + 0x10 + 0x10 * i);
200 // LogVersion = Get16(p + 0x40);
201 /* LogVersion MUST be set to zero, for known log format
202 but we don't parse log so we ignore it */
203 G32(0x44, LogLength);
204 G64(0x48, LogOffset);
205 if (Get16(p + 0x42) != 1) // Header format Version
206 return false;
207 if (IS_NON_ALIGNED(LogLength))
208 return false;
209 if (IS_NON_ALIGNED(LogOffset))
210 return false;
211 return true;
212 // return IsZeroArr(p + 0x50, kHeader2Size - 0x50);
213 }
214
215
216
217 static const Byte kBat[16] =
218 { 0x66,0x77,0xC2,0x2D,0x23,0xF6,0x00,0x42,0x9D,0x64,0x11,0x5E,0x9B,0xFD,0x4A,0x08 };
219 static const Byte kMetadataRegion[16] =
220 { 0x06,0xA2,0x7C,0x8B,0x90,0x47,0x9A,0x4B,0xB8,0xFE,0x57,0x5F,0x05,0x0F,0x88,0x6E };
221
222 struct CRegionEntry
223 {
224 // CGuid Guid;
225 UInt64 Offset;
226 UInt32 Len;
227 UInt32 Required;
228
GetEndPosNArchive::NVhdx::CRegionEntry229 UInt64 GetEndPos() const { return Offset + Len; }
230 bool Parse(const Byte *p);
231 };
232
Parse(const Byte * p)233 bool CRegionEntry::Parse(const Byte *p)
234 {
235 // Guid.SetFrom(p);
236 G64(0x10, Offset);
237 G32(0x18, Len);
238 G32(0x1c, Required);
239 if (IS_NON_ALIGNED(Offset))
240 return false;
241 if (IS_NON_ALIGNED(Len))
242 return false;
243 if (Offset + Len < Offset)
244 return false;
245 return true;
246 }
247
248
249 struct CRegion
250 {
251 bool Bat_Defined;
252 bool Meta_Defined;
253 UInt64 EndPos;
254 UInt64 DataSize;
255
256 CRegionEntry BatEntry;
257 CRegionEntry MetaEntry;
258
259 bool Parse(Byte *p);
260 };
261
262
263 static const size_t kRegionSize = 1 << 16;
264 static const unsigned kNumRegionEntriesMax = (1 << 11) - 1;
265
Parse(Byte * p)266 bool CRegion::Parse(Byte *p)
267 {
268 Bat_Defined = false;
269 Meta_Defined = false;
270 EndPos = 0;
271 DataSize = 0;
272
273 if (Get32(p) != 0x69676572) // "regi"
274 return false;
275 const UInt32 crc = Get32(p + 4);
276 SetUi32(p + 4, 0)
277 const UInt32 crc_calced = Crc32c_Calc(p, kRegionSize);
278 if (crc_calced != crc)
279 return false;
280
281 const UInt32 EntryCount = Get32(p + 8);
282 if (Get32(p + 12) != 0) // reserved field must be set to 0.
283 return false;
284 if (EntryCount > kNumRegionEntriesMax)
285 return false;
286 for (UInt32 i = 0; i < EntryCount; i++)
287 {
288 CRegionEntry e;
289 const Byte *p2 = p + 0x10 + 0x20 * (size_t)i;
290 if (!e.Parse(p2))
291 return false;
292 DataSize += e.Len;
293 const UInt64 endPos = e.GetEndPos();
294 if (EndPos < endPos)
295 EndPos = endPos;
296 CGuid Guid;
297 Guid.SetFrom(p2);
298 if (Guid.IsEqualTo(kBat))
299 {
300 if (Bat_Defined)
301 return false;
302 BatEntry = e;
303 Bat_Defined = true;
304 }
305 else if (Guid.IsEqualTo(kMetadataRegion))
306 {
307 if (Meta_Defined)
308 return false;
309 MetaEntry = e;
310 Meta_Defined = true;
311 }
312 else
313 {
314 if (e.Required != 0)
315 return false;
316 // it's allowed to ignore unknown non-required region entries
317 }
318 }
319 /*
320 const size_t k = 0x10 + 0x20 * EntryCount;
321 return IsZeroArr(p + k, kRegionSize - k);
322 */
323 return true;
324 }
325
326
327
328
329 struct CMetaEntry
330 {
331 CGuid Guid;
332 UInt32 Offset;
333 UInt32 Len;
334 UInt32 Flags0;
335 // UInt32 Flags1;
336
IsUserNArchive::NVhdx::CMetaEntry337 bool IsUser() const { return (Flags0 & 1) != 0; }
IsVirtualDiskNArchive::NVhdx::CMetaEntry338 bool IsVirtualDisk() const { return (Flags0 & 2) != 0; }
IsRequiredNArchive::NVhdx::CMetaEntry339 bool IsRequired() const { return (Flags0 & 4) != 0; }
340
CheckLimitNArchive::NVhdx::CMetaEntry341 bool CheckLimit(size_t regionSize) const
342 {
343 return Offset <= regionSize && Len <= regionSize - Offset;
344 }
345
346 bool Parse(const Byte *p);
347 };
348
349
Parse(const Byte * p)350 bool CMetaEntry::Parse(const Byte *p)
351 {
352 Guid.SetFrom(p);
353
354 G32(0x10, Offset);
355 G32(0x14, Len);
356 G32(0x18, Flags0);
357 UInt32 Flags1;
358 G32(0x1C, Flags1);
359
360 if (Offset != 0 && Offset < (1 << 16))
361 return false;
362 if (Len > (1 << 20))
363 return false;
364 if (Len == 0 && Offset != 0)
365 return false;
366 if ((Flags0 >> 3) != 0) // Reserved
367 return false;
368 if ((Flags1 & 3) != 0) // Reserved2
369 return false;
370 return true;
371 }
372
373
374 struct CParentPair
375 {
376 UString Key;
377 UString Value;
378 };
379
380
381 struct CMetaHeader
382 {
383 // UInt16 EntryCount;
384 bool Guid_Defined;
385 bool VirtualDiskSize_Defined;
386 bool Locator_Defined;
387
388 unsigned BlockSize_Log;
389 unsigned LogicalSectorSize_Log;
390 unsigned PhysicalSectorSize_Log;
391
392 UInt32 Flags;
393 UInt64 VirtualDiskSize;
394 CGuid Guid;
395 // CGuid LocatorType;
396
397 CObjectVector<CParentPair> ParentPairs;
398
FindParentKeyNArchive::NVhdx::CMetaHeader399 int FindParentKey(const char *name) const
400 {
401 FOR_VECTOR (i, ParentPairs)
402 {
403 const CParentPair &pair = ParentPairs[i];
404 if (pair.Key.IsEqualTo(name))
405 return (int)i;
406 }
407 return -1;
408 }
409
Is_LeaveBlockAllocatedNArchive::NVhdx::CMetaHeader410 bool Is_LeaveBlockAllocated() const { return (Flags & 1) != 0; }
Is_HasParentNArchive::NVhdx::CMetaHeader411 bool Is_HasParent() const { return (Flags & 2) != 0; }
412
ClearNArchive::NVhdx::CMetaHeader413 void Clear()
414 {
415 Guid_Defined = false;
416 VirtualDiskSize_Defined = false;
417 Locator_Defined = false;
418 BlockSize_Log = 0;
419 LogicalSectorSize_Log = 0;
420 PhysicalSectorSize_Log = 0;
421 Flags = 0;
422 VirtualDiskSize = 0;
423 ParentPairs.Clear();
424 }
425
426 bool Parse(const Byte *p, size_t size);
427 };
428
429
GetLogSize(UInt32 size)430 static unsigned GetLogSize(UInt32 size)
431 {
432 unsigned k;
433 for (k = 0; k < 32; k++)
434 if (((UInt32)1 << k) == size)
435 return k;
436 return k;
437 }
438
439
440 static const unsigned kMetadataSize = 8;
441 static const Byte kMetadata[kMetadataSize] =
442 { 'm','e','t','a','d','a','t','a' };
443
444 static const unsigned k_Num_MetaEntries_Max = (1 << 11) - 1;
445
446 static const Byte kFileParameters[16] =
447 { 0x37,0x67,0xa1,0xca,0x36,0xfa,0x43,0x4d,0xb3,0xb6,0x33,0xf0,0xaa,0x44,0xe7,0x6b };
448 static const Byte kVirtualDiskSize[16] =
449 { 0x24,0x42,0xa5,0x2f,0x1b,0xcd,0x76,0x48,0xb2,0x11,0x5d,0xbe,0xd8,0x3b,0xf4,0xb8 };
450 static const Byte kVirtualDiskID[16] =
451 { 0xab,0x12,0xca,0xbe,0xe6,0xb2,0x23,0x45,0x93,0xef,0xc3,0x09,0xe0,0x00,0xc7,0x46 };
452 static const Byte kLogicalSectorSize[16] =
453 { 0x1d,0xbf,0x41,0x81,0x6f,0xa9,0x09,0x47,0xba,0x47,0xf2,0x33,0xa8,0xfa,0xab,0x5f };
454 static const Byte kPhysicalSectorSize[16] =
455 { 0xc7,0x48,0xa3,0xcd,0x5d,0x44,0x71,0x44,0x9c,0xc9,0xe9,0x88,0x52,0x51,0xc5,0x56 };
456 static const Byte kParentLocator[16] =
457 { 0x2d,0x5f,0xd3,0xa8,0x0b,0xb3,0x4d,0x45,0xab,0xf7,0xd3,0xd8,0x48,0x34,0xab,0x0c };
458
GetString16(UString & s,const Byte * p,size_t size)459 static bool GetString16(UString &s, const Byte *p, size_t size)
460 {
461 s.Empty();
462 if (size & 1)
463 return false;
464 for (size_t i = 0; i < size; i += 2)
465 {
466 const wchar_t c = Get16(p + i);
467 if (c == 0)
468 return false;
469 s += c;
470 }
471 return true;
472 }
473
474
Parse(const Byte * p,size_t size)475 bool CMetaHeader::Parse(const Byte *p, size_t size)
476 {
477 if (memcmp(p, kMetadata, kMetadataSize) != 0)
478 return false;
479 if (Get16(p + 8) != 0) // Reserved
480 return false;
481 const UInt32 EntryCount = Get16(p + 10);
482 if (EntryCount > k_Num_MetaEntries_Max)
483 return false;
484 if (!IsZeroArr(p + 12, 20)) // Reserved
485 return false;
486
487 for (unsigned i = 0; i < EntryCount; i++)
488 {
489 CMetaEntry e;
490 if (!e.Parse(p + 32 + 32 * (size_t)i))
491 return false;
492 if (!e.CheckLimit(size))
493 return false;
494 const Byte *p2 = p + e.Offset;
495
496 if (e.Guid.IsEqualTo(kFileParameters))
497 {
498 if (BlockSize_Log != 0)
499 return false;
500 if (e.Len != 8)
501 return false;
502 const UInt32 v = Get32(p2);
503 Flags = Get32(p2 + 4);
504 BlockSize_Log = GetLogSize(v);
505 if (BlockSize_Log < 20 || BlockSize_Log > 28) // specification from 1 MB to 256 MB
506 return false;
507 if ((Flags >> 2) != 0) // reserved
508 return false;
509 }
510 else if (e.Guid.IsEqualTo(kVirtualDiskSize))
511 {
512 if (VirtualDiskSize_Defined)
513 return false;
514 if (e.Len != 8)
515 return false;
516 VirtualDiskSize = Get64(p2);
517 VirtualDiskSize_Defined = true;
518 }
519 else if (e.Guid.IsEqualTo(kVirtualDiskID))
520 {
521 if (e.Len != 16)
522 return false;
523 Guid.SetFrom(p2);
524 Guid_Defined = true;
525 }
526 else if (e.Guid.IsEqualTo(kLogicalSectorSize))
527 {
528 if (LogicalSectorSize_Log != 0)
529 return false;
530 if (e.Len != 4)
531 return false;
532 const UInt32 v = Get32(p2);
533 LogicalSectorSize_Log = GetLogSize(v);
534 if (LogicalSectorSize_Log != 9 && LogicalSectorSize_Log != 12)
535 return false;
536 }
537 else if (e.Guid.IsEqualTo(kPhysicalSectorSize))
538 {
539 if (PhysicalSectorSize_Log != 0)
540 return false;
541 if (e.Len != 4)
542 return false;
543 const UInt32 v = Get32(p2);
544 PhysicalSectorSize_Log = GetLogSize(v);
545 if (PhysicalSectorSize_Log != 9 && PhysicalSectorSize_Log != 12)
546 return false;
547 }
548 else if (e.Guid.IsEqualTo(kParentLocator))
549 {
550 if (Locator_Defined)
551 return false;
552 if (e.Len < 20)
553 return false;
554 // LocatorType.SetFrom(p2);
555 /* Specifies the type of the parent virtual disk.
556 is different for each type: VHDX, VHD or iSCSI.
557 only "B04AEFB7-D19E-4A81-B789-25B8E9445913" (for VHDX) is supported now
558 */
559 Locator_Defined = true;
560 if (Get16(p2 + 16) != 0) // reserved
561 return false;
562 const UInt32 KeyValueCount = Get16(p2 + 18);
563 if (20 + (UInt32)KeyValueCount * 12 > e.Len)
564 return false;
565 for (unsigned k = 0; k < KeyValueCount; k++)
566 {
567 const Byte *p3 = p2 + 20 + (size_t)k * 12;
568 const UInt32 KeyOffset = Get32(p3);
569 const UInt32 ValueOffset = Get32(p3 + 4);
570 const UInt32 KeyLength = Get16(p3 + 8);
571 const UInt32 ValueLength = Get16(p3 + 10);
572 if (KeyOffset > e.Len || KeyLength > e.Len - KeyOffset)
573 return false;
574 if (ValueOffset > e.Len || ValueLength > e.Len - ValueOffset)
575 return false;
576 CParentPair pair;
577 if (!GetString16(pair.Key, p2 + KeyOffset, KeyLength))
578 return false;
579 if (!GetString16(pair.Value, p2 + ValueOffset, ValueLength))
580 return false;
581 ParentPairs.Add(pair);
582 }
583 }
584 else
585 {
586 if (e.IsRequired())
587 return false;
588 // return false; // unknown metadata;
589 }
590 }
591
592 // some properties are required for correct processing
593
594 if (BlockSize_Log == 0)
595 return false;
596 if (LogicalSectorSize_Log == 0)
597 return false;
598 if (!VirtualDiskSize_Defined)
599 return false;
600 if (((UInt32)VirtualDiskSize & ((UInt32)1 << LogicalSectorSize_Log)) != 0)
601 return false;
602
603 // vhdx specification sets limit for 64 TB.
604 // do we need to check over same limit ?
605 const UInt64 kVirtualDiskSize_Max = (UInt64)1 << 46;
606 if (VirtualDiskSize > kVirtualDiskSize_Max)
607 return false;
608
609 return true;
610 }
611
612
613
614 struct CBat
615 {
616 CByteBuffer Data;
617
ClearNArchive::NVhdx::CBat618 void Clear() { Data.Free(); }
GetItemNArchive::NVhdx::CBat619 UInt64 GetItem(size_t n) const
620 {
621 return Get64(Data + n * 8);
622 }
623 };
624
625
626
627 Z7_class_CHandler_final: public CHandlerImg
628 {
629 UInt64 _phySize;
630
631 CBat Bat;
632 CObjectVector<CByteBuffer> BitMaps;
633
634 unsigned ChunkRatio_Log;
635 size_t ChunkRatio;
636 size_t TotalBatEntries;
637
638 CMetaHeader Meta;
639 CHeader Header;
640
641 UInt32 NumUsedBlocks;
642 UInt32 NumUsedBitMaps;
643 UInt64 HeadersSize;
644
645 UInt32 NumLevels;
646 UInt64 PackSize_Total;
647
648 /*
649 UInt64 NumUsed_1MB_Blocks; // data and bitmaps
650 bool NumUsed_1MB_Blocks_Defined;
651 */
652
653 CMyComPtr<IInStream> ParentStream;
654 CHandler *Parent;
655 UString _errorMessage;
656 UString _creator;
657
658 bool _nonEmptyLog;
659 bool _isDataContiguous;
660 // bool _batOverlap;
661
662 CGuid _parentGuid;
663 bool _parentGuid_IsDefined;
664 UStringVector ParentNames;
665 UString ParentName_Used;
666
667 const CHandler *_child;
668 unsigned _level;
669 bool _isCyclic;
670 bool _isCyclic_or_CyclicParent;
671
672 void AddErrorMessage(const char *message);
673 void AddErrorMessage(const char *message, const wchar_t *name);
674
675 void UpdatePhySize(UInt64 value)
676 {
677 if (_phySize < value)
678 _phySize = value;
679 }
680
681 HRESULT Seek2(UInt64 offset);
682 HRESULT Read_FALSE(Byte *data, size_t size)
683 {
684 return ReadStream_FALSE(Stream, data, size);
685 }
686 HRESULT ReadToBuf_FALSE(CByteBuffer &buf, size_t size)
687 {
688 buf.Alloc(size);
689 return ReadStream_FALSE(Stream, buf, size);
690 }
691
692 void InitSeekPositions();
693 HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);
694
695 bool IsDiff() const
696 {
697 // here we suppose that only HasParent() flag is mandatory for Diff archive type
698 return Meta.Is_HasParent();
699 // return _parentGuid_IsDefined;
700 }
701
702 void AddTypeString(AString &s) const
703 {
704 if (IsDiff())
705 s += "Differencing";
706 else
707 {
708 if (Meta.Is_LeaveBlockAllocated())
709 s += _isDataContiguous ? "fixed" : "fixed-non-cont";
710 else
711 s += "dynamic";
712 }
713 }
714
715 void AddComment(UString &s) const;
716
717 UInt64 GetPackSize() const
718 {
719 return (UInt64)NumUsedBlocks << Meta.BlockSize_Log;
720 }
721
722 UString GetParentSequence() const
723 {
724 const CHandler *p = this;
725 UString res;
726 while (p && p->IsDiff())
727 {
728 if (!res.IsEmpty())
729 res += " -> ";
730 res += ParentName_Used;
731 p = p->Parent;
732 }
733 return res;
734 }
735
736 bool AreParentsOK() const
737 {
738 if (_isCyclic_or_CyclicParent)
739 return false;
740 const CHandler *p = this;
741 while (p->IsDiff())
742 {
743 p = p->Parent;
744 if (!p)
745 return false;
746 }
747 return true;
748 }
749
750 // bool ParseLog(CByteBuffer &log);
751 bool ParseBat();
752 bool CheckBat();
753
754 HRESULT Open3();
755 HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override;
756 HRESULT OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen);
757 virtual void CloseAtError() Z7_override;
758
759 public:
760 Z7_IFACE_COM7_IMP(IInArchive_Img)
761
762 Z7_IFACE_COM7_IMP(IInArchiveGetStream)
763 Z7_IFACE_COM7_IMP(ISequentialInStream)
764
765 CHandler():
766 _child(NULL),
767 _level(0),
768 _isCyclic(false),
769 _isCyclic_or_CyclicParent(false)
770 {}
771 };
772
773
774 HRESULT CHandler::Seek2(UInt64 offset)
775 {
776 return InStream_SeekSet(Stream, offset);
777 }
778
779
780 void CHandler::InitSeekPositions()
781 {
782 /* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()).
783 So we must reset these variables before first call of Read() */
784 Reset_VirtPos();
785 Reset_PosInArc();
786 if (ParentStream)
787 Parent->InitSeekPositions();
788 }
789
790
791 HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed)
792 {
793 processed = 0;
794 if (offset > _phySize
795 || offset + size > _phySize)
796 {
797 // we don't expect these cases, if (_phySize) was set correctly.
798 return S_FALSE;
799 }
800 if (offset != _posInArc)
801 {
802 const HRESULT res = Seek2(offset);
803 if (res != S_OK)
804 {
805 Reset_PosInArc(); // we don't trust seek_pos in case of error
806 return res;
807 }
808 _posInArc = offset;
809 }
810 {
811 size_t size2 = size;
812 const HRESULT res = ReadStream(Stream, data, &size2);
813 processed = (UInt32)size2;
814 _posInArc += size2;
815 if (res != S_OK)
816 Reset_PosInArc(); // we don't trust seek_pos in case of reading error
817 return res;
818 }
819 }
820
821
822 #define PAYLOAD_BLOCK_NOT_PRESENT 0
823 #define PAYLOAD_BLOCK_UNDEFINED 1
824 #define PAYLOAD_BLOCK_ZERO 2
825 #define PAYLOAD_BLOCK_UNMAPPED 3
826 #define PAYLOAD_BLOCK_FULLY_PRESENT 6
827 #define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7
828
829 #define SB_BLOCK_NOT_PRESENT 0
830 #define SB_BLOCK_PRESENT 6
831
832 #define BAT_GET_OFFSET(v) ((v) & ~(UInt64)0xFFFFF)
833 #define BAT_GET_STATE(v) ((UInt32)(v) & 7)
834
835 /* The log contains only updates to metadata, bat and region tables
836 The log doesn't contain updates to start header, and 2 headers (first 192 KB of file).
837 The log is array of 4 KB blocks and each block has 4-byte signature.
838 So it's possible to scan whole log to find the latest entry sequence (and header for replay).
839 */
840
841 /*
842 struct CLogEntry
843 {
844 UInt32 EntryLength;
845 UInt32 Tail;
846 UInt64 SequenceNumber;
847 CGuid LogGuid;
848 UInt32 DescriptorCount;
849 UInt64 FlushedFileOffset;
850 UInt64 LastFileOffset;
851
852 bool Parse(const Byte *p);
853 };
854
855 bool CLogEntry::Parse(const Byte *p)
856 {
857 G32 (8, EntryLength);
858 G32 (12,Tail);
859 G64 (16, SequenceNumber);
860 G32 (24, DescriptorCount); // it's 32-bit, but specification says 64-bit
861 if (Get32(p + 28) != 0) // reserved
862 return false;
863 LogGuid.SetFrom(p + 32);
864 G64 (48, FlushedFileOffset);
865 G64 (56, LastFileOffset);
866
867 if (SequenceNumber == 0)
868 return false;
869 if ((Tail & 0xfff) != 0)
870 return false;
871 if (IS_NON_ALIGNED(FlushedFileOffset))
872 return false;
873 if (IS_NON_ALIGNED(LastFileOffset))
874 return false;
875 return true;
876 }
877
878
879 bool CHandler::ParseLog(CByteBuffer &log)
880 {
881 CLogEntry lastEntry;
882 lastEntry.SequenceNumber = 0;
883 bool lastEntry_found = false;
884 size_t lastEntry_Offset = 0;
885 for (size_t i = 0; i < log.Size(); i += 1 << 12)
886 {
887 Byte *p = (Byte *)(log + i);
888
889 if (Get32(p) != 0x65676F6C) // "loge"
890 continue;
891 const UInt32 crc = Get32(p + 4);
892
893 CLogEntry e;
894 if (!e.Parse(p))
895 {
896 return false;
897 continue;
898 }
899 const UInt32 entryLength = Get32(p + 8);
900 if (e.EntryLength > log.Size() || (e.EntryLength & 0xFFF) != 0 || e.EntryLength == 0)
901 {
902 return false;
903 continue;
904 }
905 SetUi32(p + 4, 0);
906 const UInt32 crc_calced = Crc32c_Calc(p, entryLength);
907 SetUi32(p + 4, crc); // we must restore crc if we want same data in log
908 if (crc_calced != crc)
909 continue;
910 if (!lastEntry_found || lastEntry.SequenceNumber < e.SequenceNumber)
911 {
912 lastEntry = e;
913 lastEntry_found = true;
914 lastEntry_Offset = i;
915 }
916 }
917
918 return true;
919 }
920 */
921
922
923 bool CHandler::ParseBat()
924 {
925 ChunkRatio_Log = kBitmapSize_Log + 3 + Meta.LogicalSectorSize_Log - Meta.BlockSize_Log;
926 ChunkRatio = (size_t)1 << (ChunkRatio_Log);
927
928 UInt64 totalBatEntries64;
929 const bool isDiff = IsDiff();
930 const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
931 {
932 const UInt64 up = Meta.VirtualDiskSize + blockSize - 1;
933 if (up < Meta.VirtualDiskSize)
934 return false;
935 const UInt64 numDataBlocks = up >> Meta.BlockSize_Log;
936
937 if (isDiff)
938 {
939 // differencing table must be finished with bitmap entry
940 const UInt64 numBitmaps = (numDataBlocks + ChunkRatio - 1) >> ChunkRatio_Log;
941 totalBatEntries64 = numBitmaps * (ChunkRatio + 1);
942 }
943 else
944 {
945 // we don't need last Bitmap entry
946 totalBatEntries64 = numDataBlocks + ((numDataBlocks - 1) >> ChunkRatio_Log);
947 }
948 }
949
950 if (totalBatEntries64 > Bat.Data.Size() / 8)
951 return false;
952
953 const size_t totalBatEntries = (size_t)totalBatEntries64;
954 TotalBatEntries = totalBatEntries;
955
956 bool isCont = (!isDiff && Meta.Is_LeaveBlockAllocated());
957 UInt64 prevBlockOffset = 0;
958 UInt64 maxBlockOffset = 0;
959
960 size_t remEntries = ChunkRatio + 1;
961
962 size_t i;
963 for (i = 0; i < totalBatEntries; i++)
964 {
965 const UInt64 v = Bat.GetItem(i);
966 if ((v & 0xFFFF8) != 0)
967 return false;
968 const UInt64 offset = BAT_GET_OFFSET(v);
969 const unsigned state = BAT_GET_STATE(v);
970
971 /*
972 UInt64 index64 = v >> 20;
973 printf("\n%7d", i);
974 printf("%10d, ", (unsigned)index64);
975 printf("%4x, ", (unsigned)state);
976 */
977
978 remEntries--;
979 if (remEntries == 0)
980 {
981 // printf(" ========");
982 // printf("\n");
983 remEntries = ChunkRatio + 1;
984 if (state == SB_BLOCK_PRESENT)
985 {
986 isCont = false;
987 if (!isDiff)
988 return false;
989 if (offset == 0)
990 return false;
991 const UInt64 lim = offset + kBitmapSize;
992 if (lim < offset)
993 return false;
994 if (_phySize < lim)
995 _phySize = lim;
996 NumUsedBitMaps++;
997 }
998 else if (state != SB_BLOCK_NOT_PRESENT)
999 return false;
1000 }
1001 else
1002 {
1003 if (state == PAYLOAD_BLOCK_FULLY_PRESENT
1004 || state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
1005 {
1006 if (offset == 0)
1007 return false;
1008 if (maxBlockOffset < offset)
1009 maxBlockOffset = offset;
1010
1011 if (state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
1012 {
1013 isCont = false;
1014 if (!isDiff)
1015 return false;
1016 }
1017 else if (isCont)
1018 {
1019 if (prevBlockOffset != 0 && prevBlockOffset + blockSize != offset)
1020 isCont = false;
1021 else
1022 prevBlockOffset = offset;
1023 }
1024
1025 NumUsedBlocks++;
1026 }
1027 else if (state == PAYLOAD_BLOCK_UNMAPPED)
1028 {
1029 isCont = false;
1030 // non-empty (offset) is allowed
1031 }
1032 else if (state == PAYLOAD_BLOCK_NOT_PRESENT
1033 || state == PAYLOAD_BLOCK_UNDEFINED
1034 || state == PAYLOAD_BLOCK_ZERO)
1035 {
1036 isCont = false;
1037 /* (offset) is reserved and (offset == 0) is expected here,
1038 but we ignore (offset) here */
1039 // if (offset != 0) return false;
1040 }
1041 else
1042 return false;
1043 }
1044 }
1045
1046 _isDataContiguous = isCont;
1047
1048 if (maxBlockOffset != 0)
1049 {
1050 const UInt64 lim = maxBlockOffset + blockSize;
1051 if (lim < maxBlockOffset)
1052 return false;
1053 if (_phySize < lim)
1054 _phySize = lim;
1055 const UInt64 kPhyLimit = (UInt64)1 << 62;
1056 if (maxBlockOffset >= kPhyLimit)
1057 return false;
1058 }
1059 return true;
1060 }
1061
1062
1063 bool CHandler::CheckBat()
1064 {
1065 const UInt64 upSize = _phySize + kBitmapSize * 8 - 1;
1066 if (upSize < _phySize)
1067 return false;
1068 const UInt64 useMapSize64 = upSize >> (kBitmapSize_Log + 3);
1069 const size_t useMapSize = (size_t)useMapSize64;
1070
1071 const UInt32 blockSizeMB = (UInt32)1 << (Meta.BlockSize_Log - kBitmapSize_Log);
1072
1073 // we don't check useMap, if it's too big.
1074 if (useMapSize != useMapSize64)
1075 return true;
1076 if (useMapSize == 0 || useMapSize > ((size_t)1 << 28))
1077 return true;
1078
1079 CByteArr useMap;
1080 useMap.Alloc(useMapSize);
1081 memset(useMap, 0, useMapSize);
1082 // useMap[0] = (Byte)(1 << 0); // first 1 MB is used by headers
1083 // we can also update useMap for log, and region data.
1084
1085 const size_t totalBatEntries = TotalBatEntries;
1086 size_t remEntries = ChunkRatio + 1;
1087
1088 size_t i;
1089 for (i = 0; i < totalBatEntries; i++)
1090 {
1091 const UInt64 v = Bat.GetItem(i);
1092 const UInt64 offset = BAT_GET_OFFSET(v);
1093 const unsigned state = BAT_GET_STATE(v);
1094 const UInt64 index = offset >> kBitmapSize_Log;
1095 UInt32 numBlocks = 1;
1096 remEntries--;
1097 if (remEntries == 0)
1098 {
1099 remEntries = ChunkRatio + 1;
1100 if (state != SB_BLOCK_PRESENT)
1101 continue;
1102 }
1103 else
1104 {
1105 if (state != PAYLOAD_BLOCK_FULLY_PRESENT &&
1106 state != PAYLOAD_BLOCK_PARTIALLY_PRESENT)
1107 continue;
1108 numBlocks = blockSizeMB;
1109 }
1110
1111 for (unsigned k = 0; k < numBlocks; k++)
1112 {
1113 const UInt64 index2 = index + k;
1114 const unsigned flag = (unsigned)1 << ((unsigned)index2 & 7);
1115 const size_t byteIndex = (size_t)(index2 >> 3);
1116 if (byteIndex >= useMapSize)
1117 return false;
1118 const unsigned m = useMap[byteIndex];
1119 if (m & flag)
1120 return false;
1121 useMap[byteIndex] = (Byte)(m | flag);
1122 }
1123 }
1124
1125 /*
1126 UInt64 num = 0;
1127 for (i = 0; i < useMapSize; i++)
1128 {
1129 Byte b = useMap[i];
1130 unsigned t = 0;
1131 t += (b & 1); b >>= 1;
1132 t += (b & 1); b >>= 1;
1133 t += (b & 1); b >>= 1;
1134 t += (b & 1); b >>= 1;
1135 t += (b & 1); b >>= 1;
1136 t += (b & 1); b >>= 1;
1137 t += (b & 1); b >>= 1;
1138 t += (b & 1);
1139 num += t;
1140 }
1141 NumUsed_1MB_Blocks = num;
1142 NumUsed_1MB_Blocks_Defined = true;
1143 */
1144
1145 return true;
1146 }
1147
1148
1149
1150 HRESULT CHandler::Open3()
1151 {
1152 {
1153 const unsigned kHeaderSize = 512; // + 8
1154 Byte header[kHeaderSize];
1155
1156 RINOK(Read_FALSE(header, kHeaderSize))
1157
1158 if (memcmp(header, kSignature, kSignatureSize) != 0)
1159 return S_FALSE;
1160
1161 const Byte *p = &header[0];
1162 for (unsigned i = kSignatureSize; i < kHeaderSize; i += 2)
1163 {
1164 const wchar_t c = Get16(p + i);
1165 if (c < 0x20 || c > 0x7F)
1166 break;
1167 _creator += c;
1168 }
1169 }
1170
1171 HeadersSize = (UInt32)1 << 20;
1172 CHeader headers[2];
1173 {
1174 Byte header[kHeader2Size];
1175 for (unsigned i = 0; i < 2; i++)
1176 {
1177 RINOK(Seek2((1 << 16) * (1 + i)))
1178 RINOK(Read_FALSE(header, kHeader2Size))
1179 bool headerIsOK = headers[i].Parse(header);
1180 if (!headerIsOK)
1181 return S_FALSE;
1182 }
1183 }
1184 unsigned mainIndex;
1185 if (headers[0].SequenceNumber > headers[1].SequenceNumber) mainIndex = 0;
1186 else if (headers[0].SequenceNumber < headers[1].SequenceNumber) mainIndex = 1;
1187 else
1188 {
1189 /* Disk2vhd v2.02 can create image with 2 full copies of headers.
1190 It's violation of VHDX specification:
1191 "A header is current if it is the only valid header
1192 or if it is valid and its SequenceNumber field is
1193 greater than the other header's SequenceNumber".
1194 but we support such Disk2vhd archives. */
1195 if (!headers[0].IsEqualTo(headers[1]))
1196 return S_FALSE;
1197 mainIndex = 0;
1198 }
1199
1200 const CHeader &h = headers[mainIndex];
1201 Header = h;
1202 if (h.LogLength != 0)
1203 {
1204 HeadersSize += h.LogLength;
1205 UpdatePhySize(h.LogOffset + h.LogLength);
1206 if (!h.Guids[kHeader_GUID_Index_LogGuid].IsZero())
1207 {
1208 _nonEmptyLog = true;
1209 AddErrorMessage("non-empty LOG was not replayed");
1210 /*
1211 if (h.LogVersion != 0)
1212 AddErrorMessage("unknown LogVresion");
1213 else
1214 {
1215 CByteBuffer log;
1216 RINOK(Seek2(h.LogOffset));
1217 RINOK(ReadToBuf_FALSE(log, h.LogLength));
1218 if (!ParseLog(log))
1219 {
1220 return S_FALSE;
1221 }
1222 }
1223 */
1224 }
1225 }
1226 CRegion regions[2];
1227 int correctRegionIndex = -1;
1228
1229 {
1230 CByteBuffer temp;
1231 temp.Alloc(kRegionSize * 2);
1232 RINOK(Seek2((1 << 16) * 3))
1233 RINOK(Read_FALSE(temp, kRegionSize * 2))
1234 unsigned numTables = 1;
1235 if (memcmp(temp, temp + kRegionSize, kRegionSize) != 0)
1236 {
1237 AddErrorMessage("Region tables mismatch");
1238 numTables = 2;
1239 }
1240
1241 for (unsigned i = 0; i < numTables; i++)
1242 {
1243 // RINOK(Seek2((1 << 16) * (3 + i)));
1244 // RINOK(Read_FALSE(temp, kRegionSize));
1245 if (regions[i].Parse(temp))
1246 {
1247 if (correctRegionIndex < 0)
1248 correctRegionIndex = (int)i;
1249 }
1250 else
1251 {
1252 AddErrorMessage("Incorrect region table");
1253 }
1254 }
1255 if (correctRegionIndex < 0)
1256 return S_FALSE;
1257 /*
1258 if (!regions[0].IsEqualTo(regions[1]))
1259 return S_FALSE;
1260 */
1261 }
1262
1263 // UpdatePhySize((1 << 16) * 5);
1264 UpdatePhySize(1 << 20);
1265
1266 {
1267 const CRegion ®ion = regions[correctRegionIndex];
1268 HeadersSize += region.DataSize;
1269 UpdatePhySize(region.EndPos);
1270 {
1271 if (!region.Meta_Defined)
1272 return S_FALSE;
1273 const CRegionEntry &e = region.MetaEntry;
1274 if (e.Len == 0)
1275 return S_FALSE;
1276 {
1277 // static const kMetaTableSize = 1 << 16;
1278 CByteBuffer temp;
1279 {
1280 RINOK(Seek2(e.Offset))
1281 RINOK(ReadToBuf_FALSE(temp, e.Len))
1282 }
1283 if (!Meta.Parse(temp, temp.Size()))
1284 return S_FALSE;
1285 }
1286 // UpdatePhySize(e.GetEndPos());
1287 }
1288 {
1289 if (!region.Bat_Defined)
1290 return S_FALSE;
1291 const CRegionEntry &e = region.BatEntry;
1292 if (e.Len == 0)
1293 return S_FALSE;
1294 // UpdatePhySize(e.GetEndPos());
1295 {
1296 RINOK(Seek2(e.Offset))
1297 RINOK(ReadToBuf_FALSE(Bat.Data, e.Len))
1298 }
1299 if (!ParseBat())
1300 return S_FALSE;
1301 if (!CheckBat())
1302 {
1303 AddErrorMessage("BAT overlap");
1304 // _batOverlap = true;
1305 // return S_FALSE;
1306 }
1307 }
1308 }
1309
1310 {
1311 // do we need to check "parent_linkage2" also?
1312 FOR_VECTOR (i, Meta.ParentPairs)
1313 {
1314 const CParentPair &pair = Meta.ParentPairs[i];
1315 if (pair.Key.IsEqualTo("parent_linkage"))
1316 {
1317 _parentGuid_IsDefined = _parentGuid.ParseFromFormatedHexString(pair.Value);
1318 break;
1319 }
1320 }
1321 }
1322
1323 {
1324 // absolute paths for parent stream can be rejected later in client callback
1325 // the order of check by specification:
1326 const char * const g_ParentKeys[] =
1327 {
1328 "relative_path" // "..\..\path2\sub3\parent.vhdx"
1329 , "volume_path" // "\\?\Volume{26A21BDA-A627-11D7-9931-806E6F6E6963}\path2\sub3\parent.vhdx")
1330 , "absolute_win32_path" // "d:\path2\sub3\parent.vhdx"
1331 };
1332 for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ParentKeys); i++)
1333 {
1334 const int index = Meta.FindParentKey(g_ParentKeys[i]);
1335 if (index < 0)
1336 continue;
1337 ParentNames.Add(Meta.ParentPairs[index].Value);
1338 }
1339 }
1340
1341 if (Meta.Is_HasParent())
1342 {
1343 if (!Meta.Locator_Defined)
1344 AddErrorMessage("Parent locator is not defined");
1345 else
1346 {
1347 if (!_parentGuid_IsDefined)
1348 AddErrorMessage("Parent GUID is not defined");
1349 if (ParentNames.IsEmpty())
1350 AddErrorMessage("Parent VHDX file name is not defined");
1351 }
1352 }
1353 else
1354 {
1355 if (Meta.Locator_Defined)
1356 AddErrorMessage("Unexpected parent locator");
1357 }
1358
1359 // here we suppose that and locator can be used only with HasParent flag
1360
1361 // return S_FALSE;
1362
1363 _size = Meta.VirtualDiskSize; // CHandlerImg
1364
1365 // _posInArc = 0;
1366 // Reset_PosInArc();
1367 // RINOK(InStream_SeekToBegin(Stream))
1368
1369 return S_OK;
1370 }
1371
1372
1373 /*
1374 static UInt32 g_NumCalls = 0;
1375 static UInt32 g_NumCalls2 = 0;
1376 static struct CCounter { ~CCounter()
1377 {
1378 printf("\nNumCalls = %10u\n", g_NumCalls);
1379 printf("NumCalls2 = %10u\n", g_NumCalls2);
1380 } } g_Counter;
1381 */
1382
1383 Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize))
1384 {
1385 // g_NumCalls++;
1386 if (processedSize)
1387 *processedSize = 0;
1388 if (_virtPos >= Meta.VirtualDiskSize)
1389 return S_OK;
1390 {
1391 const UInt64 rem = Meta.VirtualDiskSize - _virtPos;
1392 if (size > rem)
1393 size = (UInt32)rem;
1394 }
1395 if (size == 0)
1396 return S_OK;
1397 const size_t blockIndex = (size_t)(_virtPos >> Meta.BlockSize_Log);
1398 const size_t chunkIndex = blockIndex >> ChunkRatio_Log;
1399 const size_t chunkRatio = (size_t)1 << ChunkRatio_Log;
1400 const size_t blockIndex2 = chunkIndex * (chunkRatio + 1) + (blockIndex & (chunkRatio - 1));
1401 const UInt64 blockSectVal = Bat.GetItem(blockIndex2);
1402 const UInt64 blockOffset = BAT_GET_OFFSET(blockSectVal);
1403 const UInt32 blockState = BAT_GET_STATE(blockSectVal);
1404
1405 const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
1406 const UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
1407 size = MyMin(blockSize - offsetInBlock, size);
1408
1409 bool needParent = false;
1410 bool needRead = false;
1411
1412 if (blockState == PAYLOAD_BLOCK_FULLY_PRESENT)
1413 needRead = true;
1414 else if (blockState == PAYLOAD_BLOCK_NOT_PRESENT)
1415 {
1416 /* for a differencing VHDX: parent virtual disk SHOULD be
1417 inspected to determine the associated contents (SPECIFICATION).
1418 we suppose that we should not check BitMap.
1419 for fixed or dynamic VHDX files: the block contents are undefined and
1420 can contain arbitrary data (SPECIFICATION). NTFS::pagefile.sys can use such state. */
1421 if (IsDiff())
1422 needParent = true;
1423 }
1424 else if (blockState == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
1425 {
1426 // only allowed for differencing VHDX files.
1427 // associated sector bitmap block MUST be valid
1428 if (chunkIndex >= BitMaps.Size())
1429 return S_FALSE;
1430 // else
1431 {
1432 const CByteBuffer &bitmap = BitMaps[(unsigned)chunkIndex];
1433 const Byte *p = (const Byte *)bitmap;
1434 if (!p)
1435 return S_FALSE;
1436 // else
1437 {
1438 // g_NumCalls2++;
1439 const UInt64 sectorIndex = _virtPos >> Meta.LogicalSectorSize_Log;
1440
1441 #define BIT_MAP_UNIT_LOG 3 // it's for small block (4 KB)
1442 // #define BIT_MAP_UNIT_LOG 5 // speed optimization for large blocks (16 KB)
1443
1444 const size_t offs = (size_t)(sectorIndex >> 3) &
1445 (
1446 (kBitmapSize - 1)
1447 & ~(((UInt32)1 << (BIT_MAP_UNIT_LOG - 3)) - 1)
1448 );
1449
1450 unsigned sector2 = (unsigned)sectorIndex & ((1 << BIT_MAP_UNIT_LOG) - 1);
1451 #if BIT_MAP_UNIT_LOG == 5
1452 UInt32 v = GetUi32(p + offs) >> sector2;
1453 #else
1454 unsigned v = (unsigned)p[offs] >> sector2;
1455 #endif
1456 // UInt32 v = GetUi32(p + offs) >> sector2;
1457 const UInt32 sectorSize = (UInt32)1 << Meta.LogicalSectorSize_Log;
1458 const UInt32 offsetInSector = (UInt32)_virtPos & (sectorSize - 1);
1459 const unsigned bit = (unsigned)(v & 1);
1460 if (bit)
1461 needRead = true;
1462 else
1463 needParent = true; // zero - from the parent VHDX file
1464 UInt32 rem = sectorSize - offsetInSector;
1465 for (sector2++; sector2 < (1 << BIT_MAP_UNIT_LOG); sector2++)
1466 {
1467 v >>= 1;
1468 if (bit != (v & 1))
1469 break;
1470 rem += sectorSize;
1471 }
1472 if (size > rem)
1473 size = rem;
1474 }
1475 }
1476 }
1477
1478 bool needZero = true;
1479
1480 HRESULT res = S_OK;
1481
1482 if (needParent)
1483 {
1484 if (!ParentStream)
1485 return S_FALSE;
1486 // if (ParentStream)
1487 {
1488 RINOK(InStream_SeekSet(ParentStream, _virtPos))
1489 size_t processed = size;
1490 res = ReadStream(ParentStream, (Byte *)data, &processed);
1491 size = (UInt32)processed;
1492 needZero = false;
1493 }
1494 }
1495 else if (needRead)
1496 {
1497 UInt32 processed = 0;
1498 res = ReadPhy(blockOffset + offsetInBlock, data, size, processed);
1499 size = processed;
1500 needZero = false;
1501 }
1502
1503 if (needZero)
1504 memset(data, 0, size);
1505
1506 if (processedSize)
1507 *processedSize = size;
1508
1509 _virtPos += size;
1510 return res;
1511 }
1512
1513
1514 enum
1515 {
1516 kpidParent = kpidUserDefined
1517 };
1518
1519 static const CStatProp kArcProps[] =
1520 {
1521 { NULL, kpidClusterSize, VT_UI4},
1522 { NULL, kpidSectorSize, VT_UI4},
1523 { NULL, kpidMethod, VT_BSTR},
1524 { NULL, kpidNumVolumes, VT_UI4},
1525 { NULL, kpidTotalPhySize, VT_UI8},
1526 { "Parent", kpidParent, VT_BSTR},
1527 { NULL, kpidCreatorApp, VT_BSTR},
1528 { NULL, kpidComment, VT_BSTR},
1529 { NULL, kpidId, VT_BSTR}
1530 };
1531
1532 static const Byte kProps[] =
1533 {
1534 kpidSize,
1535 kpidPackSize
1536 };
1537
1538 IMP_IInArchive_Props
1539 IMP_IInArchive_ArcProps_WITH_NAME
1540
1541
1542 void CHandler::AddErrorMessage(const char *message)
1543 {
1544 if (!_errorMessage.IsEmpty())
1545 _errorMessage.Add_LF();
1546 _errorMessage += message;
1547 }
1548
1549 void CHandler::AddErrorMessage(const char *message, const wchar_t *name)
1550 {
1551 AddErrorMessage(message);
1552 _errorMessage += name;
1553 }
1554
1555
1556 static void AddComment_Name(UString &s, const char *name)
1557 {
1558 s += name;
1559 s += ": ";
1560 }
1561
1562 static void AddComment_Bool(UString &s, const char *name, bool val)
1563 {
1564 AddComment_Name(s, name);
1565 s.Add_Char(val ? '+' : '-');
1566 s.Add_LF();
1567 }
1568
1569 static void AddComment_UInt64(UString &s, const char *name, UInt64 v, bool showMB = false)
1570 {
1571 AddComment_Name(s, name);
1572 s.Add_UInt64(v);
1573 if (showMB)
1574 {
1575 s += " (";
1576 s.Add_UInt64(v >> 20);
1577 s += " MiB)";
1578 }
1579 s.Add_LF();
1580 }
1581
1582 static void AddComment_BlockSize(UString &s, const char *name, unsigned logSize)
1583 {
1584 if (logSize != 0)
1585 AddComment_UInt64(s, name, ((UInt64)1 << logSize));
1586 }
1587
1588
1589 void CHandler::AddComment(UString &s) const
1590 {
1591 AddComment_UInt64(s, "VirtualDiskSize", Meta.VirtualDiskSize);
1592 AddComment_UInt64(s, "PhysicalSize", _phySize);
1593
1594 if (!_errorMessage.IsEmpty())
1595 {
1596 AddComment_Name(s, "Error");
1597 s += _errorMessage;
1598 s.Add_LF();
1599 }
1600
1601 if (Meta.Guid_Defined)
1602 {
1603 AddComment_Name(s, "Id");
1604 Meta.Guid.AddHexToString(s);
1605 s.Add_LF();
1606 }
1607
1608 AddComment_UInt64(s, "SequenceNumber", Header.SequenceNumber);
1609 AddComment_UInt64(s, "LogLength", Header.LogLength, true);
1610
1611 for (unsigned i = 0; i < 3; i++)
1612 {
1613 const CGuid &g = Header.Guids[i];
1614 if (g.IsZero())
1615 continue;
1616 if (i == 0)
1617 s += "FileWrite";
1618 else if (i == 1)
1619 s += "DataWrite";
1620 else
1621 s += "Log";
1622 AddComment_Name(s, "Guid");
1623 g.AddHexToString(s);
1624 s.Add_LF();
1625 }
1626
1627 AddComment_Bool(s, "HasParent", Meta.Is_HasParent());
1628 AddComment_Bool(s, "Fixed", Meta.Is_LeaveBlockAllocated());
1629 if (Meta.Is_LeaveBlockAllocated())
1630 AddComment_Bool(s, "DataContiguous", _isDataContiguous);
1631
1632 AddComment_BlockSize(s, "BlockSize", Meta.BlockSize_Log);
1633 AddComment_BlockSize(s, "LogicalSectorSize", Meta.LogicalSectorSize_Log);
1634 AddComment_BlockSize(s, "PhysicalSectorSize", Meta.PhysicalSectorSize_Log);
1635
1636 {
1637 const UInt64 packSize = GetPackSize();
1638 AddComment_UInt64(s, "PackSize", packSize, true);
1639 const UInt64 headersSize = HeadersSize + ((UInt64)NumUsedBitMaps << kBitmapSize_Log);
1640 AddComment_UInt64(s, "HeadersSize", headersSize, true);
1641 AddComment_UInt64(s, "FreeSpace", _phySize - packSize - headersSize, true);
1642 /*
1643 if (NumUsed_1MB_Blocks_Defined)
1644 AddComment_UInt64(s, "used2", (NumUsed_1MB_Blocks << 20));
1645 */
1646 }
1647
1648 if (Meta.ParentPairs.Size() != 0)
1649 {
1650 s += "Parent:";
1651 s.Add_LF();
1652 FOR_VECTOR(i, Meta.ParentPairs)
1653 {
1654 const CParentPair &pair = Meta.ParentPairs[i];
1655 s += " ";
1656 s += pair.Key;
1657 s += ": ";
1658 s += pair.Value;
1659 s.Add_LF();
1660 }
1661 s.Add_LF();
1662 }
1663 }
1664
1665
1666
1667 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1668 {
1669 COM_TRY_BEGIN
1670 NCOM::CPropVariant prop;
1671 switch (propID)
1672 {
1673 case kpidMainSubfile: prop = (UInt32)0; break;
1674 case kpidClusterSize: prop = (UInt32)1 << Meta.BlockSize_Log; break;
1675 case kpidSectorSize: prop = (UInt32)1 << Meta.LogicalSectorSize_Log; break;
1676 case kpidShortComment:
1677 case kpidMethod:
1678 {
1679 AString s;
1680 AddTypeString(s);
1681 if (IsDiff())
1682 {
1683 s += " -> ";
1684 const CHandler *p = this;
1685 while (p && p->IsDiff())
1686 p = p->Parent;
1687 if (!p)
1688 s += '?';
1689 else
1690 p->AddTypeString(s);
1691 }
1692 prop = s;
1693 break;
1694 }
1695 case kpidComment:
1696 {
1697 UString s;
1698 {
1699 if (NumLevels > 1)
1700 {
1701 AddComment_UInt64(s, "NumVolumeLevels", NumLevels);
1702 AddComment_UInt64(s, "PackSizeTotal", PackSize_Total, true);
1703 s += "----";
1704 s.Add_LF();
1705 }
1706
1707 const CHandler *p = this;
1708 for (;;)
1709 {
1710 if (p->_level != 0 || p->Parent)
1711 AddComment_UInt64(s, "VolumeLevel", p->_level + 1);
1712 p->AddComment(s);
1713 if (!p->Parent)
1714 break;
1715 s += "----";
1716 s.Add_LF();
1717 {
1718 s.Add_LF();
1719 if (!p->ParentName_Used.IsEmpty())
1720 {
1721 AddComment_Name(s, "Name");
1722 s += p->ParentName_Used;
1723 s.Add_LF();
1724 }
1725 }
1726 p = p->Parent;
1727 }
1728 }
1729 prop = s;
1730 break;
1731 }
1732 case kpidCreatorApp:
1733 {
1734 if (!_creator.IsEmpty())
1735 prop = _creator;
1736 break;
1737 }
1738 case kpidId:
1739 {
1740 if (Meta.Guid_Defined)
1741 {
1742 UString s;
1743 Meta.Guid.AddHexToString(s);
1744 prop = s;
1745 }
1746 break;
1747 }
1748 case kpidName:
1749 {
1750 if (Meta.Guid_Defined)
1751 {
1752 UString s;
1753 Meta.Guid.AddHexToString(s);
1754 s += ".vhdx";
1755 prop = s;
1756 }
1757 break;
1758 }
1759 case kpidParent: if (IsDiff()) prop = GetParentSequence(); break;
1760 case kpidPhySize: prop = _phySize; break;
1761 case kpidTotalPhySize:
1762 {
1763 const CHandler *p = this;
1764 UInt64 sum = 0;
1765 do
1766 {
1767 sum += p->_phySize;
1768 p = p->Parent;
1769 }
1770 while (p);
1771 prop = sum;
1772 break;
1773 }
1774 case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break;
1775 case kpidError:
1776 {
1777 UString s;
1778 const CHandler *p = this;
1779 do
1780 {
1781 if (!p->_errorMessage.IsEmpty())
1782 {
1783 if (!s.IsEmpty())
1784 s.Add_LF();
1785 s += p->_errorMessage;
1786 }
1787 p = p->Parent;
1788 }
1789 while (p);
1790 if (!s.IsEmpty())
1791 prop = s;
1792 break;
1793 }
1794 }
1795 prop.Detach(value);
1796 return S_OK;
1797 COM_TRY_END
1798 }
1799
1800
1801 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback)
1802 {
1803 Stream = stream;
1804 if (_level >= (1 << 20))
1805 return S_FALSE;
1806
1807 RINOK(Open3())
1808
1809 NumLevels = 1;
1810 PackSize_Total = GetPackSize();
1811
1812 if (_child)
1813 {
1814 if (!_child->_parentGuid.IsEqualTo(Header.Guids[kHeader_GUID_Index_DataWriteGuid]))
1815 return S_FALSE;
1816 const CHandler *child = _child;
1817 do
1818 {
1819 /* We suppose that only FileWriteGuid is unique.
1820 Another IDs must be identical in in difference and parent archives. */
1821 if (Header.Guids[kHeader_GUID_Index_FileWriteGuid].IsEqualTo(
1822 child->Header.Guids[kHeader_GUID_Index_FileWriteGuid])
1823 && _phySize == child->_phySize)
1824 {
1825 _isCyclic = true;
1826 _isCyclic_or_CyclicParent = true;
1827 AddErrorMessage("Cyclic parent archive was blocked");
1828 return S_OK;
1829 }
1830 child = child->_child;
1831 }
1832 while (child);
1833 }
1834
1835 if (!Meta.Is_HasParent())
1836 return S_OK;
1837
1838 if (!Meta.Locator_Defined
1839 || !_parentGuid_IsDefined
1840 || ParentNames.IsEmpty())
1841 {
1842 return S_OK;
1843 }
1844
1845 ParentName_Used = ParentNames.Front();
1846
1847 HRESULT res;
1848 const unsigned kNumLevelsMax = (1 << 8); // Maybe we need to increase that limit
1849 if (_level >= kNumLevelsMax - 1)
1850 {
1851 AddErrorMessage("Too many parent levels");
1852 return S_OK;
1853 }
1854
1855 bool _parentFileWasOpen = false;
1856
1857 if (!openArchiveCallback)
1858 res = S_FALSE;
1859 else
1860 res = OpenParent(openArchiveCallback, _parentFileWasOpen);
1861
1862 if (res != S_OK)
1863 {
1864 if (res != S_FALSE)
1865 return res;
1866
1867 if (_parentFileWasOpen)
1868 AddErrorMessage("Can't parse parent VHDX file : ", ParentName_Used);
1869 else
1870 AddErrorMessage("Missing parent VHDX file : ", ParentName_Used);
1871 }
1872
1873
1874 return S_OK;
1875 }
1876
1877
1878 HRESULT CHandler::OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen)
1879 {
1880 _parentFileWasOpen = false;
1881 Z7_DECL_CMyComPtr_QI_FROM(
1882 IArchiveOpenVolumeCallback,
1883 openVolumeCallback, openArchiveCallback)
1884 if (!openVolumeCallback)
1885 return S_FALSE;
1886 {
1887 CMyComPtr<IInStream> nextStream;
1888 HRESULT res = S_FALSE;
1889 UString name;
1890
1891 FOR_VECTOR (i, ParentNames)
1892 {
1893 name = ParentNames[i];
1894
1895 // we remove prefix ".\\', but client already can support any variant
1896 if (name[0] == L'.' && name[1] == L'\\')
1897 name.DeleteFrontal(2);
1898
1899 res = openVolumeCallback->GetStream(name, &nextStream);
1900
1901 if (res == S_OK && nextStream)
1902 break;
1903
1904 if (res != S_OK && res != S_FALSE)
1905 return res;
1906 }
1907
1908 if (res == S_FALSE || !nextStream)
1909 return S_FALSE;
1910
1911 ParentName_Used = name;
1912 _parentFileWasOpen = true;
1913
1914 Parent = new CHandler;
1915 ParentStream = Parent;
1916
1917 try
1918 {
1919 Parent->_level = _level + 1;
1920 Parent->_child = this;
1921 /* we could call CHandlerImg::Open() here.
1922 but we don't need (_imgExt) in (Parent). So we call Open2() here */
1923 Parent->Close();
1924 res = Parent->Open2(nextStream, openArchiveCallback);
1925 }
1926 catch(...)
1927 {
1928 Parent = NULL;
1929 ParentStream.Release();
1930 res = S_FALSE;
1931 throw;
1932 }
1933
1934 if (res != S_OK)
1935 {
1936 Parent = NULL;
1937 ParentStream.Release();
1938 if (res == E_ABORT)
1939 return res;
1940 if (res != S_FALSE)
1941 {
1942 // we must show that error code
1943 }
1944 }
1945
1946 if (res == S_OK)
1947 {
1948 if (Parent->_isCyclic_or_CyclicParent)
1949 _isCyclic_or_CyclicParent = true;
1950
1951 NumLevels = Parent->NumLevels + 1;
1952 PackSize_Total += Parent->GetPackSize();
1953
1954 // we read BitMaps only if Parent was open
1955
1956 UInt64 numBytes = (UInt64)NumUsedBitMaps << kBitmapSize_Log;
1957 if (openArchiveCallback && numBytes != 0)
1958 {
1959 RINOK(openArchiveCallback->SetTotal(NULL, &numBytes))
1960 }
1961 numBytes = 0;
1962 for (size_t i = ChunkRatio; i < TotalBatEntries; i += ChunkRatio + 1)
1963 {
1964 const UInt64 v = Bat.GetItem(i);
1965 const UInt64 offset = BAT_GET_OFFSET(v);
1966 const unsigned state = BAT_GET_STATE(v);
1967
1968 CByteBuffer &buf = BitMaps.AddNew();
1969 if (state == SB_BLOCK_PRESENT)
1970 {
1971 if (openArchiveCallback)
1972 {
1973 RINOK(openArchiveCallback->SetCompleted(NULL, &numBytes))
1974 }
1975 numBytes += kBitmapSize;
1976 buf.Alloc(kBitmapSize);
1977 RINOK(Seek2(offset))
1978 RINOK(Read_FALSE(buf, kBitmapSize))
1979 /*
1980 for (unsigned i = 0; i < (1 << 20); i+=4)
1981 {
1982 UInt32 v = GetUi32(buf + i);
1983 if (v != 0 && v != (UInt32)(Int32)-1)
1984 printf("\n%7d %8x", i, v);
1985 }
1986 */
1987 }
1988 }
1989 }
1990 }
1991
1992 return S_OK;
1993 }
1994
1995
1996 void CHandler::CloseAtError()
1997 {
1998 // CHandlerImg
1999 Clear_HandlerImg_Vars();
2000 Stream.Release();
2001
2002 _phySize = 0;
2003 Bat.Clear();
2004 BitMaps.Clear();
2005 NumUsedBlocks = 0;
2006 NumUsedBitMaps = 0;
2007 HeadersSize = 0;
2008 /*
2009 NumUsed_1MB_Blocks = 0;
2010 NumUsed_1MB_Blocks_Defined = false;
2011 */
2012
2013 Parent = NULL;
2014 ParentStream.Release();
2015 _errorMessage.Empty();
2016 _creator.Empty();
2017 _nonEmptyLog = false;
2018 _parentGuid_IsDefined = false;
2019 _isDataContiguous = false;
2020 // _batOverlap = false;
2021
2022 ParentNames.Clear();
2023 ParentName_Used.Empty();
2024
2025 Meta.Clear();
2026
2027 ChunkRatio_Log = 0;
2028 ChunkRatio = 0;
2029 TotalBatEntries = 0;
2030 NumLevels = 0;
2031 PackSize_Total = 0;
2032
2033 _isCyclic = false;
2034 _isCyclic_or_CyclicParent = false;
2035 }
2036
2037 Z7_COM7F_IMF(CHandler::Close())
2038 {
2039 CloseAtError();
2040 return S_OK;
2041 }
2042
2043
2044 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value))
2045 {
2046 COM_TRY_BEGIN
2047 NCOM::CPropVariant prop;
2048
2049 switch (propID)
2050 {
2051 case kpidSize: prop = Meta.VirtualDiskSize; break;
2052 case kpidPackSize: prop = PackSize_Total; break;
2053 case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
2054 }
2055
2056 prop.Detach(value);
2057 return S_OK;
2058 COM_TRY_END
2059 }
2060
2061
2062 Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream))
2063 {
2064 COM_TRY_BEGIN
2065 *stream = NULL;
2066 // if some prarent is not OK, we don't create stream
2067 if (!AreParentsOK())
2068 return S_FALSE;
2069 InitSeekPositions();
2070 CMyComPtr<ISequentialInStream> streamTemp = this;
2071 *stream = streamTemp.Detach();
2072 return S_OK;
2073 COM_TRY_END
2074 }
2075
2076 REGISTER_ARC_I(
2077 "VHDX", "vhdx avhdx", NULL, 0xc4,
2078 kSignature,
2079 0,
2080 0,
2081 NULL)
2082
2083 }}
2084