// QcowHandler.cpp #include "StdAfx.h" // #include #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Windows/PropVariant.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/DeflateDecoder.h" #include "HandlerCont.h" #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) using namespace NWindows; namespace NArchive { namespace NQcow { #define SIGNATURE { 'Q', 'F', 'I', 0xFB, 0, 0, 0 } static const Byte k_Signature[] = SIGNATURE; class CHandler: public CHandlerImg { unsigned _clusterBits; unsigned _numMidBits; UInt64 _compressedFlag; CObjectVector _tables; UInt64 _cacheCluster; CByteBuffer _cache; CByteBuffer _cacheCompressed; UInt64 _comprPos; size_t _comprSize; UInt64 _phySize; CBufInStream *_bufInStreamSpec; CMyComPtr _bufInStream; CBufPtrSeqOutStream *_bufOutStreamSpec; CMyComPtr _bufOutStream; NCompress::NDeflate::NDecoder::CCOMCoder *_deflateDecoderSpec; CMyComPtr _deflateDecoder; bool _needDeflate; bool _isArc; bool _unsupported; UInt32 _version; UInt32 _cryptMethod; HRESULT Seek(UInt64 offset) { _posInArc = offset; return Stream->Seek(offset, STREAM_SEEK_SET, NULL); } HRESULT InitAndSeek() { _virtPos = 0; return Seek(0); } HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback); public: INTERFACE_IInArchive_Img(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (_virtPos >= _size) return S_OK; { UInt64 rem = _size - _virtPos; if (size > rem) size = (UInt32)rem; if (size == 0) return S_OK; } for (;;) { UInt64 cluster = _virtPos >> _clusterBits; size_t clusterSize = (size_t)1 << _clusterBits; size_t lowBits = (size_t)_virtPos & (clusterSize - 1); { size_t rem = clusterSize - lowBits; if (size > rem) size = (UInt32)rem; } if (cluster == _cacheCluster) { memcpy(data, _cache + lowBits, size); _virtPos += size; if (processedSize) *processedSize = size; return S_OK; } UInt64 high = cluster >> _numMidBits; if (high < _tables.Size()) { const CByteBuffer &buffer = _tables[(unsigned)high]; if (buffer.Size() != 0) { size_t midBits = (size_t)cluster & (((size_t)1 << _numMidBits) - 1); const Byte *p = (const Byte *)buffer + (midBits << 3); UInt64 v = Get64(p); if (v != 0) { if ((v & _compressedFlag) != 0) { if (_version <= 1) return E_FAIL; unsigned numOffsetBits = (62 - (_clusterBits - 8)); UInt64 offset = v & (((UInt64)1 << 62) - 1); const size_t dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; offset &= ((UInt64)1 << numOffsetBits) - 1; UInt64 sectorOffset = offset >> 9 << 9; UInt64 offset2inCache = sectorOffset - _comprPos; if (sectorOffset >= _comprPos && offset2inCache < _comprSize) { if (offset2inCache != 0) { _comprSize -= (size_t)offset2inCache; memmove(_cacheCompressed, _cacheCompressed + offset2inCache, _comprSize); _comprPos = sectorOffset; } sectorOffset += _comprSize; } else { _comprPos = sectorOffset; _comprSize = 0; } // printf("\nDeflate"); if (sectorOffset != _posInArc) { // printf("\nDeflate %12I64x %12I64x\n", sectorOffset, sectorOffset - _posInArc); RINOK(Seek(sectorOffset)); } if (_cacheCompressed.Size() < dataSize) return E_FAIL; size_t dataSize3 = dataSize - _comprSize; size_t dataSize2 = dataSize3; RINOK(ReadStream(Stream, _cacheCompressed + _comprSize, &dataSize2)); _posInArc += dataSize2; if (dataSize2 != dataSize3) return E_FAIL; _comprSize += dataSize2; const size_t kSectorMask = (1 << 9) - 1; size_t offsetInSector = ((size_t)offset & kSectorMask); _bufInStreamSpec->Init(_cacheCompressed + offsetInSector, dataSize - offsetInSector); _cacheCluster = (UInt64)(Int64)-1; if (_cache.Size() < clusterSize) return E_FAIL; _bufOutStreamSpec->Init(_cache, clusterSize); // Do we need to use smaller block than clusterSize for last cluster? UInt64 blockSize64 = clusterSize; HRESULT res = _deflateDecoderSpec->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); /* if (_bufOutStreamSpec->GetPos() != clusterSize) memset(_cache + _bufOutStreamSpec->GetPos(), 0, clusterSize - _bufOutStreamSpec->GetPos()); */ if (res == S_OK) if (!_deflateDecoderSpec->IsFinished() || _bufOutStreamSpec->GetPos() != clusterSize) res = S_FALSE; RINOK(res); _cacheCluster = cluster; continue; /* memcpy(data, _cache + lowBits, size); _virtPos += size; if (processedSize) *processedSize = size; return S_OK; */ } // version 3 support zero clusters if (((UInt32)v & 511) != 1) { v &= (_compressedFlag - 1); v += lowBits; if (v != _posInArc) { // printf("\n%12I64x\n", v - _posInArc); RINOK(Seek(v)); } HRESULT res = Stream->Read(data, size, &size); _posInArc += size; _virtPos += size; if (processedSize) *processedSize = size; return res; } } } } memset(data, 0, size); _virtPos += size; if (processedSize) *processedSize = size; return S_OK; } } static const Byte kProps[] = { kpidSize, kpidPackSize }; static const Byte kArcProps[] = { kpidClusterSize, kpidUnpackVer, kpidMethod }; IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidMainSubfile: prop = (UInt32)0; break; case kpidClusterSize: prop = (UInt32)1 << _clusterBits; break; case kpidPhySize: if (_phySize != 0) prop = _phySize; break; case kpidUnpackVer: prop = _version; break; case kpidMethod: { AString s; if (_needDeflate) s = "Deflate"; if (_cryptMethod != 0) { s.Add_Space_if_NotEmpty(); if (_cryptMethod == 1) s += "AES"; else { char temp[16]; ConvertUInt32ToString(_cryptMethod, temp); s += temp; } } if (!s.IsEmpty()) prop = s; break; } case kpidErrorFlags: { UInt32 v = 0; if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; // if (_headerError) v |= kpv_ErrorFlags_HeadersError; if (!Stream && v == 0 && _isArc) v = kpv_ErrorFlags_HeadersError; if (v != 0) prop = v; break; } } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidSize: prop = _size; break; case kpidPackSize: prop = _phySize; break; case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; } prop.Detach(value); return S_OK; COM_TRY_END } HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) { const unsigned kHeaderSize = 18 * 4; Byte buf[kHeaderSize]; RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); if (memcmp(buf, k_Signature, 4) != 0) return S_FALSE; _version = Get32(buf + 4); if (_version < 1 || _version > 3) return S_FALSE; const UInt64 backOffset = Get64(buf + 8); // UInt32 backSize = Get32(buf + 0x10); UInt64 l1Offset = 0; UInt32 l1Size = 0; if (_version == 1) { // _mTime = Get32(buf + 0x14); // is unused im most images _size = Get64(buf + 0x18); _clusterBits = buf[0x20]; _numMidBits = buf[0x21]; if (_clusterBits < 9 || _clusterBits > 30) return S_FALSE; if (_numMidBits < 1 || _numMidBits > 28) return S_FALSE; _cryptMethod = Get32(buf + 0x24); l1Offset = Get64(buf + 0x28); if (l1Offset < 0x30) return S_FALSE; unsigned numBits2 = (_clusterBits + _numMidBits); UInt64 l1Size64 = (_size + (((UInt64)1 << numBits2) - 1)) >> numBits2; if (l1Size64 > ((UInt32)1 << 31)) return S_FALSE; l1Size = (UInt32)l1Size64; } else { _clusterBits = Get32(buf + 0x14); if (_clusterBits < 9 || _clusterBits > 30) return S_FALSE; _numMidBits = _clusterBits - 3; _size = Get64(buf + 0x18); _cryptMethod = Get32(buf + 0x20); l1Size = Get32(buf + 0x24); l1Offset = Get64(buf + 0x28); // must be aligned for cluster UInt64 refOffset = Get64(buf + 0x30); // must be aligned for cluster UInt32 refClusters = Get32(buf + 0x38); // UInt32 numSnapshots = Get32(buf + 0x3C); // UInt64 snapshotsOffset = Get64(buf + 0x40); // must be aligned for cluster /* if (numSnapshots != 0) return S_FALSE; */ if (refClusters != 0) { size_t numBytes = refClusters << _clusterBits; /* CByteBuffer refs; refs.Alloc(numBytes); RINOK(stream->Seek(refOffset, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, refs, numBytes)); */ UInt64 end = refOffset + numBytes; if (_phySize < end) _phySize = end; /* for (size_t i = 0; i < numBytes; i += 2) { UInt32 v = GetBe16((const Byte *)refs + (size_t)i); if (v == 0) continue; } */ } } _isArc = true; if (backOffset != 0) { _unsupported = true; return S_FALSE; } const size_t clusterSize = (size_t)1 << _clusterBits; CByteBuffer table; { size_t t1SizeBytes = (size_t)l1Size << 3; if ((t1SizeBytes >> 3) != l1Size) return S_FALSE; table.Alloc(t1SizeBytes); RINOK(stream->Seek(l1Offset, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, table, t1SizeBytes)); { UInt64 end = l1Offset + t1SizeBytes; // we need to uses align end for empty qcow files end = (end + clusterSize - 1) >> _clusterBits << _clusterBits; if (_phySize < end) _phySize = end; } } if (openCallback) { UInt64 totalBytes = (UInt64)l1Size << (_numMidBits + 3); RINOK(openCallback->SetTotal(NULL, &totalBytes)); } _compressedFlag = (_version <= 1) ? ((UInt64)1 << 63) : ((UInt64)1 << 62); const UInt64 offsetMask = _compressedFlag - 1; for (UInt32 i = 0; i < l1Size; i++) { if (openCallback) { UInt64 numBytes = (UInt64)i << (_numMidBits + 3); RINOK(openCallback->SetCompleted(NULL, &numBytes)); } CByteBuffer &buf2 = _tables.AddNew(); { UInt64 v = Get64((const Byte *)table + (size_t)i * 8); v &= offsetMask; if (v == 0) continue; buf2.Alloc((size_t)1 << (_numMidBits + 3)); RINOK(stream->Seek(v, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, buf2, clusterSize)); const UInt64 end = v + clusterSize; if (_phySize < end) _phySize = end; } for (size_t k = 0; k < clusterSize; k += 8) { const UInt64 v = Get64((const Byte *)buf2 + (size_t)k); if (v == 0) continue; UInt64 offset = v & offsetMask; size_t dataSize = clusterSize; if ((v & _compressedFlag) != 0) { if (_version <= 1) { unsigned numOffsetBits = (63 - _clusterBits); dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; offset &= ((UInt64)1 << numOffsetBits) - 1; dataSize = 0; // offset >>= 9; // offset <<= 9; } else { unsigned numOffsetBits = (62 - (_clusterBits - 8)); dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; offset &= ((UInt64)1 << numOffsetBits) - 1; offset >>= 9; offset <<= 9; } _needDeflate = true; } else { UInt32 low = (UInt32)v & 511; if (low != 0) { // version 3 support zero clusters if (_version < 3 || low != 1) { _unsupported = true; return S_FALSE; } } } UInt64 end = offset + dataSize; if (_phySize < end) _phySize = end; } } if (_cryptMethod != 0) _unsupported = true; if (_needDeflate && _version <= 1) // that case was not implemented _unsupported = true; Stream = stream; return S_OK; } STDMETHODIMP CHandler::Close() { _tables.Clear(); _phySize = 0; _size = 0; _cacheCluster = (UInt64)(Int64)-1; _comprPos = 0; _comprSize = 0; _needDeflate = false; _isArc = false; _unsupported = false; _imgExt = NULL; Stream.Release(); return S_OK; } STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream) { COM_TRY_BEGIN *stream = NULL; if (_unsupported) return S_FALSE; if (_needDeflate) { if (_version <= 1) return S_FALSE; if (!_bufInStream) { _bufInStreamSpec = new CBufInStream; _bufInStream = _bufInStreamSpec; } if (!_bufOutStream) { _bufOutStreamSpec = new CBufPtrSeqOutStream(); _bufOutStream = _bufOutStreamSpec; } if (!_deflateDecoder) { _deflateDecoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder(); _deflateDecoder = _deflateDecoderSpec; _deflateDecoderSpec->Set_NeedFinishInput(true); } size_t clusterSize = (size_t)1 << _clusterBits; _cache.AllocAtLeast(clusterSize); _cacheCompressed.AllocAtLeast(clusterSize * 2); } CMyComPtr streamTemp = this; RINOK(InitAndSeek()); *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( "QCOW", "qcow qcow2 qcow2c", NULL, 0xCA, k_Signature, 0, 0, NULL) }}