// Update.cpp #include "StdAfx.h" #include "Update.h" #include "Common/IntToString.h" #include "Common/StringConvert.h" #ifdef _WIN32 #include "Windows/DLL.h" #endif #include "Windows/FileDir.h" #include "Windows/FileFind.h" #include "Windows/FileName.h" #include "Windows/PropVariant.h" #include "Windows/PropVariantConversions.h" #include "Windows/Time.h" #include "../../Common/FileStreams.h" #include "../../Compress/CopyCoder.h" #include "../Common/DirItem.h" #include "../Common/EnumDirItems.h" #include "../Common/OpenArchive.h" #include "../Common/UpdateProduce.h" #include "EnumDirItems.h" #include "SetProperties.h" #include "TempFiles.h" #include "UpdateCallback.h" static const char *kUpdateIsNotSupoorted = "update operations are not supported for this archive"; using namespace NWindows; using namespace NCOM; using namespace NFile; using namespace NName; #ifdef _WIN32 static const wchar_t *kTempFolderPrefix = L"7zE"; #endif using namespace NUpdateArchive; class COutMultiVolStream: public IOutStream, public CMyUnknownImp { int _streamIndex; // required stream UInt64 _offsetPos; // offset from start of _streamIndex index UInt64 _absPos; UInt64 _length; struct CSubStreamInfo { COutFileStream *StreamSpec; CMyComPtr Stream; UString Name; UInt64 Pos; UInt64 RealSize; }; CObjectVector Streams; public: // CMyComPtr VolumeCallback; CRecordVector Sizes; UString Prefix; CTempFiles *TempFiles; void Init() { _streamIndex = 0; _offsetPos = 0; _absPos = 0; _length = 0; } HRESULT Close(); MY_UNKNOWN_IMP1(IOutStream) STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); STDMETHOD(SetSize)(UInt64 newSize); }; // static NSynchronization::CCriticalSection g_TempPathsCS; HRESULT COutMultiVolStream::Close() { HRESULT res = S_OK; for (int i = 0; i < Streams.Size(); i++) { CSubStreamInfo &s = Streams[i]; if (s.StreamSpec) { HRESULT res2 = s.StreamSpec->Close(); if (res2 != S_OK) res = res2; } } return res; } STDMETHODIMP COutMultiVolStream::Write(const void *data, UInt32 size, UInt32 *processedSize) { if (processedSize != NULL) *processedSize = 0; while(size > 0) { if (_streamIndex >= Streams.Size()) { CSubStreamInfo subStream; wchar_t temp[16]; ConvertUInt32ToString(_streamIndex + 1, temp); UString res = temp; while (res.Length() < 3) res = UString(L'0') + res; UString name = Prefix + res; subStream.StreamSpec = new COutFileStream; subStream.Stream = subStream.StreamSpec; if (!subStream.StreamSpec->Create(name, false)) return ::GetLastError(); { // NSynchronization::CCriticalSectionLock lock(g_TempPathsCS); TempFiles->Paths.Add(name); } subStream.Pos = 0; subStream.RealSize = 0; subStream.Name = name; Streams.Add(subStream); continue; } CSubStreamInfo &subStream = Streams[_streamIndex]; int index = _streamIndex; if (index >= Sizes.Size()) index = Sizes.Size() - 1; UInt64 volSize = Sizes[index]; if (_offsetPos >= volSize) { _offsetPos -= volSize; _streamIndex++; continue; } if (_offsetPos != subStream.Pos) { // CMyComPtr outStream; // RINOK(subStream.Stream.QueryInterface(IID_IOutStream, &outStream)); RINOK(subStream.Stream->Seek(_offsetPos, STREAM_SEEK_SET, NULL)); subStream.Pos = _offsetPos; } UInt32 curSize = (UInt32)MyMin((UInt64)size, volSize - subStream.Pos); UInt32 realProcessed; RINOK(subStream.Stream->Write(data, curSize, &realProcessed)); data = (void *)((Byte *)data + realProcessed); size -= realProcessed; subStream.Pos += realProcessed; _offsetPos += realProcessed; _absPos += realProcessed; if (_absPos > _length) _length = _absPos; if (_offsetPos > subStream.RealSize) subStream.RealSize = _offsetPos; if (processedSize != NULL) *processedSize += realProcessed; if (subStream.Pos == volSize) { _streamIndex++; _offsetPos = 0; } if (realProcessed == 0 && curSize != 0) return E_FAIL; break; } return S_OK; } STDMETHODIMP COutMultiVolStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { if (seekOrigin >= 3) return STG_E_INVALIDFUNCTION; switch(seekOrigin) { case STREAM_SEEK_SET: _absPos = offset; break; case STREAM_SEEK_CUR: _absPos += offset; break; case STREAM_SEEK_END: _absPos = _length + offset; break; } _offsetPos = _absPos; if (newPosition != NULL) *newPosition = _absPos; _streamIndex = 0; return S_OK; } STDMETHODIMP COutMultiVolStream::SetSize(UInt64 newSize) { if (newSize < 0) return E_INVALIDARG; int i = 0; while (i < Streams.Size()) { CSubStreamInfo &subStream = Streams[i++]; if ((UInt64)newSize < subStream.RealSize) { RINOK(subStream.Stream->SetSize(newSize)); subStream.RealSize = newSize; break; } newSize -= subStream.RealSize; } while (i < Streams.Size()) { { CSubStreamInfo &subStream = Streams.Back(); subStream.Stream.Release(); NDirectory::DeleteFileAlways(subStream.Name); } Streams.DeleteBack(); } _offsetPos = _absPos; _streamIndex = 0; _length = newSize; return S_OK; } static const wchar_t *kDefaultArchiveType = L"7z"; static const wchar_t *kSFXExtension = #ifdef _WIN32 L"exe"; #else L""; #endif bool CUpdateOptions::Init(const CCodecs *codecs, const CIntVector &formatIndices, const UString &arcPath) { if (formatIndices.Size() > 1) return false; int arcTypeIndex = -1; if (formatIndices.Size() != 0) arcTypeIndex = formatIndices[0]; if (arcTypeIndex >= 0) MethodMode.FormatIndex = arcTypeIndex; else { MethodMode.FormatIndex = codecs->FindFormatForArchiveName(arcPath); // It works incorrectly for update command if archive has some non-default extension! if (MethodMode.FormatIndex < 0) MethodMode.FormatIndex = codecs->FindFormatForArchiveType(kDefaultArchiveType); } if (MethodMode.FormatIndex < 0) return false; const CArcInfoEx &arcInfo = codecs->Formats[MethodMode.FormatIndex]; if (!arcInfo.UpdateEnabled) return false; UString typeExt = arcInfo.GetMainExt(); UString ext = typeExt; if (SfxMode) ext = kSFXExtension; ArchivePath.BaseExtension = ext; ArchivePath.VolExtension = typeExt; ArchivePath.ParseFromPath(arcPath); for (int i = 0; i < Commands.Size(); i++) { CUpdateArchiveCommand &uc = Commands[i]; uc.ArchivePath.BaseExtension = ext; uc.ArchivePath.VolExtension = typeExt; uc.ArchivePath.ParseFromPath(uc.UserArchivePath); } return true; } /* struct CUpdateProduceCallbackImp: public IUpdateProduceCallback { const CObjectVector *_arcItems; IUpdateCallbackUI *_callback; CUpdateProduceCallbackImp(const CObjectVector *a, IUpdateCallbackUI *callback): _arcItems(a), _callback(callback) {} virtual HRESULT ShowDeleteFile(int arcIndex); }; HRESULT CUpdateProduceCallbackImp::ShowDeleteFile(int arcIndex) { return _callback->ShowDeleteFile((*_arcItems)[arcIndex].Name); } */ static HRESULT Compress( CCodecs *codecs, const CActionSet &actionSet, IInArchive *archive, const CCompressionMethodMode &compressionMethod, CArchivePath &archivePath, const CObjectVector &arcItems, bool shareForWrite, bool stdInMode, /* const UString & stdInFileName, */ bool stdOutMode, const CDirItems &dirItems, bool sfxMode, const UString &sfxModule, const CRecordVector &volumesSizes, CTempFiles &tempFiles, CUpdateErrorInfo &errorInfo, IUpdateCallbackUI *callback) { CMyComPtr outArchive; if (archive != NULL) { CMyComPtr archive2 = archive; HRESULT result = archive2.QueryInterface(IID_IOutArchive, &outArchive); if (result != S_OK) throw kUpdateIsNotSupoorted; } else { RINOK(codecs->CreateOutArchive(compressionMethod.FormatIndex, outArchive)); #ifdef EXTERNAL_CODECS { CMyComPtr setCompressCodecsInfo; outArchive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); if (setCompressCodecsInfo) { RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs)); } } #endif } if (outArchive == 0) throw kUpdateIsNotSupoorted; NFileTimeType::EEnum fileTimeType; UInt32 value; RINOK(outArchive->GetFileTimeType(&value)); switch(value) { case NFileTimeType::kWindows: case NFileTimeType::kUnix: case NFileTimeType::kDOS: fileTimeType = (NFileTimeType::EEnum)value; break; default: return E_FAIL; } CRecordVector updatePairs2; { CRecordVector updatePairs; GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs); // must be done only once!!! // CUpdateProduceCallbackImp upCallback(&arcItems, callback); UpdateProduce(updatePairs, actionSet, updatePairs2, NULL /* &upCallback */); } UInt32 numFiles = 0; for (int i = 0; i < updatePairs2.Size(); i++) if (updatePairs2[i].NewData) numFiles++; RINOK(callback->SetNumFiles(numFiles)); CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback; CMyComPtr updateCallback(updateCallbackSpec); updateCallbackSpec->ShareForWrite = shareForWrite; updateCallbackSpec->StdInMode = stdInMode; updateCallbackSpec->Callback = callback; updateCallbackSpec->DirItems = &dirItems; updateCallbackSpec->ArcItems = &arcItems; updateCallbackSpec->UpdatePairs = &updatePairs2; CMyComPtr outStream; if (!stdOutMode) { UString resultPath; int pos; if (!NFile::NDirectory::MyGetFullPathName(archivePath.GetFinalPath(), resultPath, pos)) throw 1417161; NFile::NDirectory::CreateComplexDirectory(resultPath.Left(pos)); } COutFileStream *outStreamSpec = NULL; COutMultiVolStream *volStreamSpec = NULL; if (volumesSizes.Size() == 0) { if (stdOutMode) outStream = new CStdOutFileStream; else { outStreamSpec = new COutFileStream; outStream = outStreamSpec; bool isOK = false; UString realPath; for (int i = 0; i < (1 << 16); i++) { if (archivePath.Temp) { if (i > 0) { wchar_t s[16]; ConvertUInt32ToString(i, s); archivePath.TempPostfix = s; } realPath = archivePath.GetTempPath(); } else realPath = archivePath.GetFinalPath(); if (outStreamSpec->Create(realPath, false)) { tempFiles.Paths.Add(realPath); isOK = true; break; } if (::GetLastError() != ERROR_FILE_EXISTS) break; if (!archivePath.Temp) break; } if (!isOK) { errorInfo.SystemError = ::GetLastError(); errorInfo.FileName = realPath; errorInfo.Message = L"7-Zip cannot open file"; return E_FAIL; } } } else { if (stdOutMode) return E_FAIL; volStreamSpec = new COutMultiVolStream; outStream = volStreamSpec; volStreamSpec->Sizes = volumesSizes; volStreamSpec->Prefix = archivePath.GetFinalPath() + UString(L"."); volStreamSpec->TempFiles = &tempFiles; volStreamSpec->Init(); /* updateCallbackSpec->VolumesSizes = volumesSizes; updateCallbackSpec->VolName = archivePath.Prefix + archivePath.Name; if (!archivePath.VolExtension.IsEmpty()) updateCallbackSpec->VolExt = UString(L'.') + archivePath.VolExtension; */ } RINOK(SetProperties(outArchive, compressionMethod.Properties)); if (sfxMode) { CInFileStream *sfxStreamSpec = new CInFileStream; CMyComPtr sfxStream(sfxStreamSpec); if (!sfxStreamSpec->Open(sfxModule)) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot open SFX module"; errorInfo.FileName = sfxModule; return E_FAIL; } CMyComPtr sfxOutStream; COutFileStream *outStreamSpec = NULL; if (volumesSizes.Size() == 0) sfxOutStream = outStream; else { outStreamSpec = new COutFileStream; sfxOutStream = outStreamSpec; UString realPath = archivePath.GetFinalPath(); if (!outStreamSpec->Create(realPath, false)) { errorInfo.SystemError = ::GetLastError(); errorInfo.FileName = realPath; errorInfo.Message = L"7-Zip cannot open file"; return E_FAIL; } } RINOK(NCompress::CopyStream(sfxStream, sfxOutStream, NULL)); if (outStreamSpec) { RINOK(outStreamSpec->Close()); } } HRESULT result = outArchive->UpdateItems(outStream, updatePairs2.Size(), updateCallback); callback->Finilize(); RINOK(result); if (outStreamSpec) result = outStreamSpec->Close(); else if (volStreamSpec) result = volStreamSpec->Close(); return result; } HRESULT EnumerateInArchiveItems(const NWildcard::CCensor &censor, const CArc &arc, CObjectVector &arcItems) { arcItems.Clear(); UInt32 numItems; IInArchive *archive = arc.Archive; RINOK(archive->GetNumberOfItems(&numItems)); arcItems.Reserve(numItems); for (UInt32 i = 0; i < numItems; i++) { CArcItem ai; RINOK(arc.GetItemPath(i, ai.Name)); RINOK(IsArchiveItemFolder(archive, i, ai.IsDir)); ai.Censored = censor.CheckPath(ai.Name, !ai.IsDir); RINOK(arc.GetItemMTime(i, ai.MTime, ai.MTimeDefined)); { CPropVariant prop; RINOK(archive->GetProperty(i, kpidSize, &prop)); ai.SizeDefined = (prop.vt != VT_EMPTY); if (ai.SizeDefined) ai.Size = ConvertPropVariantToUInt64(prop); } { CPropVariant prop; RINOK(archive->GetProperty(i, kpidTimeType, &prop)); if (prop.vt == VT_UI4) { ai.TimeType = (int)(NFileTimeType::EEnum)prop.ulVal; switch(ai.TimeType) { case NFileTimeType::kWindows: case NFileTimeType::kUnix: case NFileTimeType::kDOS: break; default: return E_FAIL; } } } ai.IndexInServer = i; arcItems.Add(ai); } return S_OK; } static HRESULT UpdateWithItemLists( CCodecs *codecs, CUpdateOptions &options, IInArchive *archive, const CObjectVector &arcItems, CDirItems &dirItems, CTempFiles &tempFiles, CUpdateErrorInfo &errorInfo, IUpdateCallbackUI2 *callback) { for(int i = 0; i < options.Commands.Size(); i++) { CUpdateArchiveCommand &command = options.Commands[i]; if (options.StdOutMode) { RINOK(callback->StartArchive(L"stdout", archive != 0)); } else { RINOK(callback->StartArchive(command.ArchivePath.GetFinalPath(), i == 0 && options.UpdateArchiveItself && archive != 0)); } RINOK(Compress( codecs, command.ActionSet, archive, options.MethodMode, command.ArchivePath, arcItems, options.OpenShareForWrite, options.StdInMode, /* options.StdInFileName, */ options.StdOutMode, dirItems, options.SfxMode, options.SfxModule, options.VolumesSizes, tempFiles, errorInfo, callback)); RINOK(callback->FinishArchive()); } return S_OK; } #if defined(_WIN32) && !defined(UNDER_CE) class CCurrentDirRestorer { UString _path; public: CCurrentDirRestorer() { NFile::NDirectory::MyGetCurrentDirectory(_path); } ~CCurrentDirRestorer() { RestoreDirectory();} bool RestoreDirectory() { return BOOLToBool(NFile::NDirectory::MySetCurrentDirectory(_path)); } }; #endif struct CEnumDirItemUpdateCallback: public IEnumDirItemCallback { IUpdateCallbackUI2 *Callback; HRESULT ScanProgress(UInt64 numFolders, UInt64 numFiles, const wchar_t *path) { return Callback->ScanProgress(numFolders, numFiles, path); } }; #ifdef _WIN32 typedef ULONG (FAR PASCAL MY_MAPISENDDOCUMENTS)( ULONG_PTR ulUIParam, LPSTR lpszDelimChar, LPSTR lpszFilePaths, LPSTR lpszFileNames, ULONG ulReserved ); typedef MY_MAPISENDDOCUMENTS FAR *MY_LPMAPISENDDOCUMENTS; #endif HRESULT UpdateArchive( CCodecs *codecs, const NWildcard::CCensor &censor, CUpdateOptions &options, CUpdateErrorInfo &errorInfo, IOpenCallbackUI *openCallback, IUpdateCallbackUI2 *callback) { if (options.StdOutMode && options.EMailMode) return E_FAIL; if (options.VolumesSizes.Size() > 0 && (options.EMailMode || options.SfxMode)) return E_NOTIMPL; if (options.SfxMode) { CProperty property; property.Name = L"rsfx"; property.Value = L"on"; options.MethodMode.Properties.Add(property); if (options.SfxModule.IsEmpty()) { errorInfo.Message = L"SFX file is not specified"; return E_FAIL; } UString name = options.SfxModule; #ifdef UNDER_CE if (!NFind::DoesFileExist(name)) #else if (!NDirectory::MySearchPath(NULL, name, NULL, options.SfxModule)) #endif { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot find specified SFX module"; errorInfo.FileName = name; return E_FAIL; } } CArchiveLink arcLink; const UString arcPath = options.ArchivePath.GetFinalPath(); if (!options.ArchivePath.OriginalPath.IsEmpty()) { NFind::CFileInfoW fi; if (fi.Find(arcPath)) { if (fi.IsDir()) throw "there is no such archive"; if (options.VolumesSizes.Size() > 0) return E_NOTIMPL; CIntVector formatIndices; if (options.MethodMode.FormatIndex >= 0) formatIndices.Add(options.MethodMode.FormatIndex); HRESULT result = arcLink.Open2(codecs, formatIndices, false, NULL, arcPath, openCallback); if (result == E_ABORT) return result; RINOK(callback->OpenResult(arcPath, result)); RINOK(result); if (arcLink.VolumePaths.Size() > 1) { errorInfo.SystemError = (DWORD)E_NOTIMPL; errorInfo.Message = L"Updating for multivolume archives is not implemented"; return E_NOTIMPL; } CArc &arc = arcLink.Arcs.Back(); arc.MTimeDefined = !fi.IsDevice; arc.MTime = fi.MTime; } } else { /* if (archiveType.IsEmpty()) throw "type of archive is not specified"; */ } CDirItems dirItems; if (options.StdInMode) { CDirItem di; di.Name = options.StdInFileName; di.Size = (UInt64)(Int64)-1; di.Attrib = 0; NTime::GetCurUtcFileTime(di.MTime); di.CTime = di.ATime = di.MTime; dirItems.Items.Add(di); } else { bool needScanning = false; for(int i = 0; i < options.Commands.Size(); i++) if (options.Commands[i].ActionSet.NeedScanning()) needScanning = true; if (needScanning) { CEnumDirItemUpdateCallback enumCallback; enumCallback.Callback = callback; RINOK(callback->StartScanning()); UStringVector errorPaths; CRecordVector errorCodes; HRESULT res = EnumerateItems(censor, dirItems, &enumCallback, errorPaths, errorCodes); for (int i = 0; i < errorPaths.Size(); i++) { RINOK(callback->CanNotFindError(errorPaths[i], errorCodes[i])); } if (res != S_OK) { if (res != E_ABORT) errorInfo.Message = L"Scanning error"; return res; } RINOK(callback->FinishScanning()); } } UString tempDirPrefix; bool usesTempDir = false; #ifdef _WIN32 NDirectory::CTempDirectoryW tempDirectory; if (options.EMailMode && options.EMailRemoveAfter) { tempDirectory.Create(kTempFolderPrefix); tempDirPrefix = tempDirectory.GetPath(); NormalizeDirPathPrefix(tempDirPrefix); usesTempDir = true; } #endif CTempFiles tempFiles; bool createTempFile = false; bool thereIsInArchive = arcLink.IsOpen; if (!options.StdOutMode && options.UpdateArchiveItself) { CArchivePath &ap = options.Commands[0].ArchivePath; ap = options.ArchivePath; // if ((archive != 0 && !usesTempDir) || !options.WorkingDir.IsEmpty()) if ((thereIsInArchive || !options.WorkingDir.IsEmpty()) && !usesTempDir && options.VolumesSizes.Size() == 0) { createTempFile = true; ap.Temp = true; if (!options.WorkingDir.IsEmpty()) { ap.TempPrefix = options.WorkingDir; NormalizeDirPathPrefix(ap.TempPrefix); } } } for(int i = 0; i < options.Commands.Size(); i++) { CArchivePath &ap = options.Commands[i].ArchivePath; if (usesTempDir) { // Check it ap.Prefix = tempDirPrefix; // ap.Temp = true; // ap.TempPrefix = tempDirPrefix; } if (!options.StdOutMode && (i > 0 || !createTempFile)) { const UString &path = ap.GetFinalPath(); if (NFind::DoesFileOrDirExist(path)) { errorInfo.SystemError = 0; errorInfo.Message = L"The file already exists"; errorInfo.FileName = path; return E_FAIL; } } } CObjectVector arcItems; if (thereIsInArchive) { RINOK(EnumerateInArchiveItems(censor, arcLink.Arcs.Back(), arcItems)); } RINOK(UpdateWithItemLists(codecs, options, thereIsInArchive ? arcLink.GetArchive() : 0, arcItems, dirItems, tempFiles, errorInfo, callback)); if (thereIsInArchive) { RINOK(arcLink.Close()); arcLink.Release(); } tempFiles.Paths.Clear(); if (createTempFile) { try { CArchivePath &ap = options.Commands[0].ArchivePath; const UString &tempPath = ap.GetTempPath(); if (thereIsInArchive) if (!NDirectory::DeleteFileAlways(arcPath)) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot delete the file"; errorInfo.FileName = arcPath; return E_FAIL; } if (!NDirectory::MyMoveFile(tempPath, arcPath)) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot move the file"; errorInfo.FileName = tempPath; errorInfo.FileName2 = arcPath; return E_FAIL; } } catch(...) { throw; } } #if defined(_WIN32) && !defined(UNDER_CE) if (options.EMailMode) { NDLL::CLibrary mapiLib; if (!mapiLib.Load(TEXT("Mapi32.dll"))) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot load Mapi32.dll"; return E_FAIL; } MY_LPMAPISENDDOCUMENTS fnSend = (MY_LPMAPISENDDOCUMENTS)mapiLib.GetProc("MAPISendDocuments"); if (fnSend == 0) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"7-Zip cannot find MAPISendDocuments function"; return E_FAIL; } UStringVector fullPaths; int i; for(i = 0; i < options.Commands.Size(); i++) { CArchivePath &ap = options.Commands[i].ArchivePath; UString arcPath; if (!NFile::NDirectory::MyGetFullPathName(ap.GetFinalPath(), arcPath)) { errorInfo.SystemError = ::GetLastError(); errorInfo.Message = L"GetFullPathName error"; return E_FAIL; } fullPaths.Add(arcPath); } CCurrentDirRestorer curDirRestorer; for(i = 0; i < fullPaths.Size(); i++) { UString arcPath = fullPaths[i]; UString fileName = ExtractFileNameFromPath(arcPath); AString path = GetAnsiString(arcPath); AString name = GetAnsiString(fileName); // Warning!!! MAPISendDocuments function changes Current directory fnSend(0, ";", (LPSTR)(LPCSTR)path, (LPSTR)(LPCSTR)name, 0); } } #endif return S_OK; }