// TarHandler.cpp #include "StdAfx.h" #include "Common/ComTry.h" #include "Common/StringConvert.h" #include "Windows/PropVariant.h" #include "Windows/Time.h" #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamObjects.h" #include "../../Common/StreamUtils.h" #include "../Common/ItemNameUtils.h" #include "TarHandler.h" #include "TarIn.h" using namespace NWindows; namespace NArchive { namespace NTar { static const char *kUnexpectedEnd = "Unexpected end of archive"; static const STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidIsDir, VT_BOOL}, { NULL, kpidSize, VT_UI8}, { NULL, kpidPackSize, VT_UI8}, { NULL, kpidMTime, VT_FILETIME}, { NULL, kpidPosixAttrib, VT_UI4}, { NULL, kpidUser, VT_BSTR}, { NULL, kpidGroup, VT_BSTR}, { NULL, kpidLink, VT_BSTR} }; static const STATPROPSTG kArcProps[] = { { NULL, kpidPhySize, VT_UI8}, { NULL, kpidHeadersSize, VT_UI8} }; IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant prop; switch(propID) { case kpidPhySize: if (_phySizeDefined) prop = _phySize; break; case kpidHeadersSize: if (_phySizeDefined) prop = _headersSize; break; case kpidError: if (!_errorMessage.IsEmpty()) prop = _errorMessage; break; } prop.Detach(value); return S_OK; } HRESULT CHandler::ReadItem2(ISequentialInStream *stream, bool &filled, CItemEx &item) { item.HeaderPos = _phySize; RINOK(ReadItem(stream, filled, item, _errorMessage)); _phySize += item.HeaderSize; _headersSize += item.HeaderSize; return S_OK; } HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { UInt64 endPos = 0; { RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); } _phySizeDefined = true; for (;;) { CItemEx item; bool filled; RINOK(ReadItem2(stream, filled, item)); if (!filled) break; _items.Add(item); RINOK(stream->Seek(item.GetPackSize(), STREAM_SEEK_CUR, &_phySize)); if (_phySize > endPos) { _errorMessage = kUnexpectedEnd; break; } /* if (_phySize == endPos) { _errorMessage = "There are no trailing zero-filled records"; break; } */ if (callback != NULL) { if (_items.Size() == 1) { RINOK(callback->SetTotal(NULL, &endPos)); } if (_items.Size() % 100 == 0) { UInt64 numFiles = _items.Size(); RINOK(callback->SetCompleted(&numFiles, &_phySize)); } } } if (_items.Size() == 0) { CMyComPtr openVolumeCallback; if (!callback) return S_FALSE; callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); if (!openVolumeCallback) return S_FALSE; NCOM::CPropVariant prop; if (openVolumeCallback->GetProperty(kpidName, &prop) != S_OK) return S_FALSE; if (prop.vt != VT_BSTR) return S_FALSE; UString baseName = prop.bstrVal; baseName = baseName.Right(4); if (baseName.CompareNoCase(L".tar") != 0) return S_FALSE; } return S_OK; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openArchiveCallback) { COM_TRY_BEGIN { Close(); RINOK(Open2(stream, openArchiveCallback)); _stream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) { Close(); _seqStream = stream; return S_OK; } STDMETHODIMP CHandler::Close() { _errorMessage.Empty(); _phySizeDefined = false; _phySize = 0; _headersSize = 0; _curIndex = 0; _latestIsRead = false; _items.Clear(); _seqStream.Release(); _stream.Release(); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = (_stream ? _items.Size() : (UInt32)(Int32)-1); return S_OK; } CHandler::CHandler() { copyCoderSpec = new NCompress::CCopyCoder(); copyCoder = copyCoderSpec; } HRESULT CHandler::SkipTo(UInt32 index) { while (_curIndex < index || !_latestIsRead) { if (_latestIsRead) { UInt64 packSize = _latestItem.GetPackSize(); RINOK(copyCoderSpec->Code(_seqStream, NULL, &packSize, &packSize, NULL)); _phySize += copyCoderSpec->TotalSize; if (copyCoderSpec->TotalSize != packSize) { _errorMessage = kUnexpectedEnd; return S_FALSE; } _latestIsRead = false; _curIndex++; } else { bool filled; RINOK(ReadItem2(_seqStream, filled, _latestItem)); if (!filled) { _phySizeDefined = true; return E_INVALIDARG; } _latestIsRead = true; } } return S_OK; } static UString TarStringToUnicode(const AString &s) { return MultiByteToUnicodeString(s, CP_OEMCP); } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItemEx *item; if (_stream) item = &_items[index]; else { if (index < _curIndex) return E_INVALIDARG; else { RINOK(SkipTo(index)); item = &_latestItem; } } switch(propID) { case kpidPath: prop = NItemName::GetOSName2(TarStringToUnicode(item->Name)); break; case kpidIsDir: prop = item->IsDir(); break; case kpidSize: prop = item->GetUnpackSize(); break; case kpidPackSize: prop = item->GetPackSize(); break; case kpidMTime: if (item->MTime != 0) { FILETIME ft; NTime::UnixTimeToFileTime(item->MTime, ft); prop = ft; } break; case kpidPosixAttrib: prop = item->Mode; break; case kpidUser: prop = TarStringToUnicode(item->User); break; case kpidGroup: prop = TarStringToUnicode(item->Group); break; case kpidLink: prop = TarStringToUnicode(item->LinkName); break; } prop.Detach(value); return S_OK; COM_TRY_END } HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN ISequentialInStream *stream = _seqStream; bool seqMode = (_stream == NULL); if (!seqMode) stream = _stream; bool allFilesMode = (numItems == (UInt32)-1); if (allFilesMode) numItems = _items.Size(); if (_stream && numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) totalSize += _items[allFilesMode ? i : indices[i]].GetUnpackSize(); extractCallback->SetTotal(totalSize); UInt64 totalPackSize; totalSize = totalPackSize = 0; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(stream); CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; CMyComPtr outStream(outStreamSpec); for (i = 0; i < numItems || seqMode; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; Int32 index = allFilesMode ? i : indices[i]; const CItemEx *item; if (seqMode) { HRESULT res = SkipTo(index); if (res == E_INVALIDARG) break; RINOK(res); item = &_latestItem; } else item = &_items[index]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); UInt64 unpackSize = item->GetUnpackSize(); totalSize += unpackSize; totalPackSize += item->GetPackSize(); if (item->IsDir()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } bool skipMode = false; if (!testMode && !realOutStream) { if (!seqMode) continue; skipMode = true; askMode = NExtract::NAskMode::kSkip; } RINOK(extractCallback->PrepareOperation(askMode)); outStreamSpec->SetStream(realOutStream); realOutStream.Release(); outStreamSpec->Init(skipMode ? 0 : unpackSize, true); if (item->IsLink()) { RINOK(WriteStream(outStreamSpec, (const char *)item->LinkName, item->LinkName.Length())); } else { if (!seqMode) { RINOK(_stream->Seek(item->GetDataPosition(), STREAM_SEEK_SET, NULL)); } streamSpec->Init(item->GetPackSize()); RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); } if (seqMode) { _latestIsRead = false; _curIndex++; } outStreamSpec->ReleaseStream(); RINOK(extractCallback->SetOperationResult(outStreamSpec->GetRem() == 0 ? NExtract::NOperationResult::kOK: NExtract::NOperationResult::kDataError)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { COM_TRY_BEGIN const CItemEx &item = _items[index]; if (item.IsLink()) { CBufInStream *streamSpec = new CBufInStream; CMyComPtr streamTemp = streamSpec; streamSpec->Init((const Byte *)(const char *)item.LinkName, item.LinkName.Length(), (IInArchive *)this); *stream = streamTemp.Detach(); return S_OK; } return CreateLimitedInStream(_stream, item.GetDataPosition(), item.Size, stream); COM_TRY_END } }}