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 25 using namespace NWindows; 26 27 namespace NCompress { 28 namespace NLzma2 { 29 30 HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props); 31 32 }} 33 34 static void *SzAlloc(void *, size_t size) { return MyAlloc(size); } 35 static void SzFree(void *, void *address) { MyFree(address); } 36 static ISzAlloc g_Alloc = { SzAlloc, SzFree }; 37 38 namespace NArchive { 39 namespace NXz { 40 41 struct CCrc64Gen { CCrc64Gen() { Crc64GenerateTable(); } } g_Crc64TableInit; 42 43 class 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 75 public: 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 96 CHandler::CHandler() 97 { 98 Init(); 99 } 100 101 STATPROPSTG kProps[] = 102 { 103 { NULL, kpidSize, VT_UI8}, 104 { NULL, kpidPackSize, VT_UI8}, 105 { NULL, kpidMethod, VT_BSTR} 106 }; 107 108 STATPROPSTG kArcProps[] = 109 { 110 { NULL, kpidMethod, VT_BSTR}, 111 { NULL, kpidNumBlocks, VT_UI4} 112 }; 113 114 IMP_IInArchive_Props 115 IMP_IInArchive_ArcProps 116 117 static char GetHex(Byte value) 118 { 119 return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); 120 } 121 122 static inline void AddHexToString(AString &res, Byte value) 123 { 124 res += GetHex((Byte)(value >> 4)); 125 res += GetHex((Byte)(value & 0xF)); 126 } 127 128 static AString ConvertUInt32ToString(UInt32 value) 129 { 130 char temp[32]; 131 ::ConvertUInt32ToString(value, temp); 132 return temp; 133 } 134 135 static 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 157 struct CMethodNamePair 158 { 159 UInt32 Id; 160 const char *Name; 161 }; 162 163 static 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 176 static 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 208 static void AddString(AString &dest, const AString &src) 209 { 210 if (!dest.IsEmpty()) 211 dest += ' '; 212 dest += src; 213 } 214 215 static 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 235 static 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 255 STDMETHODIMP 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 270 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) 271 { 272 *numItems = 1; 273 return S_OK; 274 } 275 276 STDMETHODIMP 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 292 struct COpenCallbackWrap 293 { 294 ICompressProgress p; 295 IArchiveOpenCallback *OpenCallback; 296 HRESULT Res; 297 COpenCallbackWrap(IArchiveOpenCallback *progress); 298 }; 299 300 static 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 307 COpenCallbackWrap::COpenCallbackWrap(IArchiveOpenCallback *callback) 308 { 309 p.Progress = OpenCallbackProgress; 310 OpenCallback = callback; 311 Res = SZ_OK; 312 } 313 314 struct CXzsCPP 315 { 316 CXzs p; 317 CXzsCPP() { Xzs_Construct(&p); } 318 ~CXzsCPP() { Xzs_Free(&p, &g_Alloc); } 319 }; 320 321 HRESULT 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 388 STDMETHODIMP 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 400 STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) 401 { 402 Close(); 403 _seqStream = stream; 404 return S_OK; 405 } 406 407 STDMETHODIMP 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 418 class CSeekToSeqStream: 419 public IInStream, 420 public CMyUnknownImp 421 { 422 public: 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 430 STDMETHODIMP CSeekToSeqStream::Read(void *data, UInt32 size, UInt32 *processedSize) 431 { 432 return Stream->Read(data, size, processedSize); 433 } 434 435 STDMETHODIMP CSeekToSeqStream::Seek(Int64, UInt32, UInt64 *) { return E_NOTIMPL; } 436 437 struct 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 451 STDMETHODIMP 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(¤tTotalPacked)); 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 583 STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) 584 { 585 *timeType = NFileTimeType::kUnix; 586 return S_OK; 587 } 588 589 STDMETHODIMP 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 681 STDMETHODIMP 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 695 static IInArchive *CreateArc() { return new NArchive::NXz::CHandler; } 696 #ifndef EXTRACT_ONLY 697 static IOutArchive *CreateArcOut() { return new NArchive::NXz::CHandler; } 698 #else 699 #define CreateArcOut 0 700 #endif 701 702 static CArcInfo g_ArcInfo = 703 { L"xz", L"xz txz", L"* .tar", 0xC, {0xFD, '7' , 'z', 'X', 'Z', '\0'}, 6, true, CreateArc, CreateArcOut }; 704 705 REGISTER_ARC(xz) 706 707 }} 708