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