Files
easy7zip/CPP/7zip/Archive/SparseHandler.cpp

549 lines
13 KiB
C++

// SparseHandler.cpp
#include "StdAfx.h"
#include "../../../C/CpuArch.h"
#include "../../Common/ComTry.h"
#include "../../Windows/PropVariantUtils.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"
#include "HandlerCont.h"
#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
#define G16(_offs_, dest) dest = Get16(p + (_offs_));
#define G32(_offs_, dest) dest = Get32(p + (_offs_));
using namespace NWindows;
namespace NArchive {
namespace NSparse {
// libsparse and simg2img
struct CHeader
{
// UInt32 magic; /* 0xed26ff3a */
// UInt16 major_version; /* (0x1) - reject images with higher major versions */
// UInt16 minor_version; /* (0x0) - allow images with higer minor versions */
UInt16 file_hdr_sz; /* 28 bytes for first revision of the file format */
UInt16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */
UInt32 BlockSize; /* block size in bytes, must be a multiple of 4 (4096) */
UInt32 NumBlocks; /* total blocks in the non-sparse output image */
UInt32 NumChunks; /* total chunks in the sparse input image */
// UInt32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" as 0. */
void Parse(const Byte *p)
{
// G16 (4, major_version);
// G16 (6, minor_version);
G16 (8, file_hdr_sz);
G16 (10, chunk_hdr_sz);
G32 (12, BlockSize);
G32 (16, NumBlocks);
G32 (20, NumChunks);
// G32 (24, image_checksum);
}
};
// #define SPARSE_HEADER_MAGIC 0xed26ff3a
#define CHUNK_TYPE_RAW 0xCAC1
#define CHUNK_TYPE_FILL 0xCAC2
#define CHUNK_TYPE_DONT_CARE 0xCAC3
#define CHUNK_TYPE_CRC32 0xCAC4
#define MY__CHUNK_TYPE_FILL 0
#define MY__CHUNK_TYPE_DONT_CARE 1
#define MY__CHUNK_TYPE_RAW__START 2
static const char * const g_Methods[] =
{
"RAW"
, "FILL"
, "SPARSE" // "DONT_CARE"
, "CRC32"
};
static const unsigned kFillSize = 4;
struct CChunk
{
UInt32 VirtBlock;
Byte Fill [kFillSize];
UInt64 PhyOffset;
};
static const Byte k_Signature[] = { 0x3a, 0xff, 0x26, 0xed, 1, 0 };
class CHandler: public CHandlerImg
{
CRecordVector<CChunk> Chunks;
UInt64 _virtSize_fromChunks;
unsigned _blockSizeLog;
UInt32 _chunkIndexPrev;
UInt64 _packSizeProcessed;
UInt64 _phySize;
UInt32 _methodFlags;
bool _isArc;
bool _headersError;
bool _unexpectedEnd;
// bool _unsupported;
UInt32 NumChunks; // from header
HRESULT Seek2(UInt64 offset)
{
_posInArc = offset;
return Stream->Seek(offset, STREAM_SEEK_SET, NULL);
}
void 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();
_chunkIndexPrev = 0;
_packSizeProcessed = 0;
}
// virtual functions
bool Init_PackSizeProcessed()
{
_packSizeProcessed = 0;
return true;
}
bool Get_PackSizeProcessed(UInt64 &size)
{
size = _packSizeProcessed;
return true;
}
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback);
HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);
public:
INTERFACE_IInArchive_Img(;)
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
};
static const Byte kProps[] =
{
kpidSize,
kpidPackSize
};
static const Byte kArcProps[] =
{
kpidClusterSize,
kpidNumBlocks,
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)((UInt32)1 << _blockSizeLog); break;
case kpidNumBlocks: prop = (UInt32)NumChunks; break;
case kpidPhySize: if (_phySize != 0) prop = _phySize; break;
case kpidMethod:
{
FLAGS_TO_PROP(g_Methods, _methodFlags, prop);
break;
}
case kpidErrorFlags:
{
UInt32 v = 0;
if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
if (_headersError) v |= kpv_ErrorFlags_HeadersError;
if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd;
// if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod;
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
}
static unsigned GetLogSize(UInt32 size)
{
unsigned k;
for (k = 0; k < 32; k++)
if (((UInt32)1 << k) == size)
return k;
return k;
}
HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback)
{
const unsigned kHeaderSize = 28;
const unsigned kChunkHeaderSize = 12;
CHeader h;
{
Byte buf[kHeaderSize];
RINOK(ReadStream_FALSE(stream, buf, kHeaderSize));
if (memcmp(buf, k_Signature, 6) != 0)
return S_FALSE;
h.Parse(buf);
}
if (h.file_hdr_sz != kHeaderSize ||
h.chunk_hdr_sz != kChunkHeaderSize)
return S_FALSE;
NumChunks = h.NumChunks;
const unsigned logSize = GetLogSize(h.BlockSize);
if (logSize < 2 || logSize >= 32)
return S_FALSE;
_blockSizeLog = logSize;
_size = (UInt64)h.NumBlocks << logSize;
if (h.NumChunks >= (UInt32)(Int32)-2) // it's our limit
return S_FALSE;
_isArc = true;
Chunks.Reserve(h.NumChunks + 1);
UInt64 offset = kHeaderSize;
UInt32 virtBlock = 0;
UInt32 i;
for (i = 0; i < h.NumChunks; i++)
{
{
const UInt32 mask = ((UInt32)1 << 16) - 1;
if ((i & mask) == mask && openCallback)
{
RINOK(openCallback->SetCompleted(NULL, &offset));
}
}
Byte buf[kChunkHeaderSize];
{
size_t processed = kChunkHeaderSize;
RINOK(ReadStream(stream, buf, &processed));
if (kChunkHeaderSize != processed)
{
offset += kChunkHeaderSize;
break;
}
}
const UInt32 type = Get32(&buf[0]);
const UInt32 numBlocks = Get32(&buf[4]);
UInt32 size = Get32(&buf[8]);
if (type < CHUNK_TYPE_RAW ||
type > CHUNK_TYPE_CRC32)
return S_FALSE;
if (size < kChunkHeaderSize)
return S_FALSE;
CChunk c;
c.PhyOffset = offset + kChunkHeaderSize;
c.VirtBlock = virtBlock;
offset += size;
size -= kChunkHeaderSize;
_methodFlags |= ((UInt32)1 << (type - CHUNK_TYPE_RAW));
if (numBlocks > h.NumBlocks - virtBlock)
return S_FALSE;
if (type == CHUNK_TYPE_CRC32)
{
// crc chunk must be last chunk (i == h.NumChunks -1);
if (size != kFillSize || numBlocks != 0)
return S_FALSE;
{
size_t processed = kFillSize;
RINOK(ReadStream(stream, c.Fill, &processed));
if (kFillSize != processed)
break;
}
continue;
}
// else
{
if (numBlocks == 0)
return S_FALSE;
if (type == CHUNK_TYPE_DONT_CARE)
{
if (size != 0)
return S_FALSE;
c.PhyOffset = MY__CHUNK_TYPE_DONT_CARE;
}
else if (type == CHUNK_TYPE_FILL)
{
if (size != kFillSize)
return S_FALSE;
c.PhyOffset = MY__CHUNK_TYPE_FILL;
size_t processed = kFillSize;
RINOK(ReadStream(stream, c.Fill, &processed));
if (kFillSize != processed)
break;
}
else if (type == CHUNK_TYPE_RAW)
{
/* Here we require (size == virtSize).
Probably original decoder also requires it.
But maybe size of last chunk can be non-aligned with blockSize ? */
const UInt32 virtSize = (numBlocks << _blockSizeLog);
if (size != virtSize || numBlocks != (virtSize >> _blockSizeLog))
return S_FALSE;
}
else
return S_FALSE;
virtBlock += numBlocks;
Chunks.AddInReserved(c);
if (type == CHUNK_TYPE_RAW)
RINOK(stream->Seek(offset, STREAM_SEEK_SET, NULL));
}
}
if (i != h.NumChunks)
_unexpectedEnd = true;
else if (virtBlock != h.NumBlocks)
_headersError = true;
_phySize = offset;
{
CChunk c;
c.VirtBlock = virtBlock;
c.PhyOffset = offset;
Chunks.AddInReserved(c);
}
_virtSize_fromChunks = (UInt64)virtBlock << _blockSizeLog;
Stream = stream;
return S_OK;
}
STDMETHODIMP CHandler::Close()
{
Chunks.Clear();
_isArc = false;
_virtSize_fromChunks = 0;
// _unsupported = false;
_headersError = false;
_unexpectedEnd = false;
_phySize = 0;
_methodFlags = 0;
_chunkIndexPrev = 0;
_packSizeProcessed = 0;
// CHandlerImg:
Clear_HandlerImg_Vars();
Stream.Release();
return S_OK;
}
STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
{
COM_TRY_BEGIN
*stream = NULL;
if (Chunks.Size() < 1)
return S_FALSE;
if (Chunks.Size() < 2 && _virtSize_fromChunks != 0)
return S_FALSE;
// if (_unsupported) return S_FALSE;
InitSeekPositions();
CMyComPtr<ISequentialInStream> streamTemp = this;
*stream = streamTemp.Detach();
return S_OK;
COM_TRY_END
}
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;
}
}
{
size_t size2 = size;
const HRESULT res = ReadStream(Stream, data, &size2);
processed = (UInt32)size2;
_packSizeProcessed += size2;
_posInArc += size2;
if (res != S_OK)
Reset_PosInArc(); // we don't trust seek_pos in case of reading error
return res;
}
}
STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)
{
if (processedSize)
*processedSize = 0;
// const unsigned kLimit = (1 << 16) + 1; if (size > kLimit) size = kLimit; // for debug
if (_virtPos >= _virtSize_fromChunks)
return S_OK;
{
const UInt64 rem = _virtSize_fromChunks - _virtPos;
if (size > rem)
size = (UInt32)rem;
if (size == 0)
return S_OK;
}
UInt32 chunkIndex = _chunkIndexPrev;
if (chunkIndex + 1 >= Chunks.Size())
return S_FALSE;
{
const UInt32 blockIndex = (UInt32)(_virtPos >> _blockSizeLog);
if (blockIndex < Chunks[chunkIndex ].VirtBlock ||
blockIndex >= Chunks[chunkIndex + 1].VirtBlock)
{
unsigned left = 0, right = Chunks.Size() - 1;
for (;;)
{
const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2);
if (mid == left)
break;
if (blockIndex < Chunks[mid].VirtBlock)
right = mid;
else
left = mid;
}
chunkIndex = left;
_chunkIndexPrev = chunkIndex;
}
}
const CChunk &c = Chunks[chunkIndex];
const UInt64 offset = _virtPos - ((UInt64)c.VirtBlock << _blockSizeLog);
{
const UInt32 numBlocks = Chunks[chunkIndex + 1].VirtBlock - c.VirtBlock;
const UInt64 rem = ((UInt64)numBlocks << _blockSizeLog) - offset;
if (size > rem)
size = (UInt32)rem;
}
const UInt64 phyOffset = c.PhyOffset;
if (phyOffset >= MY__CHUNK_TYPE_RAW__START)
{
UInt32 processed = 0;
const HRESULT res = ReadPhy(phyOffset + offset, data, size, processed);
if (processedSize)
*processedSize = processed;
_virtPos += processed;
return res;
}
unsigned b = 0;
if (phyOffset == MY__CHUNK_TYPE_FILL)
{
const Byte b0 = c.Fill [0];
const Byte b1 = c.Fill [1];
const Byte b2 = c.Fill [2];
const Byte b3 = c.Fill [3];
if (b0 != b1 ||
b0 != b2 ||
b0 != b3)
{
if (processedSize)
*processedSize = size;
_virtPos += size;
Byte *dest = (Byte *)data;
while (size >= 4)
{
dest[0] = b0;
dest[1] = b1;
dest[2] = b2;
dest[3] = b3;
dest += 4;
size -= 4;
}
if (size > 0) dest[0] = b0;
if (size > 1) dest[1] = b1;
if (size > 2) dest[2] = b2;
return S_OK;
}
b = b0;
}
else if (phyOffset != MY__CHUNK_TYPE_DONT_CARE)
return S_FALSE;
memset(data, b, size);
_virtPos += size;
if (processedSize)
*processedSize = size;
return S_OK;
}
REGISTER_ARC_I(
"Sparse", "simg img", NULL, 0xc2,
k_Signature,
0,
0,
NULL)
}}