// ZipUpdate.cpp #include "StdAfx.h" #include "../../../../C/Alloc.h" #include "../../../Common/AutoPtr.h" #include "../../../Common/Defs.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/TimeUtils.h" #include "../../../Windows/Thread.h" #include "../../Common/CreateCoder.h" #include "../../Common/LimitedStreams.h" #include "../../Common/OutMemStream.h" #include "../../Common/ProgressUtils.h" #ifndef _7ZIP_ST #include "../../Common/ProgressMt.h" #endif #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" #include "ZipAddCommon.h" #include "ZipOut.h" #include "ZipUpdate.h" using namespace NWindows; using namespace NSynchronization; namespace NArchive { namespace NZip { static const Byte kHostOS = #ifdef _WIN32 NFileHeader::NHostOS::kFAT; #else NFileHeader::NHostOS::kUnix; #endif static const Byte kMadeByHostOS = kHostOS; static const Byte kExtractHostOS = kHostOS; static const Byte kMethodForDirectory = NFileHeader::NCompressionMethod::kStored; static HRESULT CopyBlockToArchive(ISequentialInStream *inStream, UInt64 size, COutArchive &outArchive, ICompressProgressInfo *progress) { CMyComPtr outStream; outArchive.CreateStreamForCopying(&outStream); return NCompress::CopyStream_ExactSize(inStream, outStream, size, progress); } static void SetFileHeader( COutArchive &archive, const CCompressionMethodMode &options, const CUpdateItem &ui, // bool isSeqMode, CItemOut &item) { item.Size = ui.Size; bool isDir; item.ClearFlags(); if (ui.NewProps) { isDir = ui.IsDir; item.Name = ui.Name; item.SetUtf8(ui.IsUtf8); item.ExternalAttrib = ui.Attrib; item.Time = ui.Time; item.Ntfs_MTime = ui.Ntfs_MTime; item.Ntfs_ATime = ui.Ntfs_ATime; item.Ntfs_CTime = ui.Ntfs_CTime; item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined; } else isDir = item.IsDir(); item.LocalHeaderPos = archive.GetCurPos(); item.MadeByVersion.HostOS = kMadeByHostOS; item.MadeByVersion.Version = NFileHeader::NCompressionMethod::kMadeByProgramVersion; item.ExtractVersion.HostOS = kExtractHostOS; item.InternalAttrib = 0; // test it item.SetEncrypted(!isDir && options.PasswordIsDefined); // item.SetDescriptorMode(isSeqMode); if (isDir) { item.ExtractVersion.Version = NFileHeader::NCompressionMethod::kExtractVersion_Dir; item.Method = kMethodForDirectory; item.PackSize = 0; item.Size = 0; item.Crc = 0; } } static void SetItemInfoFromCompressingResult(const CCompressingResult &compressingResult, bool isAesMode, Byte aesKeyMode, CItem &item) { item.ExtractVersion.Version = compressingResult.ExtractVersion; item.Method = compressingResult.Method; item.Crc = compressingResult.CRC; item.Size = compressingResult.UnpackSize; item.PackSize = compressingResult.PackSize; item.LocalExtra.Clear(); item.CentralExtra.Clear(); if (isAesMode) { CWzAesExtra wzAesField; wzAesField.Strength = aesKeyMode; wzAesField.Method = compressingResult.Method; item.Method = NFileHeader::NCompressionMethod::kWzAES; item.Crc = 0; CExtraSubBlock sb; wzAesField.SetSubBlock(sb); item.LocalExtra.SubBlocks.Add(sb); item.CentralExtra.SubBlocks.Add(sb); } } #ifndef _7ZIP_ST static THREAD_FUNC_DECL CoderThread(void *threadCoderInfo); struct CThreadInfo { DECL_EXTERNAL_CODECS_LOC_VARS2; NWindows::CThread Thread; NWindows::NSynchronization::CAutoResetEvent CompressEvent; NWindows::NSynchronization::CAutoResetEvent CompressionCompletedEvent; bool ExitThread; CMtCompressProgress *ProgressSpec; CMyComPtr Progress; COutMemStream *OutStreamSpec; CMyComPtr OutStream; CMyComPtr InStream; CAddCommon Coder; HRESULT Result; CCompressingResult CompressingResult; bool IsFree; UInt32 UpdateIndex; UInt32 FileTime; CThreadInfo(const CCompressionMethodMode &options): ExitThread(false), ProgressSpec(0), OutStreamSpec(0), Coder(options), FileTime(0) {} HRESULT CreateEvents() { RINOK(CompressEvent.CreateIfNotCreated()); return CompressionCompletedEvent.CreateIfNotCreated(); } HRes CreateThread() { return Thread.Create(CoderThread, this); } void WaitAndCode(); void StopWaitClose() { ExitThread = true; if (OutStreamSpec != 0) OutStreamSpec->StopWriting(E_ABORT); if (CompressEvent.IsCreated()) CompressEvent.Set(); Thread.Wait(); Thread.Close(); } }; void CThreadInfo::WaitAndCode() { for (;;) { CompressEvent.Lock(); if (ExitThread) return; Result = Coder.Compress( EXTERNAL_CODECS_LOC_VARS InStream, OutStream, FileTime, Progress, CompressingResult); if (Result == S_OK && Progress) Result = Progress->SetRatioInfo(&CompressingResult.UnpackSize, &CompressingResult.PackSize); CompressionCompletedEvent.Set(); } } static THREAD_FUNC_DECL CoderThread(void *threadCoderInfo) { ((CThreadInfo *)threadCoderInfo)->WaitAndCode(); return 0; } class CThreads { public: CObjectVector Threads; ~CThreads() { FOR_VECTOR (i, Threads) Threads[i].StopWaitClose(); } }; struct CMemBlocks2: public CMemLockBlocks { CCompressingResult CompressingResult; bool Defined; bool Skip; CMemBlocks2(): Defined(false), Skip(false) {} }; class CMemRefs { public: CMemBlockManagerMt *Manager; CObjectVector Refs; CMemRefs(CMemBlockManagerMt *manager): Manager(manager) {} ; ~CMemRefs() { FOR_VECTOR (i, Refs) Refs[i].FreeOpt(Manager); } }; class CMtProgressMixer2: public ICompressProgressInfo, public CMyUnknownImp { UInt64 ProgressOffset; UInt64 InSizes[2]; UInt64 OutSizes[2]; CMyComPtr Progress; CMyComPtr RatioProgress; bool _inSizeIsMain; public: NWindows::NSynchronization::CCriticalSection CriticalSection; MY_UNKNOWN_IMP void Create(IProgress *progress, bool inSizeIsMain); void SetProgressOffset(UInt64 progressOffset); HRESULT SetRatioInfo(unsigned index, const UInt64 *inSize, const UInt64 *outSize); STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; void CMtProgressMixer2::Create(IProgress *progress, bool inSizeIsMain) { Progress = progress; Progress.QueryInterface(IID_ICompressProgressInfo, &RatioProgress); _inSizeIsMain = inSizeIsMain; ProgressOffset = InSizes[0] = InSizes[1] = OutSizes[0] = OutSizes[1] = 0; } void CMtProgressMixer2::SetProgressOffset(UInt64 progressOffset) { CriticalSection.Enter(); InSizes[1] = OutSizes[1] = 0; ProgressOffset = progressOffset; CriticalSection.Leave(); } HRESULT CMtProgressMixer2::SetRatioInfo(unsigned index, const UInt64 *inSize, const UInt64 *outSize) { NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); if (index == 0 && RatioProgress) { RINOK(RatioProgress->SetRatioInfo(inSize, outSize)); } if (inSize) InSizes[index] = *inSize; if (outSize) OutSizes[index] = *outSize; UInt64 v = ProgressOffset + (_inSizeIsMain ? (InSizes[0] + InSizes[1]) : (OutSizes[0] + OutSizes[1])); return Progress->SetCompleted(&v); } STDMETHODIMP CMtProgressMixer2::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) { return SetRatioInfo(0, inSize, outSize); } class CMtProgressMixer: public ICompressProgressInfo, public CMyUnknownImp { public: CMtProgressMixer2 *Mixer2; CMyComPtr RatioProgress; void Create(IProgress *progress, bool inSizeIsMain); MY_UNKNOWN_IMP STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; void CMtProgressMixer::Create(IProgress *progress, bool inSizeIsMain) { Mixer2 = new CMtProgressMixer2; RatioProgress = Mixer2; Mixer2->Create(progress, inSizeIsMain); } STDMETHODIMP CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) { return Mixer2->SetRatioInfo(1, inSize, outSize); } #endif static HRESULT UpdateItemOldData( COutArchive &archive, CInArchive *inArchive, const CItemEx &itemEx, const CUpdateItem &ui, CItemOut &item, /* bool izZip64, */ ICompressProgressInfo *progress, IArchiveUpdateCallbackFile *opCallback, UInt64 &complexity) { if (opCallback) { RINOK(opCallback->ReportOperation( NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc, NUpdateNotifyOp::kReplicate)) } if (ui.NewProps) { if (item.HasDescriptor()) return E_NOTIMPL; // use old name size. CMyComPtr packStream; RINOK(inArchive->GetItemStream(itemEx, true, packStream)); if (!packStream) return E_NOTIMPL; // we keep ExternalAttrib and some another properties from old archive // item.ExternalAttrib = ui.Attrib; item.Name = ui.Name; item.SetUtf8(ui.IsUtf8); item.Time = ui.Time; item.Ntfs_MTime = ui.Ntfs_MTime; item.Ntfs_ATime = ui.Ntfs_ATime; item.Ntfs_CTime = ui.Ntfs_CTime; item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined; item.CentralExtra.RemoveUnknownSubBlocks(); item.LocalExtra.RemoveUnknownSubBlocks(); item.LocalHeaderPos = archive.GetCurPos(); archive.PrepareWriteCompressedData2(item.Name.Len(), item.Size, item.PackSize, item.LocalExtra.HasWzAes()); archive.WriteLocalHeader(item); RINOK(CopyBlockToArchive(packStream, itemEx.PackSize, archive, progress)); complexity += itemEx.PackSize; } else { CMyComPtr packStream; RINOK(inArchive->GetItemStream(itemEx, false, packStream)); if (!packStream) return E_NOTIMPL; // set new header position item.LocalHeaderPos = archive.GetCurPos(); const UInt64 rangeSize = itemEx.GetLocalFullSize(); RINOK(CopyBlockToArchive(packStream, rangeSize, archive, progress)); complexity += rangeSize; archive.MoveCurPos(rangeSize); } return S_OK; } static void WriteDirHeader(COutArchive &archive, const CCompressionMethodMode *options, const CUpdateItem &ui, CItemOut &item) { SetFileHeader(archive, *options, ui, item); archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, // options->IsRealAesMode() false // fixed 9.31 ); archive.WriteLocalHeader_And_SeekToNextFile(item); } static inline bool IsZero_FILETIME(const FILETIME &ft) { return (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0); } static void UpdatePropsFromStream(CUpdateItem &item, ISequentialInStream *fileInStream, IArchiveUpdateCallback *updateCallback, UInt64 &totalComplexity) { CMyComPtr getProps; fileInStream->QueryInterface(IID_IStreamGetProps, (void **)&getProps); if (!getProps) return; FILETIME cTime, aTime, mTime; UInt64 size; // UInt32 attrib; if (getProps->GetProps(&size, &cTime, &aTime, &mTime, NULL) != S_OK) return; if (size != item.Size && size != (UInt64)(Int64)-1) { Int64 newComplexity = totalComplexity + ((Int64)size - (Int64)item.Size); if (newComplexity > 0) { totalComplexity = newComplexity; updateCallback->SetTotal(totalComplexity); } item.Size = size; } if (!IsZero_FILETIME(mTime)) { item.Ntfs_MTime = mTime; FILETIME loc = { 0, 0 }; if (FileTimeToLocalFileTime(&mTime, &loc)) { item.Time = 0; NTime::FileTimeToDosTime(loc, item.Time); } } if (!IsZero_FILETIME(cTime)) item.Ntfs_CTime = cTime; if (!IsZero_FILETIME(aTime)) item.Ntfs_ATime = aTime; // item.Attrib = attrib; } static HRESULT Update2St( DECL_EXTERNAL_CODECS_LOC_VARS COutArchive &archive, CInArchive *inArchive, const CObjectVector &inputItems, CObjectVector &updateItems, const CCompressionMethodMode *options, const CByteBuffer *comment, IArchiveUpdateCallback *updateCallback, UInt64 &totalComplexity, IArchiveUpdateCallbackFile *opCallback) { CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(updateCallback, true); CAddCommon compressor(*options); CObjectVector items; UInt64 unpackSizeTotal = 0, packSizeTotal = 0; FOR_VECTOR (itemIndex, updateItems) { lps->InSize = unpackSizeTotal; lps->OutSize = packSizeTotal; RINOK(lps->SetCur()); CUpdateItem &ui = updateItems[itemIndex]; CItemEx itemEx; CItemOut item; if (!ui.NewProps || !ui.NewData) { itemEx = inputItems[ui.IndexInArc]; if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; } if (ui.NewData) { bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); if (isDir) { WriteDirHeader(archive, options, ui, item); } else { CMyComPtr fileInStream; HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) { lps->ProgressOffset += ui.Size; RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); continue; } RINOK(res); if (!fileInStream) return E_INVALIDARG; // bool isSeqMode = false; /* { CMyComPtr inStream2; fileInStream->QueryInterface(IID_IInStream, (void **)&inStream2); isSeqMode = (inStream2 == NULL); } */ UpdatePropsFromStream(ui, fileInStream, updateCallback, totalComplexity); SetFileHeader(archive, *options, ui, item); // file Size can be 64-bit !!! archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, options->IsRealAesMode()); CCompressingResult compressingResult; CMyComPtr outStream; archive.CreateStreamForCompressing(&outStream); RINOK(compressor.Compress( EXTERNAL_CODECS_LOC_VARS fileInStream, outStream, ui.Time, progress, compressingResult)); if (compressingResult.FileTimeWasUsed) { /* if (!item.HasDescriptor()) return E_FAIL; */ item.SetDescriptorMode(true); } SetItemInfoFromCompressingResult(compressingResult, options->IsRealAesMode(), options->AesKeyMode, item); archive.WriteLocalHeader_And_SeekToNextFile(item); RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); unpackSizeTotal += item.Size; packSizeTotal += item.PackSize; } } else { UInt64 complexity = 0; lps->SendRatio = false; RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)); lps->SendRatio = true; lps->ProgressOffset += complexity; } items.Add(item); lps->ProgressOffset += kLocalHeaderSize; } lps->InSize = unpackSizeTotal; lps->OutSize = packSizeTotal; RINOK(lps->SetCur()); archive.WriteCentralDir(items, comment); return S_OK; } static HRESULT Update2( DECL_EXTERNAL_CODECS_LOC_VARS COutArchive &archive, CInArchive *inArchive, const CObjectVector &inputItems, CObjectVector &updateItems, const CCompressionMethodMode &options, const CByteBuffer *comment, IArchiveUpdateCallback *updateCallback) { CMyComPtr opCallback; updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); UInt64 complexity = 0; UInt64 numFilesToCompress = 0; UInt64 numBytesToCompress = 0; unsigned i; for (i = 0; i < updateItems.Size(); i++) { const CUpdateItem &ui = updateItems[i]; if (ui.NewData) { complexity += ui.Size; numBytesToCompress += ui.Size; numFilesToCompress++; /* if (ui.Commented) complexity += ui.CommentRange.Size; */ } else { CItemEx inputItem = inputItems[ui.IndexInArc]; if (inArchive->ReadLocalItemAfterCdItemFull(inputItem) != S_OK) return E_NOTIMPL; complexity += inputItem.GetLocalFullSize(); // complexity += inputItem.GetCentralExtraPlusCommentSize(); } complexity += kLocalHeaderSize; complexity += kCentralHeaderSize; } if (comment) complexity += comment->Size(); complexity++; // end of central updateCallback->SetTotal(complexity); UInt64 totalComplexity = complexity; CAddCommon compressor(options); complexity = 0; CCompressionMethodMode options2 = options; #ifndef _7ZIP_ST UInt32 numThreads = options.NumThreads; const UInt32 kNumMaxThreads = 64; if (numThreads > kNumMaxThreads) numThreads = kNumMaxThreads; if (numThreads > MAXIMUM_WAIT_OBJECTS) // is 64 in Windows (is it 64 in all versions?) numThreads = MAXIMUM_WAIT_OBJECTS; if (numThreads < 1) numThreads = 1; const size_t kMemPerThread = (1 << 25); const size_t kBlockSize = 1 << 16; bool mtMode = (numThreads > 1); if (numFilesToCompress <= 1) mtMode = false; Byte method = options.MethodSequence.Front(); if (!mtMode) { if (options2.MethodInfo.FindProp(NCoderPropID::kNumThreads) < 0) { // fixed for 9.31. bzip2 default is just one thread. if (options2.NumThreadsWasChanged || method == NFileHeader::NCompressionMethod::kBZip2) options2.MethodInfo.AddProp_NumThreads(numThreads); } } else { if (method == NFileHeader::NCompressionMethod::kStored && !options.PasswordIsDefined) numThreads = 1; if (method == NFileHeader::NCompressionMethod::kBZip2) { bool fixedNumber; UInt32 numBZip2Threads = options2.MethodInfo.Get_BZip2_NumThreads(fixedNumber); if (!fixedNumber) { UInt64 averageSize = numBytesToCompress / numFilesToCompress; UInt32 blockSize = options2.MethodInfo.Get_BZip2_BlockSize(); UInt64 averageNumberOfBlocks = averageSize / blockSize + 1; numBZip2Threads = 32; if (averageNumberOfBlocks < numBZip2Threads) numBZip2Threads = (UInt32)averageNumberOfBlocks; options2.MethodInfo.AddProp_NumThreads(numBZip2Threads); } numThreads /= numBZip2Threads; } if (method == NFileHeader::NCompressionMethod::kLZMA) { bool fixedNumber; // we suppose that default LZMA is 2 thread. So we don't change it UInt32 numLZMAThreads = options2.MethodInfo.Get_Lzma_NumThreads(fixedNumber); numThreads /= numLZMAThreads; } if (numThreads > numFilesToCompress) numThreads = (UInt32)numFilesToCompress; if (numThreads <= 1) mtMode = false; } if (!mtMode) #endif return Update2St( EXTERNAL_CODECS_LOC_VARS archive, inArchive, inputItems, updateItems, &options2, comment, updateCallback, totalComplexity, opCallback); #ifndef _7ZIP_ST CObjectVector items; CMtProgressMixer *mtProgressMixerSpec = new CMtProgressMixer; CMyComPtr progress = mtProgressMixerSpec; mtProgressMixerSpec->Create(updateCallback, true); CMtCompressProgressMixer mtCompressProgressMixer; mtCompressProgressMixer.Init(numThreads, mtProgressMixerSpec->RatioProgress); CMemBlockManagerMt memManager(kBlockSize); CMemRefs refs(&memManager); CThreads threads; CRecordVector compressingCompletedEvents; CUIntVector threadIndices; // list threads in order of updateItems { RINOK(memManager.AllocateSpaceAlways((size_t)numThreads * (kMemPerThread / kBlockSize))); for (i = 0; i < updateItems.Size(); i++) refs.Refs.Add(CMemBlocks2()); for (i = 0; i < numThreads; i++) threads.Threads.Add(CThreadInfo(options2)); for (i = 0; i < numThreads; i++) { CThreadInfo &threadInfo = threads.Threads[i]; #ifdef EXTERNAL_CODECS threadInfo.__externalCodecs = __externalCodecs; #endif RINOK(threadInfo.CreateEvents()); threadInfo.OutStreamSpec = new COutMemStream(&memManager); RINOK(threadInfo.OutStreamSpec->CreateEvents()); threadInfo.OutStream = threadInfo.OutStreamSpec; threadInfo.IsFree = true; threadInfo.ProgressSpec = new CMtCompressProgress(); threadInfo.Progress = threadInfo.ProgressSpec; threadInfo.ProgressSpec->Init(&mtCompressProgressMixer, (int)i); threadInfo.FileTime = 0; // fix it ! RINOK(threadInfo.CreateThread()); } } unsigned mtItemIndex = 0; unsigned itemIndex = 0; int lastRealStreamItemIndex = -1; while (itemIndex < updateItems.Size()) { if (threadIndices.Size() < numThreads && mtItemIndex < updateItems.Size()) { CUpdateItem &ui = updateItems[mtItemIndex++]; if (!ui.NewData) continue; CItemEx itemEx; CItemOut item; if (ui.NewProps) { if (ui.IsDir) continue; } else { itemEx = inputItems[ui.IndexInArc]; if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; if (item.IsDir()) continue; } CMyComPtr fileInStream; { NWindows::NSynchronization::CCriticalSectionLock lock(mtProgressMixerSpec->Mixer2->CriticalSection); HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) { complexity += ui.Size; complexity += kLocalHeaderSize; mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity); RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); refs.Refs[mtItemIndex - 1].Skip = true; continue; } RINOK(res); if (!fileInStream) return E_INVALIDARG; UpdatePropsFromStream(ui, fileInStream, updateCallback, totalComplexity); RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); } for (UInt32 k = 0; k < numThreads; k++) { CThreadInfo &threadInfo = threads.Threads[k]; if (threadInfo.IsFree) { threadInfo.IsFree = false; threadInfo.InStream = fileInStream; // !!!!! we must release ref before sending event // BUG was here in v4.43 and v4.44. It could change ref counter in two threads in same time fileInStream.Release(); threadInfo.OutStreamSpec->Init(); threadInfo.ProgressSpec->Reinit(); threadInfo.CompressEvent.Set(); threadInfo.UpdateIndex = mtItemIndex - 1; compressingCompletedEvents.Add(threadInfo.CompressionCompletedEvent); threadIndices.Add(k); break; } } continue; } if (refs.Refs[itemIndex].Skip) { itemIndex++; continue; } const CUpdateItem &ui = updateItems[itemIndex]; CItemEx itemEx; CItemOut item; if (!ui.NewProps || !ui.NewData) { itemEx = inputItems[ui.IndexInArc]; if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; } if (ui.NewData) { bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); if (isDir) { WriteDirHeader(archive, &options, ui, item); } else { if (lastRealStreamItemIndex < (int)itemIndex) { lastRealStreamItemIndex = itemIndex; SetFileHeader(archive, options, ui, item); // file Size can be 64-bit !!! archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, options.IsRealAesMode()); } CMemBlocks2 &memRef = refs.Refs[itemIndex]; if (memRef.Defined) { CMyComPtr outStream; archive.CreateStreamForCompressing(&outStream); memRef.WriteToStream(memManager.GetBlockSize(), outStream); SetFileHeader(archive, options, ui, item); // the BUG was fixed in 9.26: // SetItemInfoFromCompressingResult must be after SetFileHeader // to write correct Size. SetItemInfoFromCompressingResult(memRef.CompressingResult, options.IsRealAesMode(), options.AesKeyMode, item); archive.WriteLocalHeader_And_SeekToNextFile(item); // RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); memRef.FreeOpt(&memManager); } else { { CThreadInfo &thread = threads.Threads[threadIndices.Front()]; if (!thread.OutStreamSpec->WasUnlockEventSent()) { CMyComPtr outStream; archive.CreateStreamForCompressing(&outStream); thread.OutStreamSpec->SetOutStream(outStream); thread.OutStreamSpec->SetRealStreamMode(); } } DWORD result = ::WaitForMultipleObjects(compressingCompletedEvents.Size(), &compressingCompletedEvents.Front(), FALSE, INFINITE); if (result == WAIT_FAILED) { DWORD lastError = GetLastError(); return lastError != 0 ? lastError : E_FAIL; } unsigned t = (unsigned)(result - WAIT_OBJECT_0); if (t >= compressingCompletedEvents.Size()) return E_FAIL; CThreadInfo &threadInfo = threads.Threads[threadIndices[t]]; threadInfo.InStream.Release(); threadInfo.IsFree = true; RINOK(threadInfo.Result); threadIndices.Delete(t); compressingCompletedEvents.Delete(t); if (t == 0) { RINOK(threadInfo.OutStreamSpec->WriteToRealStream()); threadInfo.OutStreamSpec->ReleaseOutStream(); SetFileHeader(archive, options, ui, item); SetItemInfoFromCompressingResult(threadInfo.CompressingResult, options.IsRealAesMode(), options.AesKeyMode, item); archive.WriteLocalHeader_And_SeekToNextFile(item); } else { CMemBlocks2 &memRef2 = refs.Refs[threadInfo.UpdateIndex]; threadInfo.OutStreamSpec->DetachData(memRef2); memRef2.CompressingResult = threadInfo.CompressingResult; memRef2.Defined = true; continue; } } } } else { RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)); } items.Add(item); complexity += kLocalHeaderSize; mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity); itemIndex++; } RINOK(mtCompressProgressMixer.SetRatioInfo(0, NULL, NULL)); archive.WriteCentralDir(items, comment); return S_OK; #endif } static const size_t kCacheBlockSize = (1 << 20); static const size_t kCacheSize = (kCacheBlockSize << 2); static const size_t kCacheMask = (kCacheSize - 1); class CCacheOutStream: public IOutStream, public CMyUnknownImp { CMyComPtr _stream; Byte *_cache; UInt64 _virtPos; UInt64 _virtSize; UInt64 _phyPos; UInt64 _phySize; // <= _virtSize UInt64 _cachedPos; // (_cachedPos + _cachedSize) <= _virtSize size_t _cachedSize; HRESULT MyWrite(size_t size); HRESULT MyWriteBlock() { return MyWrite(kCacheBlockSize - ((size_t)_cachedPos & (kCacheBlockSize - 1))); } HRESULT FlushCache(); public: CCacheOutStream(): _cache(0) {} ~CCacheOutStream(); bool Allocate(); HRESULT Init(IOutStream *stream); MY_UNKNOWN_IMP STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); STDMETHOD(SetSize)(UInt64 newSize); }; bool CCacheOutStream::Allocate() { if (!_cache) _cache = (Byte *)::MidAlloc(kCacheSize); return (_cache != NULL); } HRESULT CCacheOutStream::Init(IOutStream *stream) { _virtPos = _phyPos = 0; _stream = stream; RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &_virtPos)); RINOK(_stream->Seek(0, STREAM_SEEK_END, &_virtSize)); RINOK(_stream->Seek(_virtPos, STREAM_SEEK_SET, &_virtPos)); _phyPos = _virtPos; _phySize = _virtSize; _cachedPos = 0; _cachedSize = 0; return S_OK; } HRESULT CCacheOutStream::MyWrite(size_t size) { while (size != 0 && _cachedSize != 0) { if (_phyPos != _cachedPos) { RINOK(_stream->Seek(_cachedPos, STREAM_SEEK_SET, &_phyPos)); } size_t pos = (size_t)_cachedPos & kCacheMask; size_t curSize = MyMin(kCacheSize - pos, _cachedSize); curSize = MyMin(curSize, size); RINOK(WriteStream(_stream, _cache + pos, curSize)); _phyPos += curSize; if (_phySize < _phyPos) _phySize = _phyPos; _cachedPos += curSize; _cachedSize -= curSize; size -= curSize; } return S_OK; } HRESULT CCacheOutStream::FlushCache() { return MyWrite(_cachedSize); } CCacheOutStream::~CCacheOutStream() { FlushCache(); if (_virtSize != _phySize) _stream->SetSize(_virtSize); if (_virtPos != _phyPos) _stream->Seek(_virtPos, STREAM_SEEK_SET, NULL); ::MidFree(_cache); } STDMETHODIMP CCacheOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (size == 0) return S_OK; UInt64 zerosStart = _virtPos; if (_cachedSize != 0) { if (_virtPos < _cachedPos) { RINOK(FlushCache()); } else { UInt64 cachedEnd = _cachedPos + _cachedSize; if (cachedEnd < _virtPos) { if (cachedEnd < _phySize) { RINOK(FlushCache()); } else zerosStart = cachedEnd; } } } if (_cachedSize == 0 && _phySize < _virtPos) _cachedPos = zerosStart = _phySize; if (zerosStart != _virtPos) { // write zeros to [cachedEnd ... _virtPos) for (;;) { UInt64 cachedEnd = _cachedPos + _cachedSize; size_t endPos = (size_t)cachedEnd & kCacheMask; size_t curSize = kCacheSize - endPos; if (curSize > _virtPos - cachedEnd) curSize = (size_t)(_virtPos - cachedEnd); if (curSize == 0) break; while (curSize > (kCacheSize - _cachedSize)) { RINOK(MyWriteBlock()); } memset(_cache + endPos, 0, curSize); _cachedSize += curSize; } } if (_cachedSize == 0) _cachedPos = _virtPos; size_t pos = (size_t)_virtPos & kCacheMask; size = (UInt32)MyMin((size_t)size, kCacheSize - pos); UInt64 cachedEnd = _cachedPos + _cachedSize; if (_virtPos != cachedEnd) // _virtPos < cachedEnd size = (UInt32)MyMin((size_t)size, (size_t)(cachedEnd - _virtPos)); else { // _virtPos == cachedEnd if (_cachedSize == kCacheSize) { RINOK(MyWriteBlock()); } size_t startPos = (size_t)_cachedPos & kCacheMask; if (startPos > pos) size = (UInt32)MyMin((size_t)size, (size_t)(startPos - pos)); _cachedSize += size; } memcpy(_cache + pos, data, size); if (processedSize) *processedSize = size; _virtPos += size; if (_virtSize < _virtPos) _virtSize = _virtPos; return S_OK; } STDMETHODIMP CCacheOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { switch (seekOrigin) { case STREAM_SEEK_SET: break; case STREAM_SEEK_CUR: offset += _virtPos; break; case STREAM_SEEK_END: offset += _virtSize; break; default: return STG_E_INVALIDFUNCTION; } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; _virtPos = offset; if (newPosition) *newPosition = offset; return S_OK; } STDMETHODIMP CCacheOutStream::SetSize(UInt64 newSize) { _virtSize = newSize; if (newSize < _phySize) { RINOK(_stream->SetSize(newSize)); _phySize = newSize; } if (newSize <= _cachedPos) { _cachedSize = 0; _cachedPos = newSize; } if (newSize < _cachedPos + _cachedSize) _cachedSize = (size_t)(newSize - _cachedPos); return S_OK; } HRESULT Update( DECL_EXTERNAL_CODECS_LOC_VARS const CObjectVector &inputItems, CObjectVector &updateItems, ISequentialOutStream *seqOutStream, CInArchive *inArchive, bool removeSfx, const CCompressionMethodMode &compressionMethodMode, IArchiveUpdateCallback *updateCallback) { if (inArchive) { if (!inArchive->CanUpdate()) return E_NOTIMPL; } CMyComPtr outStream; { CMyComPtr outStreamReal; seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStreamReal); if (!outStreamReal) return E_NOTIMPL; if (inArchive) { if (!inArchive->IsMultiVol && inArchive->ArcInfo.Base > 0 && !removeSfx) { IInStream *baseStream = inArchive->GetBaseStream(); RINOK(baseStream->Seek(0, STREAM_SEEK_SET, NULL)); RINOK(NCompress::CopyStream_ExactSize(baseStream, outStreamReal, inArchive->ArcInfo.Base, NULL)); } } CCacheOutStream *cacheStream = new CCacheOutStream(); outStream = cacheStream; if (!cacheStream->Allocate()) return E_OUTOFMEMORY; RINOK(cacheStream->Init(outStreamReal)); } COutArchive outArchive; RINOK(outArchive.Create(outStream)); if (inArchive) { if (!inArchive->IsMultiVol && (Int64)inArchive->ArcInfo.MarkerPos2 > inArchive->ArcInfo.Base) { IInStream *baseStream = inArchive->GetBaseStream(); RINOK(baseStream->Seek(inArchive->ArcInfo.Base, STREAM_SEEK_SET, NULL)); UInt64 embStubSize = inArchive->ArcInfo.MarkerPos2 - inArchive->ArcInfo.Base; RINOK(NCompress::CopyStream_ExactSize(baseStream, outStream, embStubSize, NULL)); outArchive.MoveCurPos(embStubSize); } } return Update2( EXTERNAL_CODECS_LOC_VARS outArchive, inArchive, inputItems, updateItems, compressionMethodMode, inArchive ? &inArchive->ArcInfo.Comment : NULL, updateCallback); } }}