xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/VhdHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // VhdHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/IntToString.h"
9 
10 #include "../../Windows/PropVariant.h"
11 
12 #include "../Common/LimitedStreams.h"
13 #include "../Common/RegisterArc.h"
14 #include "../Common/StreamUtils.h"
15 
16 #include "HandlerCont.h"
17 
18 #define Get16(p) GetBe16(p)
19 #define Get32(p) GetBe32(p)
20 #define Get64(p) GetBe64(p)
21 
22 #define G32(_offs_, dest) dest = Get32(p + (_offs_))
23 #define G64(_offs_, dest) dest = Get64(p + (_offs_))
24 
25 using namespace NWindows;
26 
27 namespace NArchive {
28 namespace NVhd {
29 
30 static const unsigned kSignatureSize = 10;
31 static const Byte kSignature[kSignatureSize] =
32   { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 };
33 
34 static const UInt32 kUnusedBlock = 0xFFFFFFFF;
35 
36 static const UInt32 kDiskType_Fixed = 2;
37 static const UInt32 kDiskType_Dynamic = 3;
38 static const UInt32 kDiskType_Diff = 4;
39 
40 static const char * const kDiskTypes[] =
41 {
42     "0"
43   , "1"
44   , "Fixed"
45   , "Dynamic"
46   , "Differencing"
47 };
48 
49 struct CFooter
50 {
51   // UInt32 Features;
52   // UInt32 FormatVersion;
53   UInt64 DataOffset;
54   UInt32 CTime;
55   UInt32 CreatorApp;
56   UInt32 CreatorVersion;
57   UInt32 CreatorHostOS;
58   // UInt64 OriginalSize;
59   UInt64 CurrentSize;
60   UInt32 DiskGeometry;
61   UInt32 Type;
62   Byte Id[16];
63   Byte SavedState;
64 
IsFixedNArchive::NVhd::CFooter65   bool IsFixed() const { return Type == kDiskType_Fixed; }
ThereIsDynamicNArchive::NVhd::CFooter66   bool ThereIsDynamic() const { return Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
67   // bool IsSupported() const { return Type == kDiskType_Fixed || Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
NumCylsNArchive::NVhd::CFooter68   UInt32 NumCyls() const { return DiskGeometry >> 16; }
NumHeadsNArchive::NVhd::CFooter69   UInt32 NumHeads() const { return (DiskGeometry >> 8) & 0xFF; }
NumSectorsPerTrackNArchive::NVhd::CFooter70   UInt32 NumSectorsPerTrack() const { return DiskGeometry & 0xFF; }
71   void AddTypeString(AString &s) const;
72   bool Parse(const Byte *p);
73 };
74 
AddTypeString(AString & s) const75 void CFooter::AddTypeString(AString &s) const
76 {
77   if (Type < Z7_ARRAY_SIZE(kDiskTypes))
78     s += kDiskTypes[Type];
79   else
80     s.Add_UInt32(Type);
81 }
82 
CheckBlock(const Byte * p,unsigned size,unsigned checkSumOffset,unsigned zeroOffset)83 static bool CheckBlock(const Byte *p, unsigned size, unsigned checkSumOffset, unsigned zeroOffset)
84 {
85   UInt32 sum = 0;
86   unsigned i;
87   for (i = 0; i < checkSumOffset; i++)
88     sum += p[i];
89   for (i = checkSumOffset + 4; i < size; i++)
90     sum += p[i];
91   if (~sum != Get32(p + checkSumOffset))
92     return false;
93   for (i = zeroOffset; i < size; i++)
94     if (p[i] != 0)
95       return false;
96   return true;
97 }
98 
99 static const unsigned kSectorSize_Log = 9;
100 static const unsigned kSectorSize = 1 << kSectorSize_Log;
101 static const unsigned kHeaderSize = 512;
102 
Parse(const Byte * p)103 bool CFooter::Parse(const Byte *p)
104 {
105   if (memcmp(p, kSignature, kSignatureSize) != 0)
106     return false;
107   // G32(0x08, Features);
108   // G32(0x0C, FormatVersion);
109   G64(0x10, DataOffset);
110   G32(0x18, CTime);
111   G32(0x1C, CreatorApp);
112   G32(0x20, CreatorVersion);
113   G32(0x24, CreatorHostOS);
114   // G64(0x28, OriginalSize);
115   G64(0x30, CurrentSize);
116   G32(0x38, DiskGeometry);
117   G32(0x3C, Type);
118   if (Type < kDiskType_Fixed ||
119       Type > kDiskType_Diff)
120     return false;
121   memcpy(Id, p + 0x44, 16);
122   SavedState = p[0x54];
123   // if (DataOffset > ((UInt64)1 << 62)) return false;
124   // if (CurrentSize > ((UInt64)1 << 62)) return false;
125   return CheckBlock(p, kHeaderSize, 0x40, 0x55);
126 }
127 
128 struct CParentLocatorEntry
129 {
130   UInt32 Code;
131   UInt32 DataSpace;
132   UInt32 DataLen;
133   UInt64 DataOffset;
134 
ParseNArchive::NVhd::CParentLocatorEntry135   bool Parse(const Byte *p)
136   {
137     G32(0x00, Code);
138     G32(0x04, DataSpace);
139     G32(0x08, DataLen);
140     G64(0x10, DataOffset);
141     return Get32(p + 0x0C) == 0; // Reserved
142   }
143 };
144 
145 struct CDynHeader
146 {
147   // UInt64 DataOffset;
148   UInt64 TableOffset;
149   // UInt32 HeaderVersion;
150   UInt32 NumBlocks;
151   unsigned BlockSizeLog;
152   UInt32 ParentTime;
153   Byte ParentId[16];
154   bool RelativeNameWasUsed;
155   UString ParentName;
156   UString RelativeParentNameFromLocator;
157   CParentLocatorEntry ParentLocators[8];
158 
159   bool Parse(const Byte *p);
NumBitMapSectorsNArchive::NVhd::CDynHeader160   UInt32 NumBitMapSectors() const
161   {
162     UInt32 numSectorsInBlock = (1 << (BlockSizeLog - kSectorSize_Log));
163     return (numSectorsInBlock + kSectorSize * 8 - 1) / (kSectorSize * 8);
164   }
ClearNArchive::NVhd::CDynHeader165   void Clear()
166   {
167     RelativeNameWasUsed = false;
168     ParentName.Empty();
169     RelativeParentNameFromLocator.Empty();
170   }
171 };
172 
Parse(const Byte * p)173 bool CDynHeader::Parse(const Byte *p)
174 {
175   if (memcmp(p, "cxsparse", 8) != 0)
176     return false;
177   // G64(0x08, DataOffset);
178   G64(0x10, TableOffset);
179   // G32(0x18, HeaderVersion);
180   G32(0x1C, NumBlocks);
181   {
182     UInt32 blockSize = Get32(p + 0x20);
183     unsigned i;
184     for (i = kSectorSize_Log;; i++)
185     {
186       if (i > 31)
187         return false;
188       if (((UInt32)1 << i) == blockSize)
189         break;
190     }
191     BlockSizeLog = i;
192   }
193   G32(0x38, ParentTime);
194   if (Get32(p + 0x3C) != 0) // reserved
195     return false;
196   memcpy(ParentId, p + 0x28, 16);
197   {
198     const unsigned kNameLen = 256;
199     wchar_t *s = ParentName.GetBuf(kNameLen);
200     unsigned i;
201     for (i = 0; i < kNameLen; i++)
202     {
203       wchar_t c = Get16(p + 0x40 + i * 2);
204       if (c == 0)
205         break;
206       s[i] = c;
207     }
208     s[i] = 0;
209     ParentName.ReleaseBuf_SetLen(i);
210   }
211   for (unsigned i = 0; i < 8; i++)
212     if (!ParentLocators[i].Parse(p + 0x240 + i * 24))
213       return false;
214   return CheckBlock(p, 1024, 0x24, 0x240 + 8 * 24);
215 }
216 
217 Z7_class_CHandler_final: public CHandlerImg
218 {
219   UInt64 _posInArcLimit;
220   UInt64 _startOffset;
221   UInt64 _phySize;
222 
223   CFooter Footer;
224   CDynHeader Dyn;
225   CRecordVector<UInt32> Bat;
226   CByteBuffer BitMap;
227   UInt32 BitMapTag;
228   UInt32 NumUsedBlocks;
229   CMyComPtr<IInStream> ParentStream;
230   CHandler *Parent;
231   UInt64 NumLevels;
232   UString _errorMessage;
233   // bool _unexpectedEnd;
234 
235   void AddErrorMessage(const char *message, const wchar_t *name = NULL)
236   {
237     if (!_errorMessage.IsEmpty())
238       _errorMessage.Add_LF();
239     _errorMessage += message;
240     if (name)
241       _errorMessage += name;
242   }
243 
244   void UpdatePhySize(UInt64 value)
245   {
246     if (_phySize < value)
247       _phySize = value;
248   }
249 
250   HRESULT Seek2(UInt64 offset);
251   HRESULT InitAndSeek();
252   HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size);
253 
254   bool NeedParent() const { return Footer.Type == kDiskType_Diff; }
255   UInt64 GetPackSize() const
256     { return Footer.ThereIsDynamic() ? ((UInt64)NumUsedBlocks << Dyn.BlockSizeLog) : Footer.CurrentSize; }
257 
258   UString GetParentSequence() const
259   {
260     const CHandler *p = this;
261     UString res;
262     while (p && p->NeedParent())
263     {
264       if (!res.IsEmpty())
265         res += " -> ";
266       UString mainName;
267       UString anotherName;
268       if (Dyn.RelativeNameWasUsed)
269       {
270         mainName = p->Dyn.RelativeParentNameFromLocator;
271         anotherName = p->Dyn.ParentName;
272       }
273       else
274       {
275         mainName = p->Dyn.ParentName;
276         anotherName = p->Dyn.RelativeParentNameFromLocator;
277       }
278       res += mainName;
279       if (mainName != anotherName && !anotherName.IsEmpty())
280       {
281         res.Add_Space();
282         res.Add_Char('(');
283         res += anotherName;
284         res.Add_Char(')');
285       }
286       p = p->Parent;
287     }
288     return res;
289   }
290 
291   bool AreParentsOK() const
292   {
293     const CHandler *p = this;
294     while (p->NeedParent())
295     {
296       p = p->Parent;
297       if (!p)
298         return false;
299     }
300     return true;
301   }
302 
303   HRESULT Open3();
304   HRESULT Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level);
305   HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override
306   {
307     return Open2(stream, NULL, openArchiveCallback, 0);
308   }
309   void CloseAtError() Z7_override;
310 
311 public:
312   Z7_IFACE_COM7_IMP(IInArchive_Img)
313 
314   Z7_IFACE_COM7_IMP(IInArchiveGetStream)
315   Z7_IFACE_COM7_IMP(ISequentialInStream)
316 };
317 
318 HRESULT CHandler::Seek2(UInt64 offset) { return InStream_SeekSet(Stream, _startOffset + offset); }
319 
320 HRESULT CHandler::InitAndSeek()
321 {
322   if (ParentStream)
323   {
324     RINOK(Parent->InitAndSeek())
325   }
326   _virtPos = _posInArc = 0;
327   BitMapTag = kUnusedBlock;
328   BitMap.Alloc(Dyn.NumBitMapSectors() << kSectorSize_Log);
329   return Seek2(0);
330 }
331 
332 HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size)
333 {
334   if (offset + size > _posInArcLimit)
335     return S_FALSE;
336   if (offset != _posInArc)
337   {
338     _posInArc = offset;
339     RINOK(Seek2(offset))
340   }
341   HRESULT res = ReadStream_FALSE(Stream, data, size);
342   if (res == S_OK)
343     _posInArc += size;
344   else
345     Reset_PosInArc();
346   return res;
347 }
348 
349 HRESULT CHandler::Open3()
350 {
351   // Fixed archive uses only footer
352 
353   UInt64 startPos;
354   RINOK(InStream_GetPos(Stream, startPos))
355   _startOffset = startPos;
356   Byte header[kHeaderSize];
357   RINOK(ReadStream_FALSE(Stream, header, kHeaderSize))
358   bool headerIsOK = Footer.Parse(header);
359   _size = Footer.CurrentSize;
360 
361   if (headerIsOK && !Footer.ThereIsDynamic())
362   {
363     // fixed archive
364     if (startPos < Footer.CurrentSize)
365       return S_FALSE;
366     _posInArcLimit = Footer.CurrentSize;
367     _phySize = Footer.CurrentSize + kHeaderSize;
368     _startOffset = startPos - Footer.CurrentSize;
369     _posInArc = _phySize;
370     return S_OK;
371   }
372 
373   UInt64 fileSize;
374   RINOK(InStream_GetSize_SeekToEnd(Stream, fileSize))
375   if (fileSize < kHeaderSize)
376     return S_FALSE;
377 
378   const UInt32 kDynSize = 1024;
379   Byte buf[kDynSize];
380 
381   RINOK(InStream_SeekSet(Stream, fileSize - kHeaderSize))
382   RINOK(ReadStream_FALSE(Stream, buf, kHeaderSize))
383 
384   if (!headerIsOK)
385   {
386     if (!Footer.Parse(buf))
387       return S_FALSE;
388     _size = Footer.CurrentSize;
389     if (Footer.ThereIsDynamic())
390       return S_FALSE; // we can't open Dynamic Archive backward.
391     // fixed archive
392     _posInArcLimit = Footer.CurrentSize;
393     _phySize = Footer.CurrentSize + kHeaderSize;
394     _startOffset = fileSize - kHeaderSize - Footer.CurrentSize;
395     _posInArc = _phySize;
396     return S_OK;
397   }
398 
399   _phySize = kHeaderSize;
400   _posInArc = fileSize - startPos;
401   _posInArcLimit = _posInArc - kHeaderSize;
402 
403   bool headerAndFooterAreEqual = false;
404   if (memcmp(header, buf, kHeaderSize) == 0)
405   {
406     headerAndFooterAreEqual = true;
407     _phySize = fileSize - _startOffset;
408   }
409 
410   RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize))
411   if (!Dyn.Parse(buf))
412     return S_FALSE;
413 
414   UpdatePhySize(Footer.DataOffset + kDynSize);
415 
416   for (int i = 0; i < 8; i++)
417   {
418     const CParentLocatorEntry &locator = Dyn.ParentLocators[i];
419     const UInt32 kNameBufSizeMax = 1024;
420     if (locator.DataLen < kNameBufSizeMax &&
421         locator.DataOffset < _posInArcLimit &&
422         locator.DataOffset + locator.DataLen <= _posInArcLimit)
423     {
424       if (locator.Code == 0x57327275 && (locator.DataLen & 1) == 0)
425       {
426         // "W2ru" locator
427         // Path is encoded as little-endian UTF-16
428         Byte nameBuf[kNameBufSizeMax];
429         UString tempString;
430         unsigned len = (locator.DataLen >> 1);
431         {
432           wchar_t *s = tempString.GetBuf(len);
433           RINOK(ReadPhy(locator.DataOffset, nameBuf, locator.DataLen))
434           unsigned j;
435           for (j = 0; j < len; j++)
436           {
437             wchar_t c = GetUi16(nameBuf + j * 2);
438             if (c == 0)
439               break;
440             s[j] = c;
441           }
442           s[j] = 0;
443           tempString.ReleaseBuf_SetLen(j);
444         }
445         if (tempString[0] == L'.' && tempString[1] == L'\\')
446           tempString.DeleteFrontal(2);
447         Dyn.RelativeParentNameFromLocator = tempString;
448       }
449     }
450     if (locator.DataLen != 0)
451       UpdatePhySize(locator.DataOffset + locator.DataLen);
452   }
453 
454   if (Dyn.NumBlocks >= (UInt32)1 << 31)
455     return S_FALSE;
456   if (Footer.CurrentSize == 0)
457   {
458     if (Dyn.NumBlocks != 0)
459       return S_FALSE;
460   }
461   else if (((Footer.CurrentSize - 1) >> Dyn.BlockSizeLog) + 1 != Dyn.NumBlocks)
462     return S_FALSE;
463 
464   Bat.ClearAndReserve(Dyn.NumBlocks);
465 
466   UInt32 bitmapSize = Dyn.NumBitMapSectors() << kSectorSize_Log;
467 
468   while ((UInt32)Bat.Size() < Dyn.NumBlocks)
469   {
470     RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, kSectorSize))
471     UpdatePhySize(Dyn.TableOffset + kSectorSize);
472     for (UInt32 j = 0; j < kSectorSize; j += 4)
473     {
474       UInt32 v = Get32(buf + j);
475       if (v != kUnusedBlock)
476       {
477         UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog;
478         UpdatePhySize(((UInt64)v << kSectorSize_Log) + bitmapSize + blockSize);
479         NumUsedBlocks++;
480       }
481       Bat.AddInReserved(v);
482       if ((UInt32)Bat.Size() >= Dyn.NumBlocks)
483         break;
484     }
485   }
486 
487   if (headerAndFooterAreEqual)
488     return S_OK;
489 
490   if (_startOffset + _phySize + kHeaderSize > fileSize)
491   {
492     // _unexpectedEnd = true;
493     _posInArcLimit = _phySize;
494     _phySize += kHeaderSize;
495     return S_OK;
496   }
497 
498   RINOK(ReadPhy(_phySize, buf, kHeaderSize))
499   if (memcmp(header, buf, kHeaderSize) == 0)
500   {
501     _posInArcLimit = _phySize;
502     _phySize += kHeaderSize;
503     return S_OK;
504   }
505 
506   if (_phySize == 0x800)
507   {
508     /* WHY does empty archive contain additional empty sector?
509        We skip that sector and check footer again. */
510     unsigned i;
511     for (i = 0; i < kSectorSize && buf[i] == 0; i++);
512     if (i == kSectorSize)
513     {
514       RINOK(ReadPhy(_phySize + kSectorSize, buf, kHeaderSize))
515       if (memcmp(header, buf, kHeaderSize) == 0)
516       {
517         _phySize += kSectorSize;
518         _posInArcLimit = _phySize;
519         _phySize += kHeaderSize;
520         return S_OK;
521       }
522     }
523   }
524   _posInArcLimit = _phySize;
525   _phySize += kHeaderSize;
526   AddErrorMessage("Can't find footer");
527   return S_OK;
528 }
529 
530 Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize))
531 {
532   if (processedSize)
533     *processedSize = 0;
534   if (_virtPos >= Footer.CurrentSize)
535     return S_OK;
536   {
537     const UInt64 rem = Footer.CurrentSize - _virtPos;
538     if (size > rem)
539       size = (UInt32)rem;
540   }
541   if (size == 0)
542     return S_OK;
543 
544   if (Footer.IsFixed())
545   {
546     if (_virtPos > _posInArcLimit)
547       return S_FALSE;
548     {
549       const UInt64 rem = _posInArcLimit - _virtPos;
550       if (size > rem)
551         size = (UInt32)rem;
552     }
553     HRESULT res = S_OK;
554     if (_virtPos != _posInArc)
555     {
556       _posInArc = _virtPos;
557       res = Seek2(_virtPos);
558     }
559     if (res == S_OK)
560     {
561       UInt32 processedSize2 = 0;
562       res = Stream->Read(data, size, &processedSize2);
563       if (processedSize)
564         *processedSize = processedSize2;
565       _posInArc += processedSize2;
566     }
567     if (res != S_OK)
568       Reset_PosInArc();
569     return res;
570   }
571 
572   const UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog);
573   if (blockIndex >= Bat.Size())
574     return E_FAIL; // it's some unexpected case
575   const UInt32 blockSectIndex = Bat[blockIndex];
576   const UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog;
577   UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
578   size = MyMin(blockSize - offsetInBlock, size);
579 
580   HRESULT res = S_OK;
581   if (blockSectIndex == kUnusedBlock)
582   {
583     if (ParentStream)
584     {
585       RINOK(InStream_SeekSet(ParentStream, _virtPos))
586       res = ParentStream->Read(data, size, &size);
587     }
588     else
589       memset(data, 0, size);
590   }
591   else
592   {
593     const UInt64 newPos = (UInt64)blockSectIndex << kSectorSize_Log;
594     if (BitMapTag != blockIndex)
595     {
596       RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.Size()))
597       BitMapTag = blockIndex;
598     }
599     RINOK(ReadPhy(newPos + BitMap.Size() + offsetInBlock, data, size))
600     for (UInt32 cur = 0; cur < size;)
601     {
602       const UInt32 rem = MyMin(0x200 - (offsetInBlock & 0x1FF), size - cur);
603       const UInt32 bmi = offsetInBlock >> kSectorSize_Log;
604       if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0)
605       {
606         if (ParentStream)
607         {
608           RINOK(InStream_SeekSet(ParentStream, _virtPos + cur))
609           RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem))
610         }
611         else
612         {
613           const Byte *p = (const Byte *)data + cur;
614           for (UInt32 i = 0; i < rem; i++)
615             if (p[i] != 0)
616               return S_FALSE;
617         }
618       }
619       offsetInBlock += rem;
620       cur += rem;
621     }
622   }
623   if (processedSize)
624     *processedSize = size;
625   _virtPos += size;
626   return res;
627 }
628 
629 
630 enum
631 {
632   kpidParent = kpidUserDefined,
633   kpidSavedState
634 };
635 
636 static const CStatProp kArcProps[] =
637 {
638   { NULL, kpidOffset, VT_UI8},
639   { NULL, kpidCTime, VT_FILETIME},
640   { NULL, kpidClusterSize, VT_UI8},
641   { NULL, kpidMethod, VT_BSTR},
642   { NULL, kpidNumVolumes, VT_UI4},
643   { NULL, kpidTotalPhySize, VT_UI8},
644   { "Parent", kpidParent, VT_BSTR},
645   { NULL, kpidCreatorApp, VT_BSTR},
646   { NULL, kpidHostOS, VT_BSTR},
647   { "Saved State", kpidSavedState, VT_BOOL},
648   { NULL, kpidId, VT_BSTR}
649  };
650 
651 static const Byte kProps[] =
652 {
653   kpidSize,
654   kpidPackSize,
655   kpidCTime
656 
657   /*
658   { kpidNumCyls, VT_UI4},
659   { kpidNumHeads, VT_UI4},
660   { kpidSectorsPerTrack, VT_UI4}
661   */
662 };
663 
664 IMP_IInArchive_Props
665 IMP_IInArchive_ArcProps_WITH_NAME
666 
667 // VHD start time: 2000-01-01
668 static const UInt64 kVhdTimeStartValue = (UInt64)3600 * 24 * (399 * 365 + 24 * 4);
669 
670 static void VhdTimeToFileTime(UInt32 vhdTime, NCOM::CPropVariant &prop)
671 {
672   FILETIME ft, utc;
673   UInt64 v = (kVhdTimeStartValue + vhdTime) * 10000000;
674   ft.dwLowDateTime = (DWORD)v;
675   ft.dwHighDateTime = (DWORD)(v >> 32);
676   // specification says that it's UTC time, but Virtual PC 6 writes local time. Why?
677   LocalFileTimeToFileTime(&ft, &utc);
678   prop = utc;
679 }
680 
681 static void StringToAString(char *dest, UInt32 val)
682 {
683   for (int i = 24; i >= 0; i -= 8)
684   {
685     const Byte b = (Byte)((val >> i) & 0xFF);
686     if (b < 0x20 || b > 0x7F)
687       break;
688     *dest++ = (char)b;
689   }
690   *dest = 0;
691 }
692 
693 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
694 {
695   COM_TRY_BEGIN
696   NCOM::CPropVariant prop;
697   switch (propID)
698   {
699     case kpidMainSubfile: prop = (UInt32)0; break;
700     case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
701     case kpidClusterSize: if (Footer.ThereIsDynamic()) prop = (UInt32)1 << Dyn.BlockSizeLog; break;
702     case kpidShortComment:
703     case kpidMethod:
704     {
705       AString s;
706       Footer.AddTypeString(s);
707       if (NeedParent())
708       {
709         s += " -> ";
710         const CHandler *p = this;
711         while (p && p->NeedParent())
712           p = p->Parent;
713         if (!p)
714           s += '?';
715         else
716           p->Footer.AddTypeString(s);
717       }
718       prop = s;
719       break;
720     }
721     case kpidCreatorApp:
722     {
723       char s[16];
724       StringToAString(s, Footer.CreatorApp);
725       AString res (s);
726       res.Trim();
727       res.Add_Space();
728       res.Add_UInt32(Footer.CreatorVersion >> 16);
729       res.Add_Dot();
730       res.Add_UInt32(Footer.CreatorVersion & 0xFFFF);
731       prop = res;
732       break;
733     }
734     case kpidHostOS:
735     {
736       if (Footer.CreatorHostOS == 0x5769326B)
737         prop = "Windows";
738       else
739       {
740         char s[16];
741         StringToAString(s, Footer.CreatorHostOS);
742         prop = s;
743       }
744       break;
745     }
746     case kpidId:
747     {
748       char s[sizeof(Footer.Id) * 2 + 2];
749       ConvertDataToHex_Upper(s, Footer.Id, sizeof(Footer.Id));
750       prop = s;
751       break;
752     }
753     case kpidSavedState: prop = Footer.SavedState ? true : false; break;
754     case kpidParent: if (NeedParent()) prop = GetParentSequence(); break;
755     case kpidOffset: prop = _startOffset; break;
756     case kpidPhySize: prop = _phySize; break;
757     case kpidTotalPhySize:
758     {
759       const CHandler *p = this;
760       UInt64 sum = 0;
761       do
762       {
763         sum += p->_phySize;
764         p = p->Parent;
765       }
766       while (p);
767       prop = sum;
768       break;
769     }
770     case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break;
771 
772     /*
773     case kpidErrorFlags:
774     {
775       UInt32 flags = 0;
776       if (_unexpectedEnd)
777         flags |= kpv_ErrorFlags_UnexpectedEndOfArc;
778       if (flags != 0)
779         prop = flags;
780       break;
781     }
782     */
783     case kpidError: if (!_errorMessage.IsEmpty()) prop = _errorMessage; break;
784   }
785   prop.Detach(value);
786   return S_OK;
787   COM_TRY_END
788 }
789 
790 
791 HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level)
792 {
793   Close();
794   Stream = stream;
795   if (level > (1 << 12)) // Maybe we need to increase that limit
796     return S_FALSE;
797 
798   RINOK(Open3())
799 
800   NumLevels = 1;
801   if (child && memcmp(child->Dyn.ParentId, Footer.Id, 16) != 0)
802     return S_FALSE;
803   if (Footer.Type != kDiskType_Diff)
804     return S_OK;
805 
806   bool useRelative;
807   UString name;
808 
809   if (!Dyn.RelativeParentNameFromLocator.IsEmpty())
810   {
811     useRelative = true;
812     name = Dyn.RelativeParentNameFromLocator;
813   }
814   else
815   {
816     useRelative = false;
817     name = Dyn.ParentName;
818   }
819 
820   Dyn.RelativeNameWasUsed = useRelative;
821 
822   Z7_DECL_CMyComPtr_QI_FROM(
823       IArchiveOpenVolumeCallback,
824       openVolumeCallback, openArchiveCallback)
825 
826   if (openVolumeCallback)
827   {
828     CMyComPtr<IInStream> nextStream;
829     HRESULT res = openVolumeCallback->GetStream(name, &nextStream);
830 
831     if (res == S_FALSE)
832     {
833       if (useRelative && Dyn.ParentName != Dyn.RelativeParentNameFromLocator)
834       {
835         res = openVolumeCallback->GetStream(Dyn.ParentName, &nextStream);
836         if (res == S_OK)
837           Dyn.RelativeNameWasUsed = false;
838       }
839     }
840 
841     if (res != S_OK && res != S_FALSE)
842       return res;
843 
844     if (res == S_FALSE || !nextStream)
845     {
846       AddErrorMessage("Missing volume : ", name);
847       return S_OK;
848     }
849 
850     Parent = new CHandler;
851     ParentStream = Parent;
852 
853     res = Parent->Open2(nextStream, this, openArchiveCallback, level + 1);
854 
855     if (res != S_OK)
856     {
857       Parent = NULL;
858       ParentStream.Release();
859       if (res == E_ABORT)
860         return res;
861       if (res != S_FALSE)
862       {
863         // we must show that error code
864       }
865     }
866     if (res == S_OK)
867     {
868       NumLevels = Parent->NumLevels + 1;
869     }
870   }
871   {
872     const CHandler *p = this;
873     while (p->NeedParent())
874     {
875       p = p->Parent;
876       if (!p)
877       {
878         AddErrorMessage("Can't open parent VHD file : ", Dyn.ParentName);
879         break;
880       }
881     }
882   }
883   return S_OK;
884 }
885 
886 
887 void CHandler::CloseAtError()
888 {
889   // CHandlerImg:
890   Stream.Release();
891   Clear_HandlerImg_Vars();
892 
893   _phySize = 0;
894   NumLevels = 0;
895   Bat.Clear();
896   NumUsedBlocks = 0;
897   Parent = NULL;
898   ParentStream.Release();
899   Dyn.Clear();
900   _errorMessage.Empty();
901   // _unexpectedEnd = false;
902 }
903 
904 Z7_COM7F_IMF(CHandler::Close())
905 {
906   CloseAtError();
907   return S_OK;
908 }
909 
910 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value))
911 {
912   COM_TRY_BEGIN
913   NCOM::CPropVariant prop;
914 
915   switch (propID)
916   {
917     case kpidSize: prop = Footer.CurrentSize; break;
918     case kpidPackSize: prop = GetPackSize(); break;
919     case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
920     case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
921 
922     /*
923     case kpidNumCyls: prop = Footer.NumCyls(); break;
924     case kpidNumHeads: prop = Footer.NumHeads(); break;
925     case kpidSectorsPerTrack: prop = Footer.NumSectorsPerTrack(); break;
926     */
927   }
928 
929   prop.Detach(value);
930   return S_OK;
931   COM_TRY_END
932 }
933 
934 
935 Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream))
936 {
937   COM_TRY_BEGIN
938   *stream = NULL;
939   if (Footer.IsFixed())
940   {
941     CMyComPtr2<ISequentialInStream, CLimitedInStream> streamSpec;
942     streamSpec.Create_if_Empty();
943     streamSpec->SetStream(Stream);
944     // fixme : check (startOffset = 0)
945     streamSpec->InitAndSeek(_startOffset, Footer.CurrentSize);
946     RINOK(streamSpec->SeekToStart())
947     *stream = streamSpec.Detach();
948     return S_OK;
949   }
950   if (!Footer.ThereIsDynamic() || !AreParentsOK())
951     return S_FALSE;
952   CMyComPtr<ISequentialInStream> streamTemp = this;
953   RINOK(InitAndSeek())
954   *stream = streamTemp.Detach();
955   return S_OK;
956   COM_TRY_END
957 }
958 
959 REGISTER_ARC_I(
960   "VHD", "vhd", NULL, 0xDC,
961   kSignature,
962   0,
963   NArcInfoFlags::kUseGlobalOffset,
964   NULL)
965 
966 }}
967