xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Tar/TarUpdate.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // TarUpdate.cpp
2 
3 #include "StdAfx.h"
4 
5 // #include <stdio.h>
6 
7 #include "../../../Windows/TimeUtils.h"
8 
9 #include "../../Common/LimitedStreams.h"
10 #include "../../Common/ProgressUtils.h"
11 #include "../../Common/StreamUtils.h"
12 
13 #include "../../Compress/CopyCoder.h"
14 
15 #include "TarOut.h"
16 #include "TarUpdate.h"
17 
18 namespace NArchive {
19 namespace NTar {
20 
FILETIME_To_PaxTime(const FILETIME & ft,CPaxTime & pt)21 static void FILETIME_To_PaxTime(const FILETIME &ft, CPaxTime &pt)
22 {
23   UInt32 ns;
24   pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(ft, ns);
25   pt.Ns = ns * 100;
26   pt.NumDigits = 7;
27 }
28 
29 
Prop_To_PaxTime(const NWindows::NCOM::CPropVariant & prop,CPaxTime & pt)30 HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt)
31 {
32   pt.Clear();
33   if (prop.vt == VT_EMPTY)
34   {
35     // pt.Sec = 0;
36     return S_OK;
37   }
38   if (prop.vt != VT_FILETIME)
39     return E_INVALIDARG;
40   {
41     UInt32 ns;
42     pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(prop.filetime, ns);
43     ns *= 100;
44     pt.NumDigits = 7;
45     const unsigned prec = prop.wReserved1;
46     if (prec >= k_PropVar_TimePrec_Base)
47     {
48       pt.NumDigits = (int)(prec - k_PropVar_TimePrec_Base);
49       if (prop.wReserved2 < 100)
50         ns += prop.wReserved2;
51     }
52     pt.Ns = ns;
53     return S_OK;
54   }
55 }
56 
57 
GetTime(IStreamGetProp * getProp,UInt32 pid,CPaxTime & pt)58 static HRESULT GetTime(IStreamGetProp *getProp, UInt32 pid, CPaxTime &pt)
59 {
60   pt.Clear();
61   NWindows::NCOM::CPropVariant prop;
62   RINOK(getProp->GetProperty(pid, &prop))
63   return Prop_To_PaxTime(prop, pt);
64 }
65 
66 
GetUser(IStreamGetProp * getProp,UInt32 pidName,UInt32 pidId,AString & name,UInt32 & id,UINT codePage,unsigned utfFlags)67 static HRESULT GetUser(IStreamGetProp *getProp,
68     UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id,
69     UINT codePage, unsigned utfFlags)
70 {
71   // printf("\nGetUser\n");
72   // we keep  old values, if both   GetProperty() return VT_EMPTY
73   // we clear old values, if any of GetProperty() returns non-VT_EMPTY;
74   bool isSet = false;
75   {
76     NWindows::NCOM::CPropVariant prop;
77     RINOK(getProp->GetProperty(pidId, &prop))
78     if (prop.vt == VT_UI4)
79     {
80       isSet = true;
81       id = prop.ulVal;
82       name.Empty();
83     }
84     else if (prop.vt != VT_EMPTY)
85       return E_INVALIDARG;
86   }
87   {
88     NWindows::NCOM::CPropVariant prop;
89     RINOK(getProp->GetProperty(pidName, &prop))
90     if (prop.vt == VT_BSTR)
91     {
92       const UString s = prop.bstrVal;
93       Get_AString_From_UString(s, name, codePage, utfFlags);
94       // printf("\ngetProp->GetProperty(pidName, &prop) : %s" , name.Ptr());
95       if (!isSet)
96         id = 0;
97     }
98     else if (prop.vt == VT_UI4)
99     {
100       id = prop.ulVal;
101       name.Empty();
102     }
103     else if (prop.vt != VT_EMPTY)
104       return E_INVALIDARG;
105   }
106   return S_OK;
107 }
108 
109 
110 /*
111 static HRESULT GetDevice(IStreamGetProp *getProp,
112     UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined)
113 {
114   NWindows::NCOM::CPropVariant prop;
115   RINOK(getProp->GetProperty(kpidDevice, &prop));
116   if (prop.vt == VT_EMPTY)
117     return S_OK;
118   if (prop.vt != VT_UI8)
119     return E_INVALIDARG;
120   {
121     printf("\nTarUpdate.cpp :: GetDevice()\n");
122     const UInt64 v = prop.uhVal.QuadPart;
123     majo = MY_dev_major(v);
124     mino = MY_dev_minor(v);
125     majo_defined = true;
126     mino_defined = true;
127   }
128   return S_OK;
129 }
130 */
131 
GetDevice(IStreamGetProp * getProp,UInt32 pid,UInt32 & id,bool & defined)132 static HRESULT GetDevice(IStreamGetProp *getProp,
133     UInt32 pid, UInt32 &id, bool &defined)
134 {
135   defined = false;
136   NWindows::NCOM::CPropVariant prop;
137   RINOK(getProp->GetProperty(pid, &prop))
138   if (prop.vt == VT_EMPTY)
139     return S_OK;
140   if (prop.vt == VT_UI4)
141   {
142     id = prop.ulVal;
143     defined = true;
144     return S_OK;
145   }
146   return E_INVALIDARG;
147 }
148 
149 
UpdateArchive(IInStream * inStream,ISequentialOutStream * outStream,const CObjectVector<NArchive::NTar::CItemEx> & inputItems,const CObjectVector<CUpdateItem> & updateItems,const CUpdateOptions & options,IArchiveUpdateCallback * updateCallback)150 HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream,
151     const CObjectVector<NArchive::NTar::CItemEx> &inputItems,
152     const CObjectVector<CUpdateItem> &updateItems,
153     const CUpdateOptions &options,
154     IArchiveUpdateCallback *updateCallback)
155 {
156   COutArchive outArchive;
157   outArchive.Create(outStream);
158   outArchive.Pos = 0;
159   outArchive.IsPosixMode = options.PosixMode;
160   outArchive.TimeOptions = options.TimeOptions;
161 
162   Z7_DECL_CMyComPtr_QI_FROM(IOutStream, outSeekStream, outStream)
163   Z7_DECL_CMyComPtr_QI_FROM(IStreamSetRestriction, setRestriction, outStream)
164   Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackFile, opCallback, outStream)
165 
166   if (outSeekStream)
167   {
168     /*
169     // for debug
170     Byte buf[1 << 14];
171     memset (buf, 0, sizeof(buf));
172     RINOK(outStream->Write(buf, sizeof(buf), NULL));
173     */
174     // we need real outArchive.Pos, if outSeekStream->SetSize() will be used.
175     RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &outArchive.Pos))
176   }
177   if (setRestriction)
178     RINOK(setRestriction->SetRestriction(0, 0))
179 
180   UInt64 complexity = 0;
181 
182   unsigned i;
183   for (i = 0; i < updateItems.Size(); i++)
184   {
185     const CUpdateItem &ui = updateItems[i];
186     if (ui.NewData)
187     {
188       if (ui.Size == (UInt64)(Int64)-1)
189         break;
190       complexity += ui.Size;
191     }
192     else
193       complexity += inputItems[(unsigned)ui.IndexInArc].Get_FullSize_Aligned();
194   }
195 
196   if (i == updateItems.Size())
197     RINOK(updateCallback->SetTotal(complexity))
198 
199   CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
200   lps->Init(updateCallback, true);
201   CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
202   CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStreamLimited;
203   inStreamLimited->SetStream(inStream);
204 
205   complexity = 0;
206 
207   // const int kNumReduceDigits = -1; // for debug
208 
209   for (i = 0;; i++)
210   {
211     lps->InSize = lps->OutSize = complexity;
212     RINOK(lps->SetCur())
213 
214     if (i == updateItems.Size())
215     {
216       if (outSeekStream && setRestriction)
217         RINOK(setRestriction->SetRestriction(0, 0))
218       return outArchive.WriteFinishHeader();
219     }
220 
221     const CUpdateItem &ui = updateItems[i];
222     CItem item;
223 
224     if (ui.NewProps)
225     {
226       item.SetMagic_Posix(options.PosixMode);
227       item.Name = ui.Name;
228       item.User = ui.User;
229       item.Group = ui.Group;
230       item.UID = ui.UID;
231       item.GID = ui.GID;
232       item.DeviceMajor = ui.DeviceMajor;
233       item.DeviceMinor = ui.DeviceMinor;
234       item.DeviceMajor_Defined = ui.DeviceMajor_Defined;
235       item.DeviceMinor_Defined = ui.DeviceMinor_Defined;
236 
237       if (ui.IsDir)
238       {
239         item.LinkFlag = NFileHeader::NLinkFlag::kDirectory;
240         item.PackSize = 0;
241       }
242       else
243       {
244         item.PackSize = ui.Size;
245         item.Set_LinkFlag_for_File(ui.Mode);
246       }
247 
248       // 22.00
249       item.Mode = ui.Mode & ~(UInt32)MY_LIN_S_IFMT;
250       item.PaxTimes = ui.PaxTimes;
251       // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug
252       item.MTime = ui.PaxTimes.MTime.GetSec();
253     }
254     else
255       item = inputItems[(unsigned)ui.IndexInArc];
256 
257     AString symLink;
258     if (ui.NewData || ui.NewProps)
259     {
260       RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidSymLink, symLink,
261           options.CodePage, options.UtfFlags, true))
262       if (!symLink.IsEmpty())
263       {
264         item.LinkFlag = NFileHeader::NLinkFlag::kSymLink;
265         item.LinkName = symLink;
266       }
267     }
268 
269     if (ui.NewData)
270     {
271       item.SparseBlocks.Clear();
272       item.PackSize = ui.Size;
273       item.Size = ui.Size;
274 #if 0
275       if (ui.Size == (UInt64)(Int64)-1)
276         return E_INVALIDARG;
277 #endif
278       CMyComPtr<ISequentialInStream> fileInStream;
279 
280       bool needWrite = true;
281 
282       if (!symLink.IsEmpty())
283       {
284         item.PackSize = 0;
285         item.Size = 0;
286       }
287       else
288       {
289         const HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
290 
291         if (res == S_FALSE)
292           needWrite = false;
293         else
294         {
295           RINOK(res)
296 
297           if (!fileInStream)
298           {
299             item.PackSize = 0;
300             item.Size = 0;
301           }
302           else
303           {
304             Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProp, getProp, fileInStream)
305             if (getProp)
306             {
307               if (options.Write_MTime.Val) RINOK(GetTime(getProp, kpidMTime, item.PaxTimes.MTime))
308               if (options.Write_ATime.Val) RINOK(GetTime(getProp, kpidATime, item.PaxTimes.ATime))
309               if (options.Write_CTime.Val) RINOK(GetTime(getProp, kpidCTime, item.PaxTimes.CTime))
310 
311               if (options.PosixMode)
312               {
313                 /*
314                 RINOK(GetDevice(getProp, item.DeviceMajor, item.DeviceMinor,
315                     item.DeviceMajor_Defined, item.DeviceMinor_Defined));
316                 */
317                 bool defined = false;
318                 UInt32 val = 0;
319                 RINOK(GetDevice(getProp, kpidDeviceMajor, val, defined))
320                 if (defined)
321                 {
322                   item.DeviceMajor = val;
323                   item.DeviceMajor_Defined = true;
324                   item.DeviceMinor = 0;
325                   item.DeviceMinor_Defined = false;
326                   RINOK(GetDevice(getProp, kpidDeviceMinor, item.DeviceMinor, item.DeviceMinor_Defined))
327                 }
328               }
329 
330               RINOK(GetUser(getProp, kpidUser,  kpidUserId,  item.User,  item.UID, options.CodePage, options.UtfFlags))
331               RINOK(GetUser(getProp, kpidGroup, kpidGroupId, item.Group, item.GID, options.CodePage, options.UtfFlags))
332 
333               {
334                 NWindows::NCOM::CPropVariant prop;
335                 RINOK(getProp->GetProperty(kpidPosixAttrib, &prop))
336                 if (prop.vt == VT_EMPTY)
337                   item.Mode =
338                     MY_LIN_S_IRWXO
339                   | MY_LIN_S_IRWXG
340                   | MY_LIN_S_IRWXU
341                   | (ui.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG);
342                 else if (prop.vt != VT_UI4)
343                   return E_INVALIDARG;
344                 else
345                   item.Mode = prop.ulVal;
346                 // 21.07 : we clear high file type bits as GNU TAR.
347                 item.Set_LinkFlag_for_File(item.Mode);
348                 item.Mode &= ~(UInt32)MY_LIN_S_IFMT;
349               }
350 
351               {
352                 NWindows::NCOM::CPropVariant prop;
353                 RINOK(getProp->GetProperty(kpidSize, &prop))
354                 if (prop.vt != VT_UI8)
355                   return E_INVALIDARG;
356                 const UInt64 size = prop.uhVal.QuadPart;
357                 // printf("\nTAR after GetProperty(kpidSize size = %8d\n", (unsigned)size);
358                 item.PackSize = size;
359                 item.Size = size;
360               }
361               /*
362               printf("\nNum digits = %d %d\n",
363                   (int)item.PaxTimes.MTime.NumDigits,
364                   (int)item.PaxTimes.MTime.Ns);
365               */
366             }
367             else
368             {
369               Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProps, getProps, fileInStream)
370               if (getProps)
371               {
372                 FILETIME mTime, aTime, cTime;
373                 UInt64 size2;
374                 if (getProps->GetProps(&size2,
375                     options.Write_CTime.Val ? &cTime : NULL,
376                     options.Write_ATime.Val ? &aTime : NULL,
377                     options.Write_MTime.Val ? &mTime : NULL,
378                     NULL) == S_OK)
379                 {
380                   item.PackSize = size2;
381                   item.Size = size2;
382                   if (options.Write_MTime.Val) FILETIME_To_PaxTime(mTime, item.PaxTimes.MTime);
383                   if (options.Write_ATime.Val) FILETIME_To_PaxTime(aTime, item.PaxTimes.ATime);
384                   if (options.Write_CTime.Val) FILETIME_To_PaxTime(cTime, item.PaxTimes.CTime);
385                 }
386               }
387             }
388           }
389 
390           {
391             // we must request kpidHardLink after updateCallback->GetStream()
392             AString hardLink;
393             RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidHardLink, hardLink,
394                 options.CodePage, options.UtfFlags, true))
395             if (!hardLink.IsEmpty())
396             {
397               item.LinkFlag = NFileHeader::NLinkFlag::kHardLink;
398               item.LinkName = hardLink;
399               item.PackSize = 0;
400               item.Size = 0;
401               fileInStream.Release();
402             }
403           }
404         }
405       }
406 
407       // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug
408 
409       if (ui.NewProps)
410         item.MTime = item.PaxTimes.MTime.GetSec();
411 
412       if (needWrite)
413       {
414         if (fileInStream)
415         // if (item.PackSize == (UInt64)(Int64)-1)
416         if (item.Size == (UInt64)(Int64)-1)
417           return E_INVALIDARG;
418 
419         const UInt64 headerPos = outArchive.Pos;
420         // item.PackSize = ((UInt64)1 << 33); // for debug
421 
422         if (outSeekStream && setRestriction)
423           RINOK(setRestriction->SetRestriction(outArchive.Pos, (UInt64)(Int64)-1))
424 
425         RINOK(outArchive.WriteHeader(item))
426         if (fileInStream)
427         {
428           for (unsigned numPasses = 0;; numPasses++)
429           {
430             // printf("\nTAR numPasses = %d" " old size = %8d\n", numPasses, (unsigned)item.PackSize);
431             /* we support 2 attempts to write header:
432                 pass-0: main pass:
433                 pass-1: additional pass, if size_of_file and size_of_header are changed */
434             if (numPasses >= 2)
435             {
436               // opRes = NArchive::NUpdate::NOperationResult::kError_FileChanged;
437               // break;
438               return E_FAIL;
439             }
440 
441             const UInt64 dataPos = outArchive.Pos;
442             RINOK(copyCoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps))
443             outArchive.Pos += copyCoder->TotalSize;
444             RINOK(outArchive.Write_AfterDataResidual(copyCoder->TotalSize))
445             // printf("\nTAR after Code old size = %8d copyCoder->TotalSize = %8d \n", (unsigned)item.PackSize, (unsigned)copyCoder->TotalSize);
446             // if (numPasses >= 10) // for debug
447             if (copyCoder->TotalSize == item.PackSize)
448               break;
449 
450             if (opCallback)
451             {
452               RINOK(opCallback->ReportOperation(
453                   NEventIndexType::kOutArcIndex, (UInt32)ui.IndexInClient,
454                   NUpdateNotifyOp::kInFileChanged))
455             }
456 
457             if (!outSeekStream)
458               return E_FAIL;
459             const UInt64 nextPos = outArchive.Pos;
460             RINOK(outSeekStream->Seek(-(Int64)(nextPos - headerPos), STREAM_SEEK_CUR, NULL))
461             outArchive.Pos = headerPos;
462             item.PackSize = copyCoder->TotalSize;
463 
464             RINOK(outArchive.WriteHeader(item))
465 
466             // if (numPasses >= 10) // for debug
467             if (outArchive.Pos == dataPos)
468             {
469               const UInt64 alignedSize = nextPos - dataPos;
470               if (alignedSize != 0)
471               {
472                 RINOK(outSeekStream->Seek((Int64)alignedSize, STREAM_SEEK_CUR, NULL))
473                 outArchive.Pos += alignedSize;
474               }
475               break;
476             }
477 
478             // size of header was changed.
479             // we remove data after header and try new attempt, if required
480             Z7_DECL_CMyComPtr_QI_FROM(IInStream, fileSeekStream, fileInStream)
481             if (!fileSeekStream)
482               return E_FAIL;
483             RINOK(InStream_SeekToBegin(fileSeekStream))
484             RINOK(outSeekStream->SetSize(outArchive.Pos))
485             if (item.PackSize == 0)
486               break;
487           }
488         }
489       }
490 
491       complexity += item.PackSize;
492       fileInStream.Release();
493       RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK))
494     }
495     else
496     {
497       // (ui.NewData == false)
498 
499       if (opCallback)
500       {
501         RINOK(opCallback->ReportOperation(
502             NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc,
503             NUpdateNotifyOp::kReplicate))
504       }
505 
506       const CItemEx &existItem = inputItems[(unsigned)ui.IndexInArc];
507       UInt64 size, pos;
508 
509       if (ui.NewProps)
510       {
511         // memcpy(item.Magic, NFileHeader::NMagic::kEmpty, 8);
512 
513         if (!symLink.IsEmpty())
514         {
515           item.PackSize = 0;
516           item.Size = 0;
517         }
518         else
519         {
520           if (ui.IsDir == existItem.IsDir())
521             item.LinkFlag = existItem.LinkFlag;
522 
523           item.SparseBlocks = existItem.SparseBlocks;
524           item.Size = existItem.Size;
525           item.PackSize = existItem.PackSize;
526         }
527 
528         item.DeviceMajor_Defined = existItem.DeviceMajor_Defined;
529         item.DeviceMinor_Defined = existItem.DeviceMinor_Defined;
530         item.DeviceMajor = existItem.DeviceMajor;
531         item.DeviceMinor = existItem.DeviceMinor;
532         item.UID = existItem.UID;
533         item.GID = existItem.GID;
534 
535         RINOK(outArchive.WriteHeader(item))
536         size = existItem.Get_PackSize_Aligned();
537         pos = existItem.Get_DataPos();
538       }
539       else
540       {
541         size = existItem.Get_FullSize_Aligned();
542         pos = existItem.HeaderPos;
543       }
544 
545       if (size != 0)
546       {
547         RINOK(InStream_SeekSet(inStream, pos))
548         inStreamLimited->Init(size);
549         if (outSeekStream && setRestriction)
550           RINOK(setRestriction->SetRestriction(0, 0))
551         // 22.00 : we copy Residual data from old archive to new archive instead of zeroing
552         RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps))
553         if (copyCoder->TotalSize != size)
554           return E_FAIL;
555         outArchive.Pos += size;
556         // RINOK(outArchive.Write_AfterDataResidual(existItem.PackSize));
557         complexity += size;
558       }
559     }
560   }
561 }
562 
563 }}
564