// VhdHandler.cpp

#include "StdAfx.h"

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

#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"

#include "../../Windows/PropVariant.h"

#include "../Common/LimitedStreams.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"

#include "HandlerCont.h"

#define Get16(p) GetBe16(p)
#define Get32(p) GetBe32(p)
#define Get64(p) GetBe64(p)

#define G32(_offs_, dest) dest = Get32(p + (_offs_))
#define G64(_offs_, dest) dest = Get64(p + (_offs_))

using namespace NWindows;

namespace NArchive {
namespace NVhd {

static const unsigned kSignatureSize = 10;
static const Byte kSignature[kSignatureSize] =
  { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 };

static const UInt32 kUnusedBlock = 0xFFFFFFFF;

static const UInt32 kDiskType_Fixed = 2;
static const UInt32 kDiskType_Dynamic = 3;
static const UInt32 kDiskType_Diff = 4;

static const char * const kDiskTypes[] =
{
    "0"
  , "1"
  , "Fixed"
  , "Dynamic"
  , "Differencing"
};

struct CFooter
{
  // UInt32 Features;
  // UInt32 FormatVersion;
  UInt64 DataOffset;
  UInt32 CTime;
  UInt32 CreatorApp;
  UInt32 CreatorVersion;
  UInt32 CreatorHostOS;
  // UInt64 OriginalSize;
  UInt64 CurrentSize;
  UInt32 DiskGeometry;
  UInt32 Type;
  Byte Id[16];
  Byte SavedState;

  bool IsFixed() const { return Type == kDiskType_Fixed; }
  bool ThereIsDynamic() const { return Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
  // bool IsSupported() const { return Type == kDiskType_Fixed || Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
  UInt32 NumCyls() const { return DiskGeometry >> 16; }
  UInt32 NumHeads() const { return (DiskGeometry >> 8) & 0xFF; }
  UInt32 NumSectorsPerTrack() const { return DiskGeometry & 0xFF; }
  void AddTypeString(AString &s) const;
  bool Parse(const Byte *p);
};

void CFooter::AddTypeString(AString &s) const
{
  if (Type < Z7_ARRAY_SIZE(kDiskTypes))
    s += kDiskTypes[Type];
  else
    s.Add_UInt32(Type);
}

static bool CheckBlock(const Byte *p, unsigned size, unsigned checkSumOffset, unsigned zeroOffset)
{
  UInt32 sum = 0;
  unsigned i;
  for (i = 0; i < checkSumOffset; i++)
    sum += p[i];
  for (i = checkSumOffset + 4; i < size; i++)
    sum += p[i];
  if (~sum != Get32(p + checkSumOffset))
    return false;
  for (i = zeroOffset; i < size; i++)
    if (p[i] != 0)
      return false;
  return true;
}

static const unsigned kSectorSize_Log = 9;
static const unsigned kSectorSize = 1 << kSectorSize_Log;
static const unsigned kHeaderSize = 512;

bool CFooter::Parse(const Byte *p)
{
  if (memcmp(p, kSignature, kSignatureSize) != 0)
    return false;
  // G32(0x08, Features);
  // G32(0x0C, FormatVersion);
  G64(0x10, DataOffset);
  G32(0x18, CTime);
  G32(0x1C, CreatorApp);
  G32(0x20, CreatorVersion);
  G32(0x24, CreatorHostOS);
  // G64(0x28, OriginalSize);
  G64(0x30, CurrentSize);
  G32(0x38, DiskGeometry);
  G32(0x3C, Type);
  if (Type < kDiskType_Fixed ||
      Type > kDiskType_Diff)
    return false;
  memcpy(Id, p + 0x44, 16);
  SavedState = p[0x54];
  // if (DataOffset > ((UInt64)1 << 62)) return false;
  // if (CurrentSize > ((UInt64)1 << 62)) return false;
  return CheckBlock(p, kHeaderSize, 0x40, 0x55);
}

struct CParentLocatorEntry
{
  UInt32 Code;
  UInt32 DataSpace;
  UInt32 DataLen;
  UInt64 DataOffset;

  bool Parse(const Byte *p)
  {
    G32(0x00, Code);
    G32(0x04, DataSpace);
    G32(0x08, DataLen);
    G64(0x10, DataOffset);
    return Get32(p + 0x0C) == 0; // Reserved
  }
};

struct CDynHeader
{
  // UInt64 DataOffset;
  UInt64 TableOffset;
  // UInt32 HeaderVersion;
  UInt32 NumBlocks;
  unsigned BlockSizeLog;
  UInt32 ParentTime;
  Byte ParentId[16];
  bool RelativeNameWasUsed;
  UString ParentName;
  UString RelativeParentNameFromLocator;
  CParentLocatorEntry ParentLocators[8];

  bool Parse(const Byte *p);
  UInt32 NumBitMapSectors() const
  {
    UInt32 numSectorsInBlock = (1 << (BlockSizeLog - kSectorSize_Log));
    return (numSectorsInBlock + kSectorSize * 8 - 1) / (kSectorSize * 8);
  }
  void Clear()
  {
    RelativeNameWasUsed = false;
    ParentName.Empty();
    RelativeParentNameFromLocator.Empty();
  }
};

bool CDynHeader::Parse(const Byte *p)
{
  if (memcmp(p, "cxsparse", 8) != 0)
    return false;
  // G64(0x08, DataOffset);
  G64(0x10, TableOffset);
  // G32(0x18, HeaderVersion);
  G32(0x1C, NumBlocks);
  {
    UInt32 blockSize = Get32(p + 0x20);
    unsigned i;
    for (i = kSectorSize_Log;; i++)
    {
      if (i > 31)
        return false;
      if (((UInt32)1 << i) == blockSize)
        break;
    }
    BlockSizeLog = i;
  }
  G32(0x38, ParentTime);
  if (Get32(p + 0x3C) != 0) // reserved
    return false;
  memcpy(ParentId, p + 0x28, 16);
  {
    const unsigned kNameLen = 256;
    wchar_t *s = ParentName.GetBuf(kNameLen);
    unsigned i;
    for (i = 0; i < kNameLen; i++)
    {
      wchar_t c = Get16(p + 0x40 + i * 2);
      if (c == 0)
        break;
      s[i] = c;
    }
    s[i] = 0;
    ParentName.ReleaseBuf_SetLen(i);
  }
  for (unsigned i = 0; i < 8; i++)
    if (!ParentLocators[i].Parse(p + 0x240 + i * 24))
      return false;
  return CheckBlock(p, 1024, 0x24, 0x240 + 8 * 24);
}

Z7_class_CHandler_final: public CHandlerImg
{
  UInt64 _posInArcLimit;
  UInt64 _startOffset;
  UInt64 _phySize;

  CFooter Footer;
  CDynHeader Dyn;
  CRecordVector<UInt32> Bat;
  CByteBuffer BitMap;
  UInt32 BitMapTag;
  UInt32 NumUsedBlocks;
  CMyComPtr<IInStream> ParentStream;
  CHandler *Parent;
  UInt64 NumLevels;
  UString _errorMessage;
  // bool _unexpectedEnd;

  void AddErrorMessage(const char *message, const wchar_t *name = NULL)
  {
    if (!_errorMessage.IsEmpty())
      _errorMessage.Add_LF();
    _errorMessage += message;
    if (name)
      _errorMessage += name;
  }

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

  HRESULT Seek2(UInt64 offset);
  HRESULT InitAndSeek();
  HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size);

  bool NeedParent() const { return Footer.Type == kDiskType_Diff; }
  UInt64 GetPackSize() const
    { return Footer.ThereIsDynamic() ? ((UInt64)NumUsedBlocks << Dyn.BlockSizeLog) : Footer.CurrentSize; }

  UString GetParentSequence() const
  {
    const CHandler *p = this;
    UString res;
    while (p && p->NeedParent())
    {
      if (!res.IsEmpty())
        res += " -> ";
      UString mainName;
      UString anotherName;
      if (Dyn.RelativeNameWasUsed)
      {
        mainName = p->Dyn.RelativeParentNameFromLocator;
        anotherName = p->Dyn.ParentName;
      }
      else
      {
        mainName = p->Dyn.ParentName;
        anotherName = p->Dyn.RelativeParentNameFromLocator;
      }
      res += mainName;
      if (mainName != anotherName && !anotherName.IsEmpty())
      {
        res.Add_Space();
        res.Add_Char('(');
        res += anotherName;
        res.Add_Char(')');
      }
      p = p->Parent;
    }
    return res;
  }

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

  HRESULT Open3();
  HRESULT Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level);
  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override
  {
    return Open2(stream, NULL, openArchiveCallback, 0);
  }
  void CloseAtError() Z7_override;

public:
  Z7_IFACE_COM7_IMP(IInArchive_Img)

  Z7_IFACE_COM7_IMP(IInArchiveGetStream)
  Z7_IFACE_COM7_IMP(ISequentialInStream)
};

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

HRESULT CHandler::InitAndSeek()
{
  if (ParentStream)
  {
    RINOK(Parent->InitAndSeek())
  }
  _virtPos = _posInArc = 0;
  BitMapTag = kUnusedBlock;
  BitMap.Alloc(Dyn.NumBitMapSectors() << kSectorSize_Log);
  return Seek2(0);
}

HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size)
{
  if (offset + size > _posInArcLimit)
    return S_FALSE;
  if (offset != _posInArc)
  {
    _posInArc = offset;
    RINOK(Seek2(offset))
  }
  HRESULT res = ReadStream_FALSE(Stream, data, size);
  if (res == S_OK)
    _posInArc += size;
  else
    Reset_PosInArc();
  return res;
}

HRESULT CHandler::Open3()
{
  // Fixed archive uses only footer

  UInt64 startPos;
  RINOK(InStream_GetPos(Stream, startPos))
  _startOffset = startPos;
  Byte header[kHeaderSize];
  RINOK(ReadStream_FALSE(Stream, header, kHeaderSize))
  bool headerIsOK = Footer.Parse(header);
  _size = Footer.CurrentSize;

  if (headerIsOK && !Footer.ThereIsDynamic())
  {
    // fixed archive
    if (startPos < Footer.CurrentSize)
      return S_FALSE;
    _posInArcLimit = Footer.CurrentSize;
    _phySize = Footer.CurrentSize + kHeaderSize;
    _startOffset = startPos - Footer.CurrentSize;
    _posInArc = _phySize;
    return S_OK;
  }

  UInt64 fileSize;
  RINOK(InStream_GetSize_SeekToEnd(Stream, fileSize))
  if (fileSize < kHeaderSize)
    return S_FALSE;

  const UInt32 kDynSize = 1024;
  Byte buf[kDynSize];

  RINOK(InStream_SeekSet(Stream, fileSize - kHeaderSize))
  RINOK(ReadStream_FALSE(Stream, buf, kHeaderSize))

  if (!headerIsOK)
  {
    if (!Footer.Parse(buf))
      return S_FALSE;
    _size = Footer.CurrentSize;
    if (Footer.ThereIsDynamic())
      return S_FALSE; // we can't open Dynamic Archive backward.
    // fixed archive
    _posInArcLimit = Footer.CurrentSize;
    _phySize = Footer.CurrentSize + kHeaderSize;
    _startOffset = fileSize - kHeaderSize - Footer.CurrentSize;
    _posInArc = _phySize;
    return S_OK;
  }

  _phySize = kHeaderSize;
  _posInArc = fileSize - startPos;
  _posInArcLimit = _posInArc - kHeaderSize;

  bool headerAndFooterAreEqual = false;
  if (memcmp(header, buf, kHeaderSize) == 0)
  {
    headerAndFooterAreEqual = true;
    _phySize = fileSize - _startOffset;
  }

  RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize))
  if (!Dyn.Parse(buf))
    return S_FALSE;

  UpdatePhySize(Footer.DataOffset + kDynSize);

  for (int i = 0; i < 8; i++)
  {
    const CParentLocatorEntry &locator = Dyn.ParentLocators[i];
    const UInt32 kNameBufSizeMax = 1024;
    if (locator.DataLen < kNameBufSizeMax &&
        locator.DataOffset < _posInArcLimit &&
        locator.DataOffset + locator.DataLen <= _posInArcLimit)
    {
      if (locator.Code == 0x57327275 && (locator.DataLen & 1) == 0)
      {
        // "W2ru" locator
        // Path is encoded as little-endian UTF-16
        Byte nameBuf[kNameBufSizeMax];
        UString tempString;
        unsigned len = (locator.DataLen >> 1);
        {
          wchar_t *s = tempString.GetBuf(len);
          RINOK(ReadPhy(locator.DataOffset, nameBuf, locator.DataLen))
          unsigned j;
          for (j = 0; j < len; j++)
          {
            wchar_t c = GetUi16(nameBuf + j * 2);
            if (c == 0)
              break;
            s[j] = c;
          }
          s[j] = 0;
          tempString.ReleaseBuf_SetLen(j);
        }
        if (tempString[0] == L'.' && tempString[1] == L'\\')
          tempString.DeleteFrontal(2);
        Dyn.RelativeParentNameFromLocator = tempString;
      }
    }
    if (locator.DataLen != 0)
      UpdatePhySize(locator.DataOffset + locator.DataLen);
  }
  
  if (Dyn.NumBlocks >= (UInt32)1 << 31)
    return S_FALSE;
  if (Footer.CurrentSize == 0)
  {
    if (Dyn.NumBlocks != 0)
      return S_FALSE;
  }
  else if (((Footer.CurrentSize - 1) >> Dyn.BlockSizeLog) + 1 != Dyn.NumBlocks)
    return S_FALSE;

  Bat.ClearAndReserve(Dyn.NumBlocks);

  UInt32 bitmapSize = Dyn.NumBitMapSectors() << kSectorSize_Log;

  while ((UInt32)Bat.Size() < Dyn.NumBlocks)
  {
    RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, kSectorSize))
    UpdatePhySize(Dyn.TableOffset + kSectorSize);
    for (UInt32 j = 0; j < kSectorSize; j += 4)
    {
      UInt32 v = Get32(buf + j);
      if (v != kUnusedBlock)
      {
        UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog;
        UpdatePhySize(((UInt64)v << kSectorSize_Log) + bitmapSize + blockSize);
        NumUsedBlocks++;
      }
      Bat.AddInReserved(v);
      if ((UInt32)Bat.Size() >= Dyn.NumBlocks)
        break;
    }
  }

  if (headerAndFooterAreEqual)
    return S_OK;

  if (_startOffset + _phySize + kHeaderSize > fileSize)
  {
    // _unexpectedEnd = true;
    _posInArcLimit = _phySize;
    _phySize += kHeaderSize;
    return S_OK;
  }

  RINOK(ReadPhy(_phySize, buf, kHeaderSize))
  if (memcmp(header, buf, kHeaderSize) == 0)
  {
    _posInArcLimit = _phySize;
    _phySize += kHeaderSize;
    return S_OK;
  }

  if (_phySize == 0x800)
  {
    /* WHY does empty archive contain additional empty sector?
       We skip that sector and check footer again. */
    unsigned i;
    for (i = 0; i < kSectorSize && buf[i] == 0; i++);
    if (i == kSectorSize)
    {
      RINOK(ReadPhy(_phySize + kSectorSize, buf, kHeaderSize))
      if (memcmp(header, buf, kHeaderSize) == 0)
      {
        _phySize += kSectorSize;
        _posInArcLimit = _phySize;
        _phySize += kHeaderSize;
        return S_OK;
      }
    }
  }
  _posInArcLimit = _phySize;
  _phySize += kHeaderSize;
  AddErrorMessage("Can't find footer");
  return S_OK;
}

Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize))
{
  if (processedSize)
    *processedSize = 0;
  if (_virtPos >= Footer.CurrentSize)
    return S_OK;
  {
    const UInt64 rem = Footer.CurrentSize - _virtPos;
    if (size > rem)
      size = (UInt32)rem;
  }
  if (size == 0)
    return S_OK;

  if (Footer.IsFixed())
  {
    if (_virtPos > _posInArcLimit)
      return S_FALSE;
    {
      const UInt64 rem = _posInArcLimit - _virtPos;
      if (size > rem)
        size = (UInt32)rem;
    }
    HRESULT res = S_OK;
    if (_virtPos != _posInArc)
    {
      _posInArc = _virtPos;
      res = Seek2(_virtPos);
    }
    if (res == S_OK)
    {
      UInt32 processedSize2 = 0;
      res = Stream->Read(data, size, &processedSize2);
      if (processedSize)
        *processedSize = processedSize2;
      _posInArc += processedSize2;
    }
    if (res != S_OK)
      Reset_PosInArc();
    return res;
  }

  const UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog);
  if (blockIndex >= Bat.Size())
    return E_FAIL; // it's some unexpected case
  const UInt32 blockSectIndex = Bat[blockIndex];
  const UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog;
  UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
  size = MyMin(blockSize - offsetInBlock, size);

  HRESULT res = S_OK;
  if (blockSectIndex == kUnusedBlock)
  {
    if (ParentStream)
    {
      RINOK(InStream_SeekSet(ParentStream, _virtPos))
      res = ParentStream->Read(data, size, &size);
    }
    else
      memset(data, 0, size);
  }
  else
  {
    const UInt64 newPos = (UInt64)blockSectIndex << kSectorSize_Log;
    if (BitMapTag != blockIndex)
    {
      RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.Size()))
      BitMapTag = blockIndex;
    }
    RINOK(ReadPhy(newPos + BitMap.Size() + offsetInBlock, data, size))
    for (UInt32 cur = 0; cur < size;)
    {
      const UInt32 rem = MyMin(0x200 - (offsetInBlock & 0x1FF), size - cur);
      const UInt32 bmi = offsetInBlock >> kSectorSize_Log;
      if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0)
      {
        if (ParentStream)
        {
          RINOK(InStream_SeekSet(ParentStream, _virtPos + cur))
          RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem))
        }
        else
        {
          const Byte *p = (const Byte *)data + cur;
          for (UInt32 i = 0; i < rem; i++)
            if (p[i] != 0)
              return S_FALSE;
        }
      }
      offsetInBlock += rem;
      cur += rem;
    }
  }
  if (processedSize)
    *processedSize = size;
  _virtPos += size;
  return res;
}


enum
{
  kpidParent = kpidUserDefined,
  kpidSavedState
};

static const CStatProp kArcProps[] =
{
  { NULL, kpidOffset, VT_UI8},
  { NULL, kpidCTime, VT_FILETIME},
  { NULL, kpidClusterSize, VT_UI8},
  { NULL, kpidMethod, VT_BSTR},
  { NULL, kpidNumVolumes, VT_UI4},
  { NULL, kpidTotalPhySize, VT_UI8},
  { "Parent", kpidParent, VT_BSTR},
  { NULL, kpidCreatorApp, VT_BSTR},
  { NULL, kpidHostOS, VT_BSTR},
  { "Saved State", kpidSavedState, VT_BOOL},
  { NULL, kpidId, VT_BSTR}
 };

static const Byte kProps[] =
{
  kpidSize,
  kpidPackSize,
  kpidCTime
  
  /*
  { kpidNumCyls, VT_UI4},
  { kpidNumHeads, VT_UI4},
  { kpidSectorsPerTrack, VT_UI4}
  */
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps_WITH_NAME

// VHD start time: 2000-01-01
static const UInt64 kVhdTimeStartValue = (UInt64)3600 * 24 * (399 * 365 + 24 * 4);

static void VhdTimeToFileTime(UInt32 vhdTime, NCOM::CPropVariant &prop)
{
  FILETIME ft, utc;
  UInt64 v = (kVhdTimeStartValue + vhdTime) * 10000000;
  ft.dwLowDateTime = (DWORD)v;
  ft.dwHighDateTime = (DWORD)(v >> 32);
  // specification says that it's UTC time, but Virtual PC 6 writes local time. Why?
  LocalFileTimeToFileTime(&ft, &utc);
  prop = utc;
}

static void StringToAString(char *dest, UInt32 val)
{
  for (int i = 24; i >= 0; i -= 8)
  {
    const Byte b = (Byte)((val >> i) & 0xFF);
    if (b < 0x20 || b > 0x7F)
      break;
    *dest++ = (char)b;
  }
  *dest = 0;
}

Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidMainSubfile: prop = (UInt32)0; break;
    case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
    case kpidClusterSize: if (Footer.ThereIsDynamic()) prop = (UInt32)1 << Dyn.BlockSizeLog; break;
    case kpidShortComment:
    case kpidMethod:
    {
      AString s;
      Footer.AddTypeString(s);
      if (NeedParent())
      {
        s += " -> ";
        const CHandler *p = this;
        while (p && p->NeedParent())
          p = p->Parent;
        if (!p)
          s += '?';
        else
          p->Footer.AddTypeString(s);
      }
      prop = s;
      break;
    }
    case kpidCreatorApp:
    {
      char s[16];
      StringToAString(s, Footer.CreatorApp);
      AString res (s);
      res.Trim();
      res.Add_Space();
      res.Add_UInt32(Footer.CreatorVersion >> 16);
      res.Add_Dot();
      res.Add_UInt32(Footer.CreatorVersion & 0xFFFF);
      prop = res;
      break;
    }
    case kpidHostOS:
    {
      if (Footer.CreatorHostOS == 0x5769326B)
        prop = "Windows";
      else
      {
        char s[16];
        StringToAString(s, Footer.CreatorHostOS);
        prop = s;
      }
      break;
    }
    case kpidId:
    {
      char s[sizeof(Footer.Id) * 2 + 2];
      ConvertDataToHex_Upper(s, Footer.Id, sizeof(Footer.Id));
      prop = s;
      break;
    }
    case kpidSavedState: prop = Footer.SavedState ? true : false; break;
    case kpidParent: if (NeedParent()) prop = GetParentSequence(); break;
    case kpidOffset: prop = _startOffset; 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 kpidErrorFlags:
    {
      UInt32 flags = 0;
      if (_unexpectedEnd)
        flags |= kpv_ErrorFlags_UnexpectedEndOfArc;
      if (flags != 0)
        prop = flags;
      break;
    }
    */
    case kpidError: if (!_errorMessage.IsEmpty()) prop = _errorMessage; break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}


HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level)
{
  Close();
  Stream = stream;
  if (level > (1 << 12)) // Maybe we need to increase that limit
    return S_FALSE;
  
  RINOK(Open3())
  
  NumLevels = 1;
  if (child && memcmp(child->Dyn.ParentId, Footer.Id, 16) != 0)
    return S_FALSE;
  if (Footer.Type != kDiskType_Diff)
    return S_OK;

  bool useRelative;
  UString name;
  
  if (!Dyn.RelativeParentNameFromLocator.IsEmpty())
  {
    useRelative = true;
    name = Dyn.RelativeParentNameFromLocator;
  }
  else
  {
    useRelative = false;
    name = Dyn.ParentName;
  }
  
  Dyn.RelativeNameWasUsed = useRelative;

  Z7_DECL_CMyComPtr_QI_FROM(
      IArchiveOpenVolumeCallback,
      openVolumeCallback, openArchiveCallback)
  
  if (openVolumeCallback)
  {
    CMyComPtr<IInStream> nextStream;
    HRESULT res = openVolumeCallback->GetStream(name, &nextStream);
    
    if (res == S_FALSE)
    {
      if (useRelative && Dyn.ParentName != Dyn.RelativeParentNameFromLocator)
      {
        res = openVolumeCallback->GetStream(Dyn.ParentName, &nextStream);
        if (res == S_OK)
          Dyn.RelativeNameWasUsed = false;
      }
    }

    if (res != S_OK && res != S_FALSE)
      return res;
    
    if (res == S_FALSE || !nextStream)
    {
      AddErrorMessage("Missing volume : ", name);
      return S_OK;
    }
    
    Parent = new CHandler;
    ParentStream = Parent;
    
    res = Parent->Open2(nextStream, this, openArchiveCallback, level + 1);

    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)
    {
      NumLevels = Parent->NumLevels + 1;
    }
  }
  {
    const CHandler *p = this;
    while (p->NeedParent())
    {
      p = p->Parent;
      if (!p)
      {
        AddErrorMessage("Can't open parent VHD file : ", Dyn.ParentName);
        break;
      }
    }
  }
  return S_OK;
}


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

  _phySize = 0;
  NumLevels = 0;
  Bat.Clear();
  NumUsedBlocks = 0;
  Parent = NULL;
  ParentStream.Release();
  Dyn.Clear();
  _errorMessage.Empty();
  // _unexpectedEnd = 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 = Footer.CurrentSize; break;
    case kpidPackSize: prop = GetPackSize(); break;
    case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
    case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;

    /*
    case kpidNumCyls: prop = Footer.NumCyls(); break;
    case kpidNumHeads: prop = Footer.NumHeads(); break;
    case kpidSectorsPerTrack: prop = Footer.NumSectorsPerTrack(); 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 (Footer.IsFixed())
  {
    CMyComPtr2<ISequentialInStream, CLimitedInStream> streamSpec;
    streamSpec.Create_if_Empty();
    streamSpec->SetStream(Stream);
    // fixme : check (startOffset = 0)
    streamSpec->InitAndSeek(_startOffset, Footer.CurrentSize);
    RINOK(streamSpec->SeekToStart())
    *stream = streamSpec.Detach();
    return S_OK;
  }
  if (!Footer.ThereIsDynamic() || !AreParentsOK())
    return S_FALSE;
  CMyComPtr<ISequentialInStream> streamTemp = this;
  RINOK(InitAndSeek())
  *stream = streamTemp.Detach();
  return S_OK;
  COM_TRY_END
}

REGISTER_ARC_I(
  "VHD", "vhd", NULL, 0xDC,
  kSignature,
  0,
  NArcInfoFlags::kUseGlobalOffset,
  NULL)

}}
