1// XzHandler.cpp
2
3#include "StdAfx.h"
4
5#include "../../../C/Alloc.h"
6#include "../../../C/XzCrc64.h"
7#include "../../../C/XzEnc.h"
8
9#include "../../Common/ComTry.h"
10#include "../../Common/IntToString.h"
11
12#include "../ICoder.h"
13
14#include "../Common/CWrappers.h"
15#include "../Common/ProgressUtils.h"
16#include "../Common/RegisterArc.h"
17#include "../Common/StreamUtils.h"
18
19#include "../Compress/CopyCoder.h"
20
21#include "IArchive.h"
22
23#include "Common/HandlerOut.h"
24
25using namespace NWindows;
26
27namespace NCompress {
28namespace NLzma2 {
29
30HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props);
31
32}}
33
34static void *SzAlloc(void *, size_t size) { return MyAlloc(size); }
35static void SzFree(void *, void *address) { MyFree(address); }
36static ISzAlloc g_Alloc = { SzAlloc, SzFree };
37
38namespace NArchive {
39namespace NXz {
40
41struct CCrc64Gen { CCrc64Gen() { Crc64GenerateTable(); } } g_Crc64TableInit;
42
43class CHandler:
44  public IInArchive,
45  public IArchiveOpenSeq,
46  #ifndef EXTRACT_ONLY
47  public IOutArchive,
48  public ISetProperties,
49  public COutHandler,
50  #endif
51  public CMyUnknownImp
52{
53  Int64 _startPosition;
54  UInt64 _packSize;
55  UInt64 _unpackSize;
56  UInt64 _numBlocks;
57  AString _methodsString;
58  bool _useSeq;
59  UInt64 _unpackSizeDefined;
60  UInt64 _packSizeDefined;
61
62  CMyComPtr<IInStream> _stream;
63  CMyComPtr<ISequentialInStream> _seqStream;
64
65  UInt32 _crcSize;
66
67  void Init()
68  {
69    _crcSize = 4;
70    COutHandler::Init();
71  }
72
73  HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *callback);
74
75public:
76  MY_QUERYINTERFACE_BEGIN2(IInArchive)
77  MY_QUERYINTERFACE_ENTRY(IArchiveOpenSeq)
78  #ifndef EXTRACT_ONLY
79  MY_QUERYINTERFACE_ENTRY(IOutArchive)
80  MY_QUERYINTERFACE_ENTRY(ISetProperties)
81  #endif
82  MY_QUERYINTERFACE_END
83  MY_ADDREF_RELEASE
84
85  INTERFACE_IInArchive(;)
86  STDMETHOD(OpenSeq)(ISequentialInStream *stream);
87
88  #ifndef EXTRACT_ONLY
89  INTERFACE_IOutArchive(;)
90  STDMETHOD(SetProperties)(const wchar_t **names, const PROPVARIANT *values, Int32 numProps);
91  #endif
92
93  CHandler();
94};
95
96CHandler::CHandler()
97{
98  Init();
99}
100
101STATPROPSTG kProps[] =
102{
103  { NULL, kpidSize, VT_UI8},
104  { NULL, kpidPackSize, VT_UI8},
105  { NULL, kpidMethod, VT_BSTR}
106};
107
108STATPROPSTG kArcProps[] =
109{
110  { NULL, kpidMethod, VT_BSTR},
111  { NULL, kpidNumBlocks, VT_UI4}
112};
113
114IMP_IInArchive_Props
115IMP_IInArchive_ArcProps
116
117static char GetHex(Byte value)
118{
119  return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
120}
121
122static inline void AddHexToString(AString &res, Byte value)
123{
124  res += GetHex((Byte)(value >> 4));
125  res += GetHex((Byte)(value & 0xF));
126}
127
128static AString ConvertUInt32ToString(UInt32 value)
129{
130  char temp[32];
131  ::ConvertUInt32ToString(value, temp);
132  return temp;
133}
134
135static AString Lzma2PropToString(int prop)
136{
137  if ((prop & 1) == 0)
138    return ConvertUInt32ToString(prop / 2 + 12);
139  AString res;
140  char c;
141
142  UInt32 size = (2 | ((prop) & 1)) << ((prop) / 2 + 1);
143
144  if (prop > 17)
145  {
146    res = ConvertUInt32ToString(size >> 10);
147    c = 'm';
148  }
149  else
150  {
151    res = ConvertUInt32ToString(size);
152    c = 'k';
153  }
154  return res + c;
155}
156
157struct CMethodNamePair
158{
159  UInt32 Id;
160  const char *Name;
161};
162
163static CMethodNamePair g_NamePairs[] =
164{
165  { XZ_ID_Subblock, "SB" },
166  { XZ_ID_Delta, "Delta" },
167  { XZ_ID_X86, "x86" },
168  { XZ_ID_PPC, "PPC" },
169  { XZ_ID_IA64, "IA64" },
170  { XZ_ID_ARM, "ARM" },
171  { XZ_ID_ARMT, "ARMT" },
172  { XZ_ID_SPARC, "SPARC" },
173  { XZ_ID_LZMA2, "LZMA2" }
174};
175
176static AString GetMethodString(const CXzFilter &f)
177{
178  AString s;
179
180  for (int i = 0; i < sizeof(g_NamePairs) / sizeof(g_NamePairs[i]); i++)
181    if (g_NamePairs[i].Id == f.id)
182      s = g_NamePairs[i].Name;
183  if (s.IsEmpty())
184  {
185    char temp[32];
186    ::ConvertUInt64ToString(f.id, temp);
187    s = temp;
188  }
189
190  if (f.propsSize > 0)
191  {
192    s += ':';
193    if (f.id == XZ_ID_LZMA2 && f.propsSize == 1)
194      s += Lzma2PropToString(f.props[0]);
195    else if (f.id == XZ_ID_Delta && f.propsSize == 1)
196      s += ConvertUInt32ToString((UInt32)f.props[0] + 1);
197    else
198    {
199      s += '[';
200      for (UInt32 bi = 0; bi < f.propsSize; bi++)
201        AddHexToString(s, f.props[bi]);
202      s += ']';
203    }
204  }
205  return s;
206}
207
208static void AddString(AString &dest, const AString &src)
209{
210  if (!dest.IsEmpty())
211    dest += ' ';
212  dest += src;
213}
214
215static const char *kChecks[] =
216{
217  "NoCheck",
218  "CRC32",
219  NULL,
220  NULL,
221  "CRC64",
222  NULL,
223  NULL,
224  NULL,
225  NULL,
226  NULL,
227  "SHA256",
228  NULL,
229  NULL,
230  NULL,
231  NULL,
232  NULL
233};
234
235static AString GetCheckString(const CXzs &xzs)
236{
237  size_t i;
238  UInt32 mask = 0;
239  for (i = 0; i < xzs.num; i++)
240    mask |= ((UInt32)1 << XzFlags_GetCheckType(xzs.streams[i].flags));
241  AString s;
242  for (i = 0; i <= XZ_CHECK_MASK; i++)
243    if (((mask >> i) & 1) != 0)
244    {
245      AString s2;
246      if (kChecks[i])
247        s2 = kChecks[i];
248      else
249        s2 = "Check-" + ConvertUInt32ToString((UInt32)i);
250      AddString(s, s2);
251    }
252  return s;
253}
254
255STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
256{
257  COM_TRY_BEGIN
258  NWindows::NCOM::CPropVariant prop;
259  switch(propID)
260  {
261    case kpidNumBlocks: if (!_useSeq) prop = _numBlocks; break;
262    case kpidPhySize: if (_packSizeDefined) prop = _packSize; break;
263    case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break;
264  }
265  prop.Detach(value);
266  return S_OK;
267  COM_TRY_END
268}
269
270STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
271{
272  *numItems = 1;
273  return S_OK;
274}
275
276STDMETHODIMP CHandler::GetProperty(UInt32, PROPID propID,  PROPVARIANT *value)
277{
278  COM_TRY_BEGIN
279  NWindows::NCOM::CPropVariant prop;
280  switch(propID)
281  {
282    case kpidSize: if (_unpackSizeDefined) prop = _unpackSize; break;
283    case kpidPackSize: if (_packSizeDefined) prop = _packSize; break;
284    case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break;
285  }
286  prop.Detach(value);
287  return S_OK;
288  COM_TRY_END
289}
290
291
292struct COpenCallbackWrap
293{
294  ICompressProgress p;
295  IArchiveOpenCallback *OpenCallback;
296  HRESULT Res;
297  COpenCallbackWrap(IArchiveOpenCallback *progress);
298};
299
300static SRes OpenCallbackProgress(void *pp, UInt64 inSize, UInt64 /* outSize */)
301{
302  COpenCallbackWrap *p = (COpenCallbackWrap *)pp;
303  p->Res = p->OpenCallback->SetCompleted(NULL, &inSize);
304  return (SRes)p->Res;
305}
306
307COpenCallbackWrap::COpenCallbackWrap(IArchiveOpenCallback *callback)
308{
309  p.Progress = OpenCallbackProgress;
310  OpenCallback = callback;
311  Res = SZ_OK;
312}
313
314struct CXzsCPP
315{
316  CXzs p;
317  CXzsCPP() { Xzs_Construct(&p); }
318  ~CXzsCPP() { Xzs_Free(&p, &g_Alloc); }
319};
320
321HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback)
322{
323  CSeekInStreamWrap inStreamImp(inStream);
324
325  CLookToRead lookStream;
326  LookToRead_CreateVTable(&lookStream, True);
327  lookStream.realStream = &inStreamImp.p;
328  LookToRead_Init(&lookStream);
329
330  COpenCallbackWrap openWrap(callback);
331  RINOK(inStream->Seek(0, STREAM_SEEK_END, &_packSize));
332  RINOK(callback->SetTotal(NULL, &_packSize));
333
334  CXzsCPP xzs;
335  SRes res = Xzs_ReadBackward(&xzs.p, &lookStream.s, &_startPosition, &openWrap.p, &g_Alloc);
336  if (res == SZ_ERROR_NO_ARCHIVE && xzs.p.num > 0)
337    res = SZ_OK;
338  if (res == SZ_OK)
339  {
340    _packSize -= _startPosition;
341    _unpackSize = Xzs_GetUnpackSize(&xzs.p);
342    _unpackSizeDefined = _packSizeDefined = true;
343    _numBlocks = (UInt64)Xzs_GetNumBlocks(&xzs.p);
344
345    RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
346    CXzStreamFlags st;
347    CSeqInStreamWrap inStreamWrap(inStream);
348    SRes res2 = Xz_ReadHeader(&st, &inStreamWrap.p);
349
350    if (res2 == SZ_OK)
351    {
352      CXzBlock block;
353      Bool isIndex;
354      UInt32 headerSizeRes;
355      res2 = XzBlock_ReadHeader(&block, &inStreamWrap.p, &isIndex, &headerSizeRes);
356      if (res2 == SZ_OK && !isIndex)
357      {
358        int numFilters = XzBlock_GetNumFilters(&block);
359        for (int i = 0; i < numFilters; i++)
360          AddString(_methodsString, GetMethodString(block.filters[i]));
361      }
362    }
363    AddString(_methodsString, GetCheckString(xzs.p));
364  }
365
366  if (res != SZ_OK || _startPosition != 0)
367  {
368    RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
369    CXzStreamFlags st;
370    CSeqInStreamWrap inStreamWrap(inStream);
371    SRes res2 = Xz_ReadHeader(&st, &inStreamWrap.p);
372    if (res2 == SZ_OK)
373    {
374      res = res2;
375      _startPosition = 0;
376      _useSeq = True;
377      _unpackSizeDefined = _packSizeDefined = false;
378    }
379  }
380  if (res == SZ_ERROR_NO_ARCHIVE)
381    return S_FALSE;
382  RINOK(SResToHRESULT(res));
383  _stream = inStream;
384  _seqStream = inStream;
385  return S_OK;
386}
387
388STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)
389{
390  COM_TRY_BEGIN
391  try
392  {
393    Close();
394    return Open2(inStream, callback);
395  }
396  catch(...) { return S_FALSE; }
397  COM_TRY_END
398}
399
400STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream)
401{
402  Close();
403  _seqStream = stream;
404  return S_OK;
405}
406
407STDMETHODIMP CHandler::Close()
408{
409  _numBlocks = 0;
410  _useSeq = true;
411  _unpackSizeDefined = _packSizeDefined = false;
412  _methodsString.Empty();
413  _stream.Release();
414  _seqStream.Release();
415  return S_OK;
416}
417
418class CSeekToSeqStream:
419  public IInStream,
420  public CMyUnknownImp
421{
422public:
423  CMyComPtr<ISequentialInStream> Stream;
424  MY_UNKNOWN_IMP1(IInStream)
425
426  STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
427  STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
428};
429
430STDMETHODIMP CSeekToSeqStream::Read(void *data, UInt32 size, UInt32 *processedSize)
431{
432  return Stream->Read(data, size, processedSize);
433}
434
435STDMETHODIMP CSeekToSeqStream::Seek(Int64, UInt32, UInt64 *) { return E_NOTIMPL; }
436
437struct CXzUnpackerCPP
438{
439  Byte *InBuf;
440  Byte *OutBuf;
441  CXzUnpacker p;
442  CXzUnpackerCPP(): InBuf(0), OutBuf(0) {}
443  ~CXzUnpackerCPP()
444  {
445    XzUnpacker_Free(&p);
446    MyFree(InBuf);
447    MyFree(OutBuf);
448  }
449};
450
451STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
452    Int32 testMode, IArchiveExtractCallback *extractCallback)
453{
454  COM_TRY_BEGIN
455  if (numItems == 0)
456    return S_OK;
457  if (numItems != (UInt32)-1 && (numItems != 1 || indices[0] != 0))
458    return E_INVALIDARG;
459
460  extractCallback->SetTotal(_packSize);
461  UInt64 currentTotalPacked = 0;
462  RINOK(extractCallback->SetCompleted(&currentTotalPacked));
463  CMyComPtr<ISequentialOutStream> realOutStream;
464  Int32 askMode = testMode ?
465      NExtract::NAskMode::kTest :
466      NExtract::NAskMode::kExtract;
467
468  RINOK(extractCallback->GetStream(0, &realOutStream, askMode));
469
470  if (!testMode && !realOutStream)
471    return S_OK;
472
473  extractCallback->PrepareOperation(askMode);
474
475  if (_stream)
476  {
477    RINOK(_stream->Seek(_startPosition, STREAM_SEEK_SET, NULL));
478  }
479
480  CLocalProgress *lps = new CLocalProgress;
481  CMyComPtr<ICompressProgressInfo> progress = lps;
482  lps->Init(extractCallback, true);
483
484  CCompressProgressWrap progressWrap(progress);
485
486  SRes res;
487
488  const UInt32 kInBufSize = 1 << 15;
489  const UInt32 kOutBufSize = 1 << 21;
490
491  UInt32 inPos = 0;
492  UInt32 inSize = 0;
493  UInt32 outPos = 0;
494  CXzUnpackerCPP xzu;
495  res = XzUnpacker_Create(&xzu.p, &g_Alloc);
496  if (res == SZ_OK)
497  {
498    xzu.InBuf = (Byte *)MyAlloc(kInBufSize);
499    xzu.OutBuf = (Byte *)MyAlloc(kOutBufSize);
500    if (xzu.InBuf == 0 || xzu.OutBuf == 0)
501      res = SZ_ERROR_MEM;
502  }
503  if (res == SZ_OK)
504  for (;;)
505  {
506    if (inPos == inSize)
507    {
508      inPos = inSize = 0;
509      RINOK(_seqStream->Read(xzu.InBuf, kInBufSize, &inSize));
510    }
511
512    SizeT inLen = inSize - inPos;
513    SizeT outLen = kOutBufSize - outPos;
514    ECoderStatus status;
515    res = XzUnpacker_Code(&xzu.p,
516        xzu.OutBuf + outPos, &outLen,
517        xzu.InBuf + inPos, &inLen,
518        (inSize == 0 ? CODER_FINISH_END : CODER_FINISH_ANY), &status);
519
520    // printf("\n_inPos = %6d  inLen = %5d, outLen = %5d", inPos, inLen, outLen);
521
522    inPos += (UInt32)inLen;
523    outPos += (UInt32)outLen;
524    lps->InSize += inLen;
525    lps->OutSize += outLen;
526
527    bool finished = (((inLen == 0) && (outLen == 0)) || res != SZ_OK);
528
529    if (outPos == kOutBufSize || finished)
530    {
531      if (realOutStream && outPos > 0)
532      {
533        RINOK(WriteStream(realOutStream, xzu.OutBuf, outPos));
534      }
535      outPos = 0;
536    }
537    if (finished)
538    {
539      _packSize = lps->InSize;
540      _unpackSize = lps->OutSize;
541      _packSizeDefined = _unpackSizeDefined = true;
542      if (res == SZ_OK)
543      {
544        if (status == CODER_STATUS_NEEDS_MORE_INPUT)
545        {
546          if (XzUnpacker_IsStreamWasFinished(&xzu.p))
547            _packSize -= xzu.p.padSize;
548          else
549            res = SZ_ERROR_DATA;
550        }
551        else
552          res = SZ_ERROR_DATA;
553      }
554      break;
555    }
556    RINOK(lps->SetCur());
557  }
558
559  Int32 opRes;
560  switch(res)
561  {
562    case SZ_OK:
563      opRes = NExtract::NOperationResult::kOK; break;
564    case SZ_ERROR_UNSUPPORTED:
565      opRes = NExtract::NOperationResult::kUnSupportedMethod; break;
566    case SZ_ERROR_CRC:
567      opRes = NExtract::NOperationResult::kCRCError; break;
568    case SZ_ERROR_DATA:
569    case SZ_ERROR_ARCHIVE:
570    case SZ_ERROR_NO_ARCHIVE:
571      opRes = NExtract::NOperationResult::kDataError; break;
572    default:
573      return SResToHRESULT(res);
574  }
575  realOutStream.Release();
576  RINOK(extractCallback->SetOperationResult(opRes));
577  return S_OK;
578  COM_TRY_END
579}
580
581#ifndef EXTRACT_ONLY
582
583STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType)
584{
585  *timeType = NFileTimeType::kUnix;
586  return S_OK;
587}
588
589STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
590    IArchiveUpdateCallback *updateCallback)
591{
592  CSeqOutStreamWrap seqOutStream(outStream);
593
594  if (numItems == 0)
595  {
596    SRes res = Xz_EncodeEmpty(&seqOutStream.p);
597    return SResToHRESULT(res);
598  }
599
600  if (numItems != 1)
601    return E_INVALIDARG;
602
603  Int32 newData, newProps;
604  UInt32 indexInArchive;
605  if (!updateCallback)
606    return E_FAIL;
607  RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive));
608
609  if (IntToBool(newProps))
610  {
611    {
612      NCOM::CPropVariant prop;
613      RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop));
614      if (prop.vt != VT_EMPTY)
615        if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE)
616          return E_INVALIDARG;
617    }
618  }
619
620  if (IntToBool(newData))
621  {
622    {
623      UInt64 size;
624      NCOM::CPropVariant prop;
625      RINOK(updateCallback->GetProperty(0, kpidSize, &prop));
626      if (prop.vt != VT_UI8)
627        return E_INVALIDARG;
628      size = prop.uhVal.QuadPart;
629      RINOK(updateCallback->SetTotal(size));
630    }
631
632    CLzma2EncProps lzma2Props;
633    Lzma2EncProps_Init(&lzma2Props);
634
635    lzma2Props.lzmaProps.level = _level;
636
637    CMyComPtr<ISequentialInStream> fileInStream;
638    RINOK(updateCallback->GetStream(0, &fileInStream));
639
640    CSeqInStreamWrap seqInStream(fileInStream);
641
642    for (int i = 0; i < _methods.Size(); i++)
643    {
644      COneMethodInfo &m = _methods[i];
645      SetCompressionMethod2(m
646      #ifndef _7ZIP_ST
647      , _numThreads
648      #endif
649      );
650      if (m.IsLzma())
651      {
652        for (int j = 0; j < m.Props.Size(); j++)
653        {
654          const CProp &prop = m.Props[j];
655          RINOK(NCompress::NLzma2::SetLzma2Prop(prop.Id, prop.Value, lzma2Props));
656        }
657      }
658    }
659
660    #ifndef _7ZIP_ST
661    lzma2Props.numTotalThreads = _numThreads;
662    #endif
663
664    CLocalProgress *lps = new CLocalProgress;
665    CMyComPtr<ICompressProgressInfo> progress = lps;
666    lps->Init(updateCallback, true);
667
668    CCompressProgressWrap progressWrap(progress);
669    SRes res = Xz_Encode(&seqOutStream.p, &seqInStream.p, &lzma2Props, False, &progressWrap.p);
670    if (res == SZ_OK)
671      return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK);
672    return SResToHRESULT(res);
673  }
674  if (indexInArchive != 0)
675    return E_INVALIDARG;
676  if (_stream)
677    RINOK(_stream->Seek(_startPosition, STREAM_SEEK_SET, NULL));
678  return NCompress::CopyStream(_stream, outStream, 0);
679}
680
681STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProps)
682{
683  COM_TRY_BEGIN
684  BeforeSetProperty();
685  for (int i = 0; i < numProps; i++)
686  {
687    RINOK(SetProperty(names[i], values[i]));
688  }
689  return S_OK;
690  COM_TRY_END
691}
692
693#endif
694
695static IInArchive *CreateArc() { return new NArchive::NXz::CHandler; }
696#ifndef EXTRACT_ONLY
697static IOutArchive *CreateArcOut() { return new NArchive::NXz::CHandler; }
698#else
699#define CreateArcOut 0
700#endif
701
702static CArcInfo g_ArcInfo =
703  { L"xz", L"xz txz", L"* .tar", 0xC, {0xFD, '7' , 'z', 'X', 'Z', '\0'}, 6, true, CreateArc, CreateArcOut };
704
705REGISTER_ARC(xz)
706
707}}
708