// TarUpdate.cpp #include "StdAfx.h" // #include #include "../../../Windows/TimeUtils.h" #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" #include "TarOut.h" #include "TarUpdate.h" namespace NArchive { namespace NTar { static void FILETIME_To_PaxTime(const FILETIME &ft, CPaxTime &pt) { UInt32 ns; pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(ft, ns); pt.Ns = ns * 100; pt.NumDigits = 7; } HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt) { pt.Clear(); if (prop.vt == VT_EMPTY) { // pt.Sec = 0; return S_OK; } if (prop.vt != VT_FILETIME) return E_INVALIDARG; { UInt32 ns; pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(prop.filetime, ns); ns *= 100; pt.NumDigits = 7; const unsigned prec = prop.wReserved1; if (prec >= k_PropVar_TimePrec_Base) { pt.NumDigits = (int)(prec - k_PropVar_TimePrec_Base); if (prop.wReserved2 < 100) ns += prop.wReserved2; } pt.Ns = ns; return S_OK; } } static HRESULT GetTime(IStreamGetProp *getProp, UInt32 pid, CPaxTime &pt) { pt.Clear(); NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(pid, &prop)) return Prop_To_PaxTime(prop, pt); } static HRESULT GetUser(IStreamGetProp *getProp, UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id, UINT codePage, unsigned utfFlags) { // printf("\nGetUser\n"); // we keep old values, if both GetProperty() return VT_EMPTY // we clear old values, if any of GetProperty() returns non-VT_EMPTY; bool isSet = false; { NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(pidId, &prop)) if (prop.vt == VT_UI4) { isSet = true; id = prop.ulVal; name.Empty(); } else if (prop.vt != VT_EMPTY) return E_INVALIDARG; } { NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(pidName, &prop)) if (prop.vt == VT_BSTR) { const UString s = prop.bstrVal; Get_AString_From_UString(s, name, codePage, utfFlags); // printf("\ngetProp->GetProperty(pidName, &prop) : %s" , name.Ptr()); if (!isSet) id = 0; } else if (prop.vt == VT_UI4) { id = prop.ulVal; name.Empty(); } else if (prop.vt != VT_EMPTY) return E_INVALIDARG; } return S_OK; } /* static HRESULT GetDevice(IStreamGetProp *getProp, UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined) { NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(kpidDevice, &prop)); if (prop.vt == VT_EMPTY) return S_OK; if (prop.vt != VT_UI8) return E_INVALIDARG; { printf("\nTarUpdate.cpp :: GetDevice()\n"); const UInt64 v = prop.uhVal.QuadPart; majo = MY_dev_major(v); mino = MY_dev_minor(v); majo_defined = true; mino_defined = true; } return S_OK; } */ static HRESULT GetDevice(IStreamGetProp *getProp, UInt32 pid, UInt32 &id, bool &defined) { defined = false; NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(pid, &prop)) if (prop.vt == VT_EMPTY) return S_OK; if (prop.vt == VT_UI4) { id = prop.ulVal; defined = true; return S_OK; } return E_INVALIDARG; } HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, const CObjectVector &inputItems, const CObjectVector &updateItems, const CUpdateOptions &options, IArchiveUpdateCallback *updateCallback) { COutArchive outArchive; outArchive.Create(outStream); outArchive.Pos = 0; outArchive.IsPosixMode = options.PosixMode; outArchive.TimeOptions = options.TimeOptions; Z7_DECL_CMyComPtr_QI_FROM(IOutStream, outSeekStream, outStream) Z7_DECL_CMyComPtr_QI_FROM(IStreamSetRestriction, setRestriction, outStream) Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackFile, opCallback, outStream) if (outSeekStream) { /* // for debug Byte buf[1 << 14]; memset (buf, 0, sizeof(buf)); RINOK(outStream->Write(buf, sizeof(buf), NULL)); */ // we need real outArchive.Pos, if outSeekStream->SetSize() will be used. RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &outArchive.Pos)) } if (setRestriction) RINOK(setRestriction->SetRestriction(0, 0)) UInt64 complexity = 0; unsigned i; for (i = 0; i < updateItems.Size(); i++) { const CUpdateItem &ui = updateItems[i]; if (ui.NewData) { if (ui.Size == (UInt64)(Int64)-1) break; complexity += ui.Size; } else complexity += inputItems[(unsigned)ui.IndexInArc].Get_FullSize_Aligned(); } if (i == updateItems.Size()) RINOK(updateCallback->SetTotal(complexity)) CMyComPtr2_Create lps; lps->Init(updateCallback, true); CMyComPtr2_Create copyCoder; CMyComPtr2_Create inStreamLimited; inStreamLimited->SetStream(inStream); complexity = 0; // const int kNumReduceDigits = -1; // for debug for (i = 0;; i++) { lps->InSize = lps->OutSize = complexity; RINOK(lps->SetCur()) if (i == updateItems.Size()) { if (outSeekStream && setRestriction) RINOK(setRestriction->SetRestriction(0, 0)) return outArchive.WriteFinishHeader(); } const CUpdateItem &ui = updateItems[i]; CItem item; if (ui.NewProps) { item.SetMagic_Posix(options.PosixMode); item.Name = ui.Name; item.User = ui.User; item.Group = ui.Group; item.UID = ui.UID; item.GID = ui.GID; item.DeviceMajor = ui.DeviceMajor; item.DeviceMinor = ui.DeviceMinor; item.DeviceMajor_Defined = ui.DeviceMajor_Defined; item.DeviceMinor_Defined = ui.DeviceMinor_Defined; if (ui.IsDir) { item.LinkFlag = NFileHeader::NLinkFlag::kDirectory; item.PackSize = 0; } else { item.PackSize = ui.Size; item.Set_LinkFlag_for_File(ui.Mode); } // 22.00 item.Mode = ui.Mode & ~(UInt32)MY_LIN_S_IFMT; item.PaxTimes = ui.PaxTimes; // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug item.MTime = ui.PaxTimes.MTime.GetSec(); } else item = inputItems[(unsigned)ui.IndexInArc]; AString symLink; if (ui.NewData || ui.NewProps) { RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidSymLink, symLink, options.CodePage, options.UtfFlags, true)) if (!symLink.IsEmpty()) { item.LinkFlag = NFileHeader::NLinkFlag::kSymLink; item.LinkName = symLink; } } if (ui.NewData) { item.SparseBlocks.Clear(); item.PackSize = ui.Size; item.Size = ui.Size; #if 0 if (ui.Size == (UInt64)(Int64)-1) return E_INVALIDARG; #endif CMyComPtr fileInStream; bool needWrite = true; if (!symLink.IsEmpty()) { item.PackSize = 0; item.Size = 0; } else { const HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) needWrite = false; else { RINOK(res) if (!fileInStream) { item.PackSize = 0; item.Size = 0; } else { Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProp, getProp, fileInStream) if (getProp) { if (options.Write_MTime.Val) RINOK(GetTime(getProp, kpidMTime, item.PaxTimes.MTime)) if (options.Write_ATime.Val) RINOK(GetTime(getProp, kpidATime, item.PaxTimes.ATime)) if (options.Write_CTime.Val) RINOK(GetTime(getProp, kpidCTime, item.PaxTimes.CTime)) if (options.PosixMode) { /* RINOK(GetDevice(getProp, item.DeviceMajor, item.DeviceMinor, item.DeviceMajor_Defined, item.DeviceMinor_Defined)); */ bool defined = false; UInt32 val = 0; RINOK(GetDevice(getProp, kpidDeviceMajor, val, defined)) if (defined) { item.DeviceMajor = val; item.DeviceMajor_Defined = true; item.DeviceMinor = 0; item.DeviceMinor_Defined = false; RINOK(GetDevice(getProp, kpidDeviceMinor, item.DeviceMinor, item.DeviceMinor_Defined)) } } RINOK(GetUser(getProp, kpidUser, kpidUserId, item.User, item.UID, options.CodePage, options.UtfFlags)) RINOK(GetUser(getProp, kpidGroup, kpidGroupId, item.Group, item.GID, options.CodePage, options.UtfFlags)) { NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(kpidPosixAttrib, &prop)) if (prop.vt == VT_EMPTY) item.Mode = MY_LIN_S_IRWXO | MY_LIN_S_IRWXG | MY_LIN_S_IRWXU | (ui.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG); else if (prop.vt != VT_UI4) return E_INVALIDARG; else item.Mode = prop.ulVal; // 21.07 : we clear high file type bits as GNU TAR. item.Set_LinkFlag_for_File(item.Mode); item.Mode &= ~(UInt32)MY_LIN_S_IFMT; } { NWindows::NCOM::CPropVariant prop; RINOK(getProp->GetProperty(kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; const UInt64 size = prop.uhVal.QuadPart; // printf("\nTAR after GetProperty(kpidSize size = %8d\n", (unsigned)size); item.PackSize = size; item.Size = size; } /* printf("\nNum digits = %d %d\n", (int)item.PaxTimes.MTime.NumDigits, (int)item.PaxTimes.MTime.Ns); */ } else { Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProps, getProps, fileInStream) if (getProps) { FILETIME mTime, aTime, cTime; UInt64 size2; if (getProps->GetProps(&size2, options.Write_CTime.Val ? &cTime : NULL, options.Write_ATime.Val ? &aTime : NULL, options.Write_MTime.Val ? &mTime : NULL, NULL) == S_OK) { item.PackSize = size2; item.Size = size2; if (options.Write_MTime.Val) FILETIME_To_PaxTime(mTime, item.PaxTimes.MTime); if (options.Write_ATime.Val) FILETIME_To_PaxTime(aTime, item.PaxTimes.ATime); if (options.Write_CTime.Val) FILETIME_To_PaxTime(cTime, item.PaxTimes.CTime); } } } } { // we must request kpidHardLink after updateCallback->GetStream() AString hardLink; RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidHardLink, hardLink, options.CodePage, options.UtfFlags, true)) if (!hardLink.IsEmpty()) { item.LinkFlag = NFileHeader::NLinkFlag::kHardLink; item.LinkName = hardLink; item.PackSize = 0; item.Size = 0; fileInStream.Release(); } } } } // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug if (ui.NewProps) item.MTime = item.PaxTimes.MTime.GetSec(); if (needWrite) { if (fileInStream) // if (item.PackSize == (UInt64)(Int64)-1) if (item.Size == (UInt64)(Int64)-1) return E_INVALIDARG; const UInt64 headerPos = outArchive.Pos; // item.PackSize = ((UInt64)1 << 33); // for debug if (outSeekStream && setRestriction) RINOK(setRestriction->SetRestriction(outArchive.Pos, (UInt64)(Int64)-1)) RINOK(outArchive.WriteHeader(item)) if (fileInStream) { for (unsigned numPasses = 0;; numPasses++) { // printf("\nTAR numPasses = %d" " old size = %8d\n", numPasses, (unsigned)item.PackSize); /* we support 2 attempts to write header: pass-0: main pass: pass-1: additional pass, if size_of_file and size_of_header are changed */ if (numPasses >= 2) { // opRes = NArchive::NUpdate::NOperationResult::kError_FileChanged; // break; return E_FAIL; } const UInt64 dataPos = outArchive.Pos; RINOK(copyCoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps)) outArchive.Pos += copyCoder->TotalSize; RINOK(outArchive.Write_AfterDataResidual(copyCoder->TotalSize)) // printf("\nTAR after Code old size = %8d copyCoder->TotalSize = %8d \n", (unsigned)item.PackSize, (unsigned)copyCoder->TotalSize); // if (numPasses >= 10) // for debug if (copyCoder->TotalSize == item.PackSize) break; if (opCallback) { RINOK(opCallback->ReportOperation( NEventIndexType::kOutArcIndex, (UInt32)ui.IndexInClient, NUpdateNotifyOp::kInFileChanged)) } if (!outSeekStream) return E_FAIL; const UInt64 nextPos = outArchive.Pos; RINOK(outSeekStream->Seek(-(Int64)(nextPos - headerPos), STREAM_SEEK_CUR, NULL)) outArchive.Pos = headerPos; item.PackSize = copyCoder->TotalSize; RINOK(outArchive.WriteHeader(item)) // if (numPasses >= 10) // for debug if (outArchive.Pos == dataPos) { const UInt64 alignedSize = nextPos - dataPos; if (alignedSize != 0) { RINOK(outSeekStream->Seek((Int64)alignedSize, STREAM_SEEK_CUR, NULL)) outArchive.Pos += alignedSize; } break; } // size of header was changed. // we remove data after header and try new attempt, if required Z7_DECL_CMyComPtr_QI_FROM(IInStream, fileSeekStream, fileInStream) if (!fileSeekStream) return E_FAIL; RINOK(InStream_SeekToBegin(fileSeekStream)) RINOK(outSeekStream->SetSize(outArchive.Pos)) if (item.PackSize == 0) break; } } } complexity += item.PackSize; fileInStream.Release(); RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) } else { // (ui.NewData == false) if (opCallback) { RINOK(opCallback->ReportOperation( NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc, NUpdateNotifyOp::kReplicate)) } const CItemEx &existItem = inputItems[(unsigned)ui.IndexInArc]; UInt64 size, pos; if (ui.NewProps) { // memcpy(item.Magic, NFileHeader::NMagic::kEmpty, 8); if (!symLink.IsEmpty()) { item.PackSize = 0; item.Size = 0; } else { if (ui.IsDir == existItem.IsDir()) item.LinkFlag = existItem.LinkFlag; item.SparseBlocks = existItem.SparseBlocks; item.Size = existItem.Size; item.PackSize = existItem.PackSize; } item.DeviceMajor_Defined = existItem.DeviceMajor_Defined; item.DeviceMinor_Defined = existItem.DeviceMinor_Defined; item.DeviceMajor = existItem.DeviceMajor; item.DeviceMinor = existItem.DeviceMinor; item.UID = existItem.UID; item.GID = existItem.GID; RINOK(outArchive.WriteHeader(item)) size = existItem.Get_PackSize_Aligned(); pos = existItem.Get_DataPos(); } else { size = existItem.Get_FullSize_Aligned(); pos = existItem.HeaderPos; } if (size != 0) { RINOK(InStream_SeekSet(inStream, pos)) inStreamLimited->Init(size); if (outSeekStream && setRestriction) RINOK(setRestriction->SetRestriction(0, 0)) // 22.00 : we copy Residual data from old archive to new archive instead of zeroing RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps)) if (copyCoder->TotalSize != size) return E_FAIL; outArchive.Pos += size; // RINOK(outArchive.Write_AfterDataResidual(existItem.PackSize)); complexity += size; } } } } }}