// XzHandler.cpp #include "StdAfx.h" #include "../../../C/Alloc.h" #include "../../../C/XzCrc64.h" #include "../../../C/XzEnc.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../ICoder.h" #include "../Common/CWrappers.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #include "IArchive.h" #include "Common/HandlerOut.h" using namespace NWindows; namespace NCompress { namespace NLzma2 { HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props); }} static void *SzAlloc(void *, size_t size) { return MyAlloc(size); } static void SzFree(void *, void *address) { MyFree(address); } static ISzAlloc g_Alloc = { SzAlloc, SzFree }; namespace NArchive { namespace NXz { struct CCrc64Gen { CCrc64Gen() { Crc64GenerateTable(); } } g_Crc64TableInit; class CHandler: public IInArchive, public IArchiveOpenSeq, #ifndef EXTRACT_ONLY public IOutArchive, public ISetProperties, public COutHandler, #endif public CMyUnknownImp { Int64 _startPosition; UInt64 _packSize; UInt64 _unpackSize; UInt64 _numBlocks; AString _methodsString; bool _useSeq; UInt64 _unpackSizeDefined; UInt64 _packSizeDefined; CMyComPtr _stream; CMyComPtr _seqStream; UInt32 _crcSize; void Init() { _crcSize = 4; COutHandler::Init(); } HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *callback); public: MY_QUERYINTERFACE_BEGIN2(IInArchive) MY_QUERYINTERFACE_ENTRY(IArchiveOpenSeq) #ifndef EXTRACT_ONLY MY_QUERYINTERFACE_ENTRY(IOutArchive) MY_QUERYINTERFACE_ENTRY(ISetProperties) #endif MY_QUERYINTERFACE_END MY_ADDREF_RELEASE INTERFACE_IInArchive(;) STDMETHOD(OpenSeq)(ISequentialInStream *stream); #ifndef EXTRACT_ONLY INTERFACE_IOutArchive(;) STDMETHOD(SetProperties)(const wchar_t **names, const PROPVARIANT *values, Int32 numProps); #endif CHandler(); }; CHandler::CHandler() { Init(); } STATPROPSTG kProps[] = { { NULL, kpidSize, VT_UI8}, { NULL, kpidPackSize, VT_UI8}, { NULL, kpidMethod, VT_BSTR} }; STATPROPSTG kArcProps[] = { { NULL, kpidMethod, VT_BSTR}, { NULL, kpidNumBlocks, VT_UI4} }; IMP_IInArchive_Props IMP_IInArchive_ArcProps static char GetHex(Byte value) { return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); } static inline void AddHexToString(AString &res, Byte value) { res += GetHex((Byte)(value >> 4)); res += GetHex((Byte)(value & 0xF)); } static AString ConvertUInt32ToString(UInt32 value) { char temp[32]; ::ConvertUInt32ToString(value, temp); return temp; } static AString Lzma2PropToString(int prop) { if ((prop & 1) == 0) return ConvertUInt32ToString(prop / 2 + 12); AString res; char c; UInt32 size = (2 | ((prop) & 1)) << ((prop) / 2 + 1); if (prop > 17) { res = ConvertUInt32ToString(size >> 10); c = 'm'; } else { res = ConvertUInt32ToString(size); c = 'k'; } return res + c; } struct CMethodNamePair { UInt32 Id; const char *Name; }; static CMethodNamePair g_NamePairs[] = { { XZ_ID_Subblock, "SB" }, { XZ_ID_Delta, "Delta" }, { XZ_ID_X86, "x86" }, { XZ_ID_PPC, "PPC" }, { XZ_ID_IA64, "IA64" }, { XZ_ID_ARM, "ARM" }, { XZ_ID_ARMT, "ARMT" }, { XZ_ID_SPARC, "SPARC" }, { XZ_ID_LZMA2, "LZMA2" } }; static AString GetMethodString(const CXzFilter &f) { AString s; for (unsigned int i = 0; i < sizeof(g_NamePairs) / sizeof(g_NamePairs[i]); i++) // PQR for MinGW-w64: Signed < Unsigned comparison. if (g_NamePairs[i].Id == f.id) s = g_NamePairs[i].Name; if (s.IsEmpty()) { char temp[32]; ::ConvertUInt64ToString(f.id, temp); s = temp; } if (f.propsSize > 0) { s += ':'; if (f.id == XZ_ID_LZMA2 && f.propsSize == 1) s += Lzma2PropToString(f.props[0]); else if (f.id == XZ_ID_Delta && f.propsSize == 1) s += ConvertUInt32ToString((UInt32)f.props[0] + 1); else { s += '['; for (UInt32 bi = 0; bi < f.propsSize; bi++) AddHexToString(s, f.props[bi]); s += ']'; } } return s; } static void AddString(AString &dest, const AString &src) { if (!dest.IsEmpty()) dest += ' '; dest += src; } static const char *kChecks[] = { "NoCheck", "CRC32", NULL, NULL, "CRC64", NULL, NULL, NULL, NULL, NULL, "SHA256", NULL, NULL, NULL, NULL, NULL }; static AString GetCheckString(const CXzs &xzs) { size_t i; UInt32 mask = 0; for (i = 0; i < xzs.num; i++) mask |= ((UInt32)1 << XzFlags_GetCheckType(xzs.streams[i].flags)); AString s; for (i = 0; i <= XZ_CHECK_MASK; i++) if (((mask >> i) & 1) != 0) { AString s2; if (kChecks[i]) s2 = kChecks[i]; else s2 = "Check-" + ConvertUInt32ToString((UInt32)i); AddString(s, s2); } return s; } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch(propID) { case kpidNumBlocks: if (!_useSeq) prop = _numBlocks; break; case kpidPhySize: if (_packSizeDefined) prop = _packSize; break; case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = 1; return S_OK; } STDMETHODIMP CHandler::GetProperty(UInt32, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch(propID) { case kpidSize: if (_unpackSizeDefined) prop = _unpackSize; break; case kpidPackSize: if (_packSizeDefined) prop = _packSize; break; case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break; } prop.Detach(value); return S_OK; COM_TRY_END } struct COpenCallbackWrap { ICompressProgress p; IArchiveOpenCallback *OpenCallback; HRESULT Res; COpenCallbackWrap(IArchiveOpenCallback *progress); }; static SRes OpenCallbackProgress(void *pp, UInt64 inSize, UInt64 /* outSize */) { COpenCallbackWrap *p = (COpenCallbackWrap *)pp; p->Res = p->OpenCallback->SetCompleted(NULL, &inSize); return (SRes)p->Res; } COpenCallbackWrap::COpenCallbackWrap(IArchiveOpenCallback *callback) { p.Progress = OpenCallbackProgress; OpenCallback = callback; Res = SZ_OK; } struct CXzsCPP { CXzs p; CXzsCPP() { Xzs_Construct(&p); } ~CXzsCPP() { Xzs_Free(&p, &g_Alloc); } }; HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback) { CSeekInStreamWrap inStreamImp(inStream); CLookToRead lookStream; LookToRead_CreateVTable(&lookStream, True); lookStream.realStream = &inStreamImp.p; LookToRead_Init(&lookStream); COpenCallbackWrap openWrap(callback); RINOK(inStream->Seek(0, STREAM_SEEK_END, &_packSize)); RINOK(callback->SetTotal(NULL, &_packSize)); CXzsCPP xzs; SRes res = Xzs_ReadBackward(&xzs.p, &lookStream.s, &_startPosition, &openWrap.p, &g_Alloc); if (res == SZ_ERROR_NO_ARCHIVE && xzs.p.num > 0) res = SZ_OK; if (res == SZ_OK) { _packSize -= _startPosition; _unpackSize = Xzs_GetUnpackSize(&xzs.p); _unpackSizeDefined = _packSizeDefined = true; _numBlocks = (UInt64)Xzs_GetNumBlocks(&xzs.p); RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); CXzStreamFlags st; CSeqInStreamWrap inStreamWrap(inStream); SRes res2 = Xz_ReadHeader(&st, &inStreamWrap.p); if (res2 == SZ_OK) { CXzBlock block; Bool isIndex; UInt32 headerSizeRes; res2 = XzBlock_ReadHeader(&block, &inStreamWrap.p, &isIndex, &headerSizeRes); if (res2 == SZ_OK && !isIndex) { int numFilters = XzBlock_GetNumFilters(&block); for (int i = 0; i < numFilters; i++) AddString(_methodsString, GetMethodString(block.filters[i])); } } AddString(_methodsString, GetCheckString(xzs.p)); } if (res != SZ_OK || _startPosition != 0) { RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); CXzStreamFlags st; CSeqInStreamWrap inStreamWrap(inStream); SRes res2 = Xz_ReadHeader(&st, &inStreamWrap.p); if (res2 == SZ_OK) { res = res2; _startPosition = 0; _useSeq = True; _unpackSizeDefined = _packSizeDefined = false; } } if (res == SZ_ERROR_NO_ARCHIVE) return S_FALSE; RINOK(SResToHRESULT(res)); _stream = inStream; _seqStream = inStream; return S_OK; } STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN try { Close(); return Open2(inStream, callback); } catch(...) { return S_FALSE; } COM_TRY_END } STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) { Close(); _seqStream = stream; return S_OK; } STDMETHODIMP CHandler::Close() { _numBlocks = 0; _useSeq = true; _unpackSizeDefined = _packSizeDefined = false; _methodsString.Empty(); _stream.Release(); _seqStream.Release(); return S_OK; } class CSeekToSeqStream: public IInStream, public CMyUnknownImp { public: CMyComPtr Stream; MY_UNKNOWN_IMP1(IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; STDMETHODIMP CSeekToSeqStream::Read(void *data, UInt32 size, UInt32 *processedSize) { return Stream->Read(data, size, processedSize); } STDMETHODIMP CSeekToSeqStream::Seek(Int64, UInt32, UInt64 *) { return E_NOTIMPL; } struct CXzUnpackerCPP { Byte *InBuf; Byte *OutBuf; CXzUnpacker p; CXzUnpackerCPP(): InBuf(0), OutBuf(0) {} ~CXzUnpackerCPP() { XzUnpacker_Free(&p); MyFree(InBuf); MyFree(OutBuf); } }; STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN if (numItems == 0) return S_OK; if (numItems != (UInt32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; extractCallback->SetTotal(_packSize); UInt64 currentTotalPacked = 0; RINOK(extractCallback->SetCompleted(¤tTotalPacked)); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); if (!testMode && !realOutStream) return S_OK; extractCallback->PrepareOperation(askMode); if (_stream) { RINOK(_stream->Seek(_startPosition, STREAM_SEEK_SET, NULL)); } CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, true); CCompressProgressWrap progressWrap(progress); SRes res; const UInt32 kInBufSize = 1 << 15; const UInt32 kOutBufSize = 1 << 21; UInt32 inPos = 0; UInt32 inSize = 0; UInt32 outPos = 0; CXzUnpackerCPP xzu; res = XzUnpacker_Create(&xzu.p, &g_Alloc); if (res == SZ_OK) { xzu.InBuf = (Byte *)MyAlloc(kInBufSize); xzu.OutBuf = (Byte *)MyAlloc(kOutBufSize); if (xzu.InBuf == 0 || xzu.OutBuf == 0) res = SZ_ERROR_MEM; } if (res == SZ_OK) for (;;) { if (inPos == inSize) { inPos = inSize = 0; RINOK(_seqStream->Read(xzu.InBuf, kInBufSize, &inSize)); } SizeT inLen = inSize - inPos; SizeT outLen = kOutBufSize - outPos; ECoderStatus status; res = XzUnpacker_Code(&xzu.p, xzu.OutBuf + outPos, &outLen, xzu.InBuf + inPos, &inLen, (inSize == 0 ? CODER_FINISH_END : CODER_FINISH_ANY), &status); // printf("\n_inPos = %6d inLen = %5d, outLen = %5d", inPos, inLen, outLen); inPos += (UInt32)inLen; outPos += (UInt32)outLen; lps->InSize += inLen; lps->OutSize += outLen; bool finished = (((inLen == 0) && (outLen == 0)) || res != SZ_OK); if (outPos == kOutBufSize || finished) { if (realOutStream && outPos > 0) { RINOK(WriteStream(realOutStream, xzu.OutBuf, outPos)); } outPos = 0; } if (finished) { _packSize = lps->InSize; _unpackSize = lps->OutSize; _packSizeDefined = _unpackSizeDefined = true; if (res == SZ_OK) { if (status == CODER_STATUS_NEEDS_MORE_INPUT) { if (XzUnpacker_IsStreamWasFinished(&xzu.p)) _packSize -= xzu.p.padSize; else res = SZ_ERROR_DATA; } else res = SZ_ERROR_DATA; } break; } RINOK(lps->SetCur()); } Int32 opRes; switch(res) { case SZ_OK: opRes = NExtract::NOperationResult::kOK; break; case SZ_ERROR_UNSUPPORTED: opRes = NExtract::NOperationResult::kUnSupportedMethod; break; case SZ_ERROR_CRC: opRes = NExtract::NOperationResult::kCRCError; break; case SZ_ERROR_DATA: case SZ_ERROR_ARCHIVE: case SZ_ERROR_NO_ARCHIVE: opRes = NExtract::NOperationResult::kDataError; break; default: return SResToHRESULT(res); } realOutStream.Release(); RINOK(extractCallback->SetOperationResult(opRes)); return S_OK; COM_TRY_END } #ifndef EXTRACT_ONLY STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) { *timeType = NFileTimeType::kUnix; return S_OK; } STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *updateCallback) { CSeqOutStreamWrap seqOutStream(outStream); if (numItems == 0) { SRes res = Xz_EncodeEmpty(&seqOutStream.p); return SResToHRESULT(res); } if (numItems != 1) return E_INVALIDARG; Int32 newData, newProps; UInt32 indexInArchive; if (!updateCallback) return E_FAIL; RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); if (IntToBool(newProps)) { { NCOM::CPropVariant prop; RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); if (prop.vt != VT_EMPTY) if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) return E_INVALIDARG; } } if (IntToBool(newData)) { { UInt64 size; NCOM::CPropVariant prop; RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; RINOK(updateCallback->SetTotal(size)); } CLzma2EncProps lzma2Props; Lzma2EncProps_Init(&lzma2Props); lzma2Props.lzmaProps.level = _level; CMyComPtr fileInStream; RINOK(updateCallback->GetStream(0, &fileInStream)); CSeqInStreamWrap seqInStream(fileInStream); for (int i = 0; i < _methods.Size(); i++) { COneMethodInfo &m = _methods[i]; SetCompressionMethod2(m #ifndef _7ZIP_ST , _numThreads #endif ); if (m.IsLzma()) { for (int j = 0; j < m.Props.Size(); j++) { const CProp &prop = m.Props[j]; RINOK(NCompress::NLzma2::SetLzma2Prop(prop.Id, prop.Value, lzma2Props)); } } } #ifndef _7ZIP_ST lzma2Props.numTotalThreads = _numThreads; #endif CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(updateCallback, true); CCompressProgressWrap progressWrap(progress); SRes res = Xz_Encode(&seqOutStream.p, &seqInStream.p, &lzma2Props, False, &progressWrap.p); if (res == SZ_OK) return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); return SResToHRESULT(res); } if (indexInArchive != 0) return E_INVALIDARG; if (_stream) RINOK(_stream->Seek(_startPosition, STREAM_SEEK_SET, NULL)); return NCompress::CopyStream(_stream, outStream, 0); } STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProps) { COM_TRY_BEGIN BeforeSetProperty(); for (int i = 0; i < numProps; i++) { RINOK(SetProperty(names[i], values[i])); } return S_OK; COM_TRY_END } #endif static IInArchive *CreateArc() { return new NArchive::NXz::CHandler; } #ifndef EXTRACT_ONLY static IOutArchive *CreateArcOut() { return new NArchive::NXz::CHandler; } #else #define CreateArcOut 0 #endif static CArcInfo g_ArcInfo = { L"xz", L"xz txz", L"* .tar", 0xC, {0xFD, '7' , 'z', 'X', 'Z', '\0'}, 6, true, CreateArc, CreateArcOut }; REGISTER_ARC(xz) }}