// VhdxHandler.cpp

#include "StdAfx.h"

// #include <stdio.h>

#include "../../../C/CpuArch.h"

#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"
#include "../../Common/StringToInt.h"
#include "../../Common/MyBuffer.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_))

#if defined(__DOS__)
  #include "../../DOS/PropVariant.h"
  using namespace NDOS;
#else
  #include "../../Windows/PropVariant.h"
  using namespace NWindows;
#endif


EXTERN_C_BEGIN

// CRC-32C (Castagnoli) : reversed for poly 0x1EDC6F41
#define k_Crc32c_Poly 0x82f63b78

MY_ALIGN(64)
static UInt32 g_Crc32c_Table[256];

static void Z7_FASTCALL 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;
  }
}


#define CRC32C_INIT_VAL 0xFFFFFFFF

#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))

// UInt32 Z7_FASTCALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);
static UInt32 Z7_FASTCALL CrcUpdateT1_vhdx(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
  const Byte *p = (const Byte *)data;
  const Byte *pEnd = p + size;
  for (; p != pEnd; p++)
    v = CRC_UPDATE_BYTE_2(v, *p);
  return v;
}

static UInt32 Z7_FASTCALL Crc32c_Calc(const void *data, size_t size)
{
  return CrcUpdateT1_vhdx(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;

static const unsigned kSignatureSize = 8;
static const Byte kSignature[kSignatureSize] =
  { 'v', 'h', 'd', 'x', 'f', 'i', 'l', 'e' };

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;
}



Z7_FORCE_INLINE
static int DecodeFrom2HexChars(const wchar_t *s)
{
  unsigned v0 = (unsigned)s[0];  Z7_PARSE_HEX_DIGIT(v0, return -1;)
  unsigned v1 = (unsigned)s[1];  Z7_PARSE_HEX_DIGIT(v1, return -1;)
  return (int)((v0 << 4) | 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
{
  char temp[sizeof(Data) * 2 + 2];
  ConvertDataToHex_Lower(temp, Data, sizeof(Data));
  s += temp;
}


#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 IsEqualTo(const CHeader &h) const
  {
    if (SequenceNumber != h.SequenceNumber)
      return false;
    if (LogLength != h.LogLength)
      return false;
    if (LogOffset != h.LogOffset)
      return false;
    for (unsigned i = 0; i < 3; i++)
      if (!Guids[i].IsEqualTo(h.Guids[i]))
        return false;
    return true;
  }

  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 size_t kRegionSize = 1 << 16;
static const unsigned kNumRegionEntriesMax = (1 << 11) - 1;

bool CRegion::Parse(Byte *p)
{
  Bat_Defined = false;
  Meta_Defined = false;
  EndPos = 0;
  DataSize = 0;

  if (Get32(p) != 0x69676572) // "regi"
    return false;
  const UInt32 crc = Get32(p + 4);
  SetUi32(p + 4, 0)
  const UInt32 crc_calced = Crc32c_Calc(p, kRegionSize);
  if (crc_calced != crc)
    return false;
  
  const UInt32 EntryCount = Get32(p + 8);
  if (Get32(p + 12) != 0) // reserved field must be set to 0.
    return false;
  if (EntryCount > kNumRegionEntriesMax)
    return false;
  for (UInt32 i = 0; i < EntryCount; i++)
  {
    CRegionEntry e;
    const Byte *p2 = p + 0x10 + 0x20 * (size_t)i;
    if (!e.Parse(p2))
      return false;
    DataSize += e.Len;
    const UInt64 endPos = e.GetEndPos();
    if (EndPos < endPos)
      EndPos = endPos;
    CGuid Guid;
    Guid.SetFrom(p2);
    if (Guid.IsEqualTo(kBat))
    {
      if (Bat_Defined)
        return false;
      BatEntry = e;
      Bat_Defined = true;
    }
    else if (Guid.IsEqualTo(kMetadataRegion))
    {
      if (Meta_Defined)
        return false;
      MetaEntry = e;
      Meta_Defined = true;
    }
    else
    {
      if (e.Required != 0)
        return false;
      // it's allowed to ignore unknown non-required region entries
    }
  }
  /*
  const size_t k = 0x10 + 0x20 * EntryCount;
  return IsZeroArr(p + k, kRegionSize - k);
  */
  return true;
}




struct CMetaEntry
{
  CGuid Guid;
  UInt32 Offset;
  UInt32 Len;
  UInt32 Flags0;
  // UInt32 Flags1;

  bool IsUser()        const { return (Flags0 & 1) != 0; }
  bool IsVirtualDisk() const { return (Flags0 & 2) != 0; }
  bool IsRequired()    const { return (Flags0 & 4) != 0; }

  bool CheckLimit(size_t regionSize) const
  {
    return Offset <= regionSize && Len <= regionSize - Offset;
  }

  bool Parse(const Byte *p);
};


bool CMetaEntry::Parse(const Byte *p)
{
  Guid.SetFrom(p);
  
  G32(0x10, Offset);
  G32(0x14, Len);
  G32(0x18, Flags0);
  UInt32 Flags1;
  G32(0x1C, Flags1);

  if (Offset != 0 && Offset < (1 << 16))
    return false;
  if (Len > (1 << 20))
    return false;
  if (Len == 0 && Offset != 0)
    return false;
  if ((Flags0 >> 3) != 0) // Reserved
    return false;
  if ((Flags1 & 3) != 0) // Reserved2
    return false;
  return true;
}
  

struct CParentPair
{
  UString Key;
  UString Value;
};


struct CMetaHeader
{
  // UInt16 EntryCount;
  bool Guid_Defined;
  bool VirtualDiskSize_Defined;
  bool Locator_Defined;

  unsigned BlockSize_Log;
  unsigned LogicalSectorSize_Log;
  unsigned PhysicalSectorSize_Log;

  UInt32 Flags;
  UInt64 VirtualDiskSize;
  CGuid Guid;
  // CGuid LocatorType;

  CObjectVector<CParentPair> ParentPairs;

  int FindParentKey(const char *name) const
  {
    FOR_VECTOR (i, ParentPairs)
    {
      const CParentPair &pair = ParentPairs[i];
      if (pair.Key.IsEqualTo(name))
        return (int)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);
  }
};



Z7_class_CHandler_final: public CHandlerImg
{
  UInt64 _phySize;

  CBat Bat;
  CObjectVector<CByteBuffer> BitMaps;

  unsigned ChunkRatio_Log;
  size_t ChunkRatio;
  size_t TotalBatEntries;

  CMetaHeader Meta;
  CHeader Header;

  UInt32 NumUsedBlocks;
  UInt32 NumUsedBitMaps;
  UInt64 HeadersSize;

  UInt32 NumLevels;
  UInt64 PackSize_Total;

  /*
  UInt64 NumUsed_1MB_Blocks; // data and bitmaps
  bool NumUsed_1MB_Blocks_Defined;
  */

  CMyComPtr<IInStream> ParentStream;
  CHandler *Parent;
  UString _errorMessage;
  UString _creator;

  bool _nonEmptyLog;
  bool _isDataContiguous;
  // bool _batOverlap;

  CGuid _parentGuid;
  bool _parentGuid_IsDefined;
  UStringVector ParentNames;
  UString ParentName_Used;

  const CHandler *_child;
  unsigned _level;
  bool _isCyclic;
  bool _isCyclic_or_CyclicParent;

  void AddErrorMessage(const char *message);
  void AddErrorMessage(const char *message, const wchar_t *name);

  void UpdatePhySize(UInt64 value)
  {
    if (_phySize < value)
      _phySize = value;
  }

  HRESULT Seek2(UInt64 offset);
  HRESULT Read_FALSE(Byte *data, size_t size)
  {
    return ReadStream_FALSE(Stream, data, size);
  }
  HRESULT ReadToBuf_FALSE(CByteBuffer &buf, size_t size)
  {
    buf.Alloc(size);
    return ReadStream_FALSE(Stream, buf, size);
  }
  
  void InitSeekPositions();
  HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);

  bool IsDiff() const
  {
    // here we suppose that only HasParent() flag is mandatory for Diff archive type
    return Meta.Is_HasParent();
    // return _parentGuid_IsDefined;
  }

  void AddTypeString(AString &s) const
  {
    if (IsDiff())
      s += "Differencing";
    else
    {
      if (Meta.Is_LeaveBlockAllocated())
        s +=  _isDataContiguous ? "fixed" : "fixed-non-cont";
      else
        s += "dynamic";
    }
  }

  void AddComment(UString &s) const;

  UInt64 GetPackSize() const
  {
    return (UInt64)NumUsedBlocks << Meta.BlockSize_Log;
  }

  UString GetParentSequence() const
  {
    const CHandler *p = this;
    UString res;
    while (p && p->IsDiff())
    {
      if (!res.IsEmpty())
        res += " -> ";
      res += ParentName_Used;
      p = p->Parent;
    }
    return res;
  }

  bool AreParentsOK() const
  {
    if (_isCyclic_or_CyclicParent)
      return false;
    const CHandler *p = this;
    while (p->IsDiff())
    {
      p = p->Parent;
      if (!p)
        return false;
    }
    return true;
  }

  // bool ParseLog(CByteBuffer &log);
  bool ParseBat();
  bool CheckBat();

  HRESULT Open3();
  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override;
  HRESULT OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen);
  virtual void CloseAtError() Z7_override;

public:
  Z7_IFACE_COM7_IMP(IInArchive_Img)

  Z7_IFACE_COM7_IMP(IInArchiveGetStream)
  Z7_IFACE_COM7_IMP(ISequentialInStream)

  CHandler():
    _child(NULL),
    _level(0),
    _isCyclic(false),
    _isCyclic_or_CyclicParent(false)
    {}
};


HRESULT CHandler::Seek2(UInt64 offset)
{
  return InStream_SeekSet(Stream, offset);
}


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()
{
  {
    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
  {
    /* Disk2vhd v2.02 can create image with 2 full copies of headers.
       It's violation of VHDX specification:
          "A header is current if it is the only valid header
           or if it is valid and its SequenceNumber field is
           greater than the other header's SequenceNumber".
       but we support such Disk2vhd archives. */
    if (!headers[0].IsEqualTo(headers[1]))
      return S_FALSE;
    mainIndex = 0;
  }

  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 = (int)i;
      }
      else
      {
        AddErrorMessage("Incorrect region table");
      }
    }
    if (correctRegionIndex < 0)
      return S_FALSE;
    /*
    if (!regions[0].IsEqualTo(regions[1]))
      return S_FALSE;
    */
  }
  
  // UpdatePhySize((1 << 16) * 5);
  UpdatePhySize(1 << 20);
  
  {
    const CRegion &region = regions[correctRegionIndex];
    HeadersSize += region.DataSize;
    UpdatePhySize(region.EndPos);
    {
      if (!region.Meta_Defined)
        return S_FALSE;
      const CRegionEntry &e = region.MetaEntry;
      if (e.Len == 0)
        return S_FALSE;
      {
        // static const kMetaTableSize = 1 << 16;
        CByteBuffer temp;
        {
          RINOK(Seek2(e.Offset))
          RINOK(ReadToBuf_FALSE(temp, e.Len))
        }
        if (!Meta.Parse(temp, temp.Size()))
          return S_FALSE;
      }
      // UpdatePhySize(e.GetEndPos());
    }
    {
      if (!region.Bat_Defined)
        return S_FALSE;
      const CRegionEntry &e = region.BatEntry;
      if (e.Len == 0)
        return S_FALSE;
      // UpdatePhySize(e.GetEndPos());
      {
        RINOK(Seek2(e.Offset))
        RINOK(ReadToBuf_FALSE(Bat.Data, e.Len))
      }
      if (!ParseBat())
        return S_FALSE;
      if (!CheckBat())
      {
        AddErrorMessage("BAT overlap");
        // _batOverlap = true;
        // return S_FALSE;
      }
    }
  }

  {
    // do we need to check "parent_linkage2" also?
    FOR_VECTOR (i, Meta.ParentPairs)
    {
      const CParentPair &pair = Meta.ParentPairs[i];
      if (pair.Key.IsEqualTo("parent_linkage"))
      {
        _parentGuid_IsDefined = _parentGuid.ParseFromFormatedHexString(pair.Value);
        break;
      }
    }
  }

  {
    // absolute paths for parent stream can be rejected later in client callback
    // the order of check by specification:
    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 < Z7_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(InStream_SeekToBegin(Stream))

  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;
*/

Z7_COM7F_IMF(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(InStream_SeekSet(ParentStream, _virtPos))
      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.Add_Char(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, "VirtualDiskSize", Meta.VirtualDiskSize);
  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();
  }
}



Z7_COM7F_IMF(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;
  Z7_DECL_CMyComPtr_QI_FROM(
      IArchiveOpenVolumeCallback,
      openVolumeCallback, openArchiveCallback)
  if (!openVolumeCallback)
    return S_FALSE;
  {
    CMyComPtr<IInStream> nextStream;
    HRESULT res = S_FALSE;
    UString name;
    
    FOR_VECTOR (i, ParentNames)
    {
      name = ParentNames[i];
      
      // we remove prefix ".\\', but client already can support any variant
      if (name[0] == L'.' && name[1] == L'\\')
        name.DeleteFrontal(2);

      res = openVolumeCallback->GetStream(name, &nextStream);

      if (res == S_OK && nextStream)
        break;
         
      if (res != S_OK && res != S_FALSE)
        return res;
    }
    
    if (res == S_FALSE || !nextStream)
      return S_FALSE;
    
    ParentName_Used = name;
    _parentFileWasOpen = true;

    Parent = new CHandler;
    ParentStream = Parent;

    try
    {
      Parent->_level = _level + 1;
      Parent->_child = this;
      /* we could call CHandlerImg::Open() here.
         but we don't need (_imgExt) in (Parent). So we call Open2() here */
      Parent->Close();
      res = Parent->Open2(nextStream, openArchiveCallback);
    }
    catch(...)
    {
      Parent = NULL;
      ParentStream.Release();
      res = S_FALSE;
      throw;
    }

    if (res != S_OK)
    {
      Parent = NULL;
      ParentStream.Release();
      if (res == E_ABORT)
        return res;
      if (res != S_FALSE)
      {
        // we must show that error code
      }
    }

    if (res == S_OK)
    {
      if (Parent->_isCyclic_or_CyclicParent)
        _isCyclic_or_CyclicParent = true;

      NumLevels = Parent->NumLevels + 1;
      PackSize_Total += Parent->GetPackSize();

      // we read BitMaps only if Parent was open

      UInt64 numBytes = (UInt64)NumUsedBitMaps << kBitmapSize_Log;
      if (openArchiveCallback && numBytes != 0)
      {
        RINOK(openArchiveCallback->SetTotal(NULL, &numBytes))
      }
      numBytes = 0;
      for (size_t i = ChunkRatio; i < TotalBatEntries; i += ChunkRatio + 1)
      {
        const UInt64 v = Bat.GetItem(i);
        const UInt64 offset = BAT_GET_OFFSET(v);
        const unsigned state = BAT_GET_STATE(v);

        CByteBuffer &buf = BitMaps.AddNew();
        if (state == SB_BLOCK_PRESENT)
        {
          if (openArchiveCallback)
          {
            RINOK(openArchiveCallback->SetCompleted(NULL, &numBytes))
          }
          numBytes += kBitmapSize;
          buf.Alloc(kBitmapSize);
          RINOK(Seek2(offset))
          RINOK(Read_FALSE(buf, kBitmapSize))
          /*
          for (unsigned i = 0; i < (1 << 20); i+=4)
          {
            UInt32 v = GetUi32(buf + i);
            if (v != 0 && v != (UInt32)(Int32)-1)
              printf("\n%7d %8x", i, v);
          }
          */
        }
      }
    }
  }

  return S_OK;
}


void CHandler::CloseAtError()
{
  // CHandlerImg
  Clear_HandlerImg_Vars();
  Stream.Release();

  _phySize = 0;
  Bat.Clear();
  BitMaps.Clear();
  NumUsedBlocks = 0;
  NumUsedBitMaps = 0;
  HeadersSize = 0;
  /*
  NumUsed_1MB_Blocks = 0;
  NumUsed_1MB_Blocks_Defined = false;
  */

  Parent = NULL;
  ParentStream.Release();
  _errorMessage.Empty();
  _creator.Empty();
  _nonEmptyLog = false;
  _parentGuid_IsDefined = false;
  _isDataContiguous = false;
  // _batOverlap = false;

  ParentNames.Clear();
  ParentName_Used.Empty();

  Meta.Clear();

  ChunkRatio_Log = 0;
  ChunkRatio = 0;
  TotalBatEntries = 0;
  NumLevels = 0;
  PackSize_Total = 0;

  _isCyclic = false;
  _isCyclic_or_CyclicParent = false;
}

Z7_COM7F_IMF(CHandler::Close())
{
  CloseAtError();
  return S_OK;
}


Z7_COM7F_IMF(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
}


Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream))
{
  COM_TRY_BEGIN
  *stream = NULL;
  // if some prarent is not OK, we don't create stream
  if (!AreParentsOK())
    return S_FALSE;
  InitSeekPositions();
  CMyComPtr<ISequentialInStream> streamTemp = this;
  *stream = streamTemp.Detach();
  return S_OK;
  COM_TRY_END
}

REGISTER_ARC_I(
  "VHDX", "vhdx avhdx", NULL, 0xc4,
  kSignature,
  0,
  0,
  NULL)

}}
