1 // WimHandlerOut.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/IntToString.h"
7 #include "../../../Common/StringToInt.h"
8 #include "../../../Common/UTFConvert.h"
9 #include "../../../Common/Wildcard.h"
10 
11 #include "../../../Windows/PropVariant.h"
12 #include "../../../Windows/TimeUtils.h"
13 
14 #include "../../Common/LimitedStreams.h"
15 #include "../../Common/ProgressUtils.h"
16 #include "../../Common/StreamUtils.h"
17 #include "../../Common/UniqBlocks.h"
18 
19 #include "../../Crypto/RandGen.h"
20 #include "../../Crypto/Sha1Cls.h"
21 
22 #include "WimHandler.h"
23 
24 using namespace NWindows;
25 
26 namespace NArchive {
27 namespace NWim {
28 
AddUniqHash(const CStreamInfo * streams,CUIntVector & sorted,const Byte * h,int streamIndexForInsert)29 static int AddUniqHash(const CStreamInfo *streams, CUIntVector &sorted, const Byte *h, int streamIndexForInsert)
30 {
31   unsigned left = 0, right = sorted.Size();
32   while (left != right)
33   {
34     unsigned mid = (left + right) / 2;
35     unsigned index = sorted[mid];
36     const Byte *hash2 = streams[index].Hash;
37 
38     unsigned i;
39     for (i = 0; i < kHashSize; i++)
40       if (h[i] != hash2[i])
41         break;
42 
43     if (i == kHashSize)
44       return index;
45 
46     if (h[i] < hash2[i])
47       right = mid;
48     else
49       left = mid + 1;
50   }
51 
52   if (streamIndexForInsert >= 0)
53     sorted.Insert(left, streamIndexForInsert);
54 
55   return -1;
56 }
57 
58 
59 struct CAltStream
60 {
61   int UpdateIndex;
62   int HashIndex;
63   UInt64 Size;
64   UString Name;
65   bool Skip;
66 
CAltStreamNArchive::NWim::CAltStream67   CAltStream(): UpdateIndex(-1), HashIndex(-1), Skip(false) {}
68 };
69 
70 
71 struct CMetaItem
72 {
73   int UpdateIndex;
74   int HashIndex;
75 
76   UInt64 Size;
77   FILETIME CTime;
78   FILETIME ATime;
79   FILETIME MTime;
80   UInt32 Attrib;
81   UInt64 FileID;
82   UInt64 VolID;
83 
84   UString Name;
85   UString ShortName;
86 
87   int SecurityId;       // -1: means no secutity ID
88   bool IsDir;
89   bool Skip;
90   unsigned NumSkipAltStreams;
91   CObjectVector<CAltStream> AltStreams;
92 
93   CByteBuffer Reparse;
94 
GetNumAltStreamsNArchive::NWim::CMetaItem95   unsigned GetNumAltStreams() const { return AltStreams.Size() - NumSkipAltStreams; }
CMetaItemNArchive::NWim::CMetaItem96   CMetaItem(): UpdateIndex(-1), HashIndex(-1), SecurityId(-1),
97       FileID(0), VolID(0),
98       Skip(false), NumSkipAltStreams(0) {}
99 };
100 
101 
Compare_HardLink_MetaItems(const CMetaItem & a1,const CMetaItem & a2)102 static int Compare_HardLink_MetaItems(const CMetaItem &a1, const CMetaItem &a2)
103 {
104   if (a1.VolID < a2.VolID) return -1;
105   if (a1.VolID > a2.VolID) return 1;
106   if (a1.FileID < a2.FileID) return -1;
107   if (a1.FileID > a2.FileID) return 1;
108   if (a1.Size < a2.Size) return -1;
109   if (a1.Size > a2.Size) return 1;
110   return ::CompareFileTime(&a1.MTime, &a2.MTime);
111 }
112 
113 
AddToHardLinkList(const CObjectVector<CMetaItem> & metaItems,unsigned indexOfItem,CUIntVector & indexes)114 static int AddToHardLinkList(const CObjectVector<CMetaItem> &metaItems, unsigned indexOfItem, CUIntVector &indexes)
115 {
116   const CMetaItem &mi = metaItems[indexOfItem];
117   unsigned left = 0, right = indexes.Size();
118   while (left != right)
119   {
120     unsigned mid = (left + right) / 2;
121     unsigned index = indexes[mid];
122     int comp = Compare_HardLink_MetaItems(mi, metaItems[index]);
123     if (comp == 0)
124       return index;
125     if (comp < 0)
126       right = mid;
127     else
128       left = mid + 1;
129   }
130   indexes.Insert(left, indexOfItem);
131   return -1;
132 }
133 
134 
135 struct CUpdateItem
136 {
137   unsigned CallbackIndex; // index in callback
138 
139   int MetaIndex;          // index in in MetaItems[]
140 
141   int AltStreamIndex;     // index in CMetaItem::AltStreams vector
142                           // -1: if not alt stream?
143 
144   int InArcIndex;         // >= 0, if we use OLD Data
145                           //   -1, if we use NEW Data
146 
CUpdateItemNArchive::NWim::CUpdateItem147   CUpdateItem(): MetaIndex(-1), AltStreamIndex(-1), InArcIndex(-1) {}
148 };
149 
150 
151 struct CDir
152 {
153   int MetaIndex;
154   CObjectVector<CDir> Dirs;
155   CUIntVector Files; // indexes in MetaItems[]
156 
CDirNArchive::NWim::CDir157   CDir(): MetaIndex(-1) {}
158   unsigned GetNumDirs() const;
159   unsigned GetNumFiles() const;
160   UInt64 GetTotalSize(const CObjectVector<CMetaItem> &metaItems) const;
161   bool FindDir(const CObjectVector<CMetaItem> &items, const UString &name, unsigned &index);
162 };
163 
164 /* imagex counts Junctions as files (not as dirs).
165    We suppose that it's not correct */
166 
GetNumDirs() const167 unsigned CDir::GetNumDirs() const
168 {
169   unsigned num = Dirs.Size();
170   FOR_VECTOR (i, Dirs)
171     num += Dirs[i].GetNumDirs();
172   return num;
173 }
174 
GetNumFiles() const175 unsigned CDir::GetNumFiles() const
176 {
177   unsigned num = Files.Size();
178   FOR_VECTOR (i, Dirs)
179     num += Dirs[i].GetNumFiles();
180   return num;
181 }
182 
GetTotalSize(const CObjectVector<CMetaItem> & metaItems) const183 UInt64 CDir::GetTotalSize(const CObjectVector<CMetaItem> &metaItems) const
184 {
185   UInt64 sum = 0;
186   unsigned i;
187   for (i = 0; i < Files.Size(); i++)
188     sum += metaItems[Files[i]].Size;
189   for (i = 0; i < Dirs.Size(); i++)
190     sum += Dirs[i].GetTotalSize(metaItems);
191   return sum;
192 }
193 
FindDir(const CObjectVector<CMetaItem> & items,const UString & name,unsigned & index)194 bool CDir::FindDir(const CObjectVector<CMetaItem> &items, const UString &name, unsigned &index)
195 {
196   unsigned left = 0, right = Dirs.Size();
197   while (left != right)
198   {
199     unsigned mid = (left + right) / 2;
200     int comp = CompareFileNames(name, items[Dirs[mid].MetaIndex].Name);
201     if (comp == 0)
202     {
203       index = mid;
204       return true;
205     }
206     if (comp < 0)
207       right = mid;
208     else
209       left = mid + 1;
210   }
211   index = left;
212   return false;
213 }
214 
215 
GetFileTimeType(UInt32 * type)216 STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type)
217 {
218   *type = NFileTimeType::kWindows;
219   return S_OK;
220 }
221 
222 
GetOutProperty(IArchiveUpdateCallback * callback,UInt32 callbackIndex,Int32 arcIndex,PROPID propID,PROPVARIANT * value)223 HRESULT CHandler::GetOutProperty(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, PROPVARIANT *value)
224 {
225   if (arcIndex >= 0)
226     return GetProperty(arcIndex, propID, value);
227   return callback->GetProperty(callbackIndex, propID, value);
228 }
229 
230 
GetTime(IArchiveUpdateCallback * callback,UInt32 callbackIndex,Int32 arcIndex,PROPID propID,FILETIME & ft)231 HRESULT CHandler::GetTime(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, FILETIME &ft)
232 {
233   ft.dwLowDateTime = ft.dwHighDateTime = 0;
234   NCOM::CPropVariant prop;
235   RINOK(GetOutProperty(callback, callbackIndex, arcIndex, propID, &prop));
236   if (prop.vt == VT_FILETIME)
237     ft = prop.filetime;
238   else if (prop.vt != VT_EMPTY)
239     return E_INVALIDARG;
240   return S_OK;
241 }
242 
243 
GetRootTime(IArchiveGetRootProps * callback,IArchiveGetRootProps * arcRoot,PROPID propID,FILETIME & ft)244 static HRESULT GetRootTime(
245     IArchiveGetRootProps *callback,
246     IArchiveGetRootProps *arcRoot,
247     PROPID propID, FILETIME &ft)
248 {
249   NCOM::CPropVariant prop;
250   if (callback)
251   {
252     RINOK(callback->GetRootProp(propID, &prop));
253     if (prop.vt == VT_FILETIME)
254     {
255       ft = prop.filetime;
256       return S_OK;
257     }
258     if (prop.vt != VT_EMPTY)
259       return E_INVALIDARG;
260   }
261   if (arcRoot)
262   {
263     RINOK(arcRoot->GetRootProp(propID, &prop));
264     if (prop.vt == VT_FILETIME)
265     {
266       ft = prop.filetime;
267       return S_OK;
268     }
269     if (prop.vt != VT_EMPTY)
270       return E_INVALIDARG;
271   }
272   return S_OK;
273 }
274 
275 #define Set16(p, d) SetUi16(p, d)
276 #define Set32(p, d) SetUi32(p, d)
277 #define Set64(p, d) SetUi64(p, d)
278 
WriteTo(Byte * p) const279 void CResource::WriteTo(Byte *p) const
280 {
281   Set64(p, PackSize);
282   p[7] = Flags;
283   Set64(p + 8, Offset);
284   Set64(p + 16, UnpackSize);
285 }
286 
287 
WriteTo(Byte * p) const288 void CHeader::WriteTo(Byte *p) const
289 {
290   memcpy(p, kSignature, kSignatureSize);
291   Set32(p + 8, kHeaderSizeMax);
292   Set32(p + 0xC, Version);
293   Set32(p + 0x10, Flags);
294   Set32(p + 0x14, ChunkSize);
295   memcpy(p + 0x18, Guid, 16);
296   Set16(p + 0x28, PartNumber);
297   Set16(p + 0x2A, NumParts);
298   Set32(p + 0x2C, NumImages);
299   OffsetResource.WriteTo(p + 0x30);
300   XmlResource.WriteTo(p + 0x48);
301   MetadataResource.WriteTo(p + 0x60);
302   IntegrityResource.WriteTo(p + 0x7C);
303   Set32(p + 0x78, BootIndex);
304   memset(p + 0x94, 0, 60);
305 }
306 
307 
WriteTo(Byte * p) const308 void CStreamInfo::WriteTo(Byte *p) const
309 {
310   Resource.WriteTo(p);
311   Set16(p + 0x18, PartNumber);
312   Set32(p + 0x1A, RefCount);
313   memcpy(p + 0x1E, Hash, kHashSize);
314 }
315 
316 
317 class CInStreamWithSha1:
318   public ISequentialInStream,
319   public CMyUnknownImp
320 {
321   CMyComPtr<ISequentialInStream> _stream;
322   UInt64 _size;
323   NCrypto::NSha1::CContext _sha;
324 public:
325   MY_UNKNOWN_IMP1(IInStream)
326   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
327 
SetStream(ISequentialInStream * stream)328   void SetStream(ISequentialInStream *stream) { _stream = stream;  }
Init()329   void Init()
330   {
331     _size = 0;
332     _sha.Init();
333   }
ReleaseStream()334   void ReleaseStream() { _stream.Release(); }
GetSize() const335   UInt64 GetSize() const { return _size; }
Final(Byte * digest)336   void Final(Byte *digest) { _sha.Final(digest); }
337 };
338 
Read(void * data,UInt32 size,UInt32 * processedSize)339 STDMETHODIMP CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize)
340 {
341   UInt32 realProcessedSize;
342   HRESULT result = _stream->Read(data, size, &realProcessedSize);
343   _size += realProcessedSize;
344   _sha.Update((const Byte *)data, realProcessedSize);
345   if (processedSize)
346     *processedSize = realProcessedSize;
347   return result;
348 }
349 
350 
SetFileTimeToMem(Byte * p,const FILETIME & ft)351 static void SetFileTimeToMem(Byte *p, const FILETIME &ft)
352 {
353   Set32(p, ft.dwLowDateTime);
354   Set32(p + 4, ft.dwHighDateTime);
355 }
356 
WriteItem_Dummy(const CMetaItem & item)357 static size_t WriteItem_Dummy(const CMetaItem &item)
358 {
359   if (item.Skip)
360     return 0;
361   unsigned fileNameLen = item.Name.Len() * 2;
362   // we write fileNameLen + 2 + 2 to be same as original WIM.
363   unsigned fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2);
364 
365   unsigned shortNameLen = item.ShortName.Len() * 2;
366   unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4);
367 
368   size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7);
369   if (item.GetNumAltStreams() != 0)
370   {
371     if (!item.IsDir)
372     {
373       UInt32 curLen = (((0x26 + 0) + 6) & ~7);
374       totalLen += curLen;
375     }
376     FOR_VECTOR (i, item.AltStreams)
377     {
378       const CAltStream &ss = item.AltStreams[i];
379       if (ss.Skip)
380         continue;
381       fileNameLen = ss.Name.Len() * 2;
382       fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2);
383       UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~7);
384       totalLen += curLen;
385     }
386   }
387   return totalLen;
388 }
389 
390 
WriteItem(const CStreamInfo * streams,const CMetaItem & item,Byte * p)391 static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte *p)
392 {
393   if (item.Skip)
394     return 0;
395   unsigned fileNameLen = item.Name.Len() * 2;
396   unsigned fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2);
397   unsigned shortNameLen = item.ShortName.Len() * 2;
398   unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4);
399 
400   size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7);
401 
402   memset(p, 0, totalLen);
403   Set64(p, totalLen);
404   Set64(p + 8, item.Attrib);
405   Set32(p + 0xC, (Int32)item.SecurityId);
406   SetFileTimeToMem(p + 0x28, item.CTime);
407   SetFileTimeToMem(p + 0x30, item.ATime);
408   SetFileTimeToMem(p + 0x38, item.MTime);
409 
410   /* WIM format probably doesn't support hard links to symbolic links.
411      In these cases it just stores symbolic links (REPARSE TAGS).
412      Check it in new versions of WIM software form MS !!!
413      We also follow that scheme */
414 
415   if (item.Reparse.Size() != 0)
416   {
417     UInt32 tag = GetUi32(item.Reparse);
418     Set32(p + 0x58, tag);
419     // Set32(p + 0x5C, 0); // probably it's always ZERO
420   }
421   else if (item.FileID != 0)
422   {
423     Set64(p + 0x58, item.FileID);
424   }
425 
426   Set16(p + 0x62, (UInt16)shortNameLen);
427   Set16(p + 0x64, (UInt16)fileNameLen);
428   unsigned i;
429   for (i = 0; i * 2 < fileNameLen; i++)
430     Set16(p + kDirRecordSize + i * 2, item.Name[i]);
431   for (i = 0; i * 2 < shortNameLen; i++)
432     Set16(p + kDirRecordSize + fileNameLen2 + i * 2, item.ShortName[i]);
433 
434   if (item.GetNumAltStreams() == 0)
435   {
436     if (item.HashIndex >= 0)
437       memcpy(p + 0x40, streams[item.HashIndex].Hash, kHashSize);
438   }
439   else
440   {
441     Set16(p + 0x60, (UInt16)(item.GetNumAltStreams() + (item.IsDir ? 0 : 1)));
442     p += totalLen;
443 
444     if (!item.IsDir)
445     {
446       UInt32 curLen = (((0x26 + 0) + 6) & ~7);
447       memset(p, 0, curLen);
448       Set64(p, curLen);
449       if (item.HashIndex >= 0)
450         memcpy(p + 0x10, streams[item.HashIndex].Hash, kHashSize);
451       totalLen += curLen;
452       p += curLen;
453     }
454 
455     FOR_VECTOR (si, item.AltStreams)
456     {
457       const CAltStream &ss = item.AltStreams[si];
458       if (ss.Skip)
459         continue;
460 
461       fileNameLen = ss.Name.Len() * 2;
462       fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2);
463       UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~7);
464       memset(p, 0, curLen);
465 
466       Set64(p, curLen);
467       if (ss.HashIndex >= 0)
468         memcpy(p + 0x10, streams[ss.HashIndex].Hash, kHashSize);
469       Set16(p + 0x24, (UInt16)fileNameLen);
470       for (i = 0; i * 2 < fileNameLen; i++)
471         Set16(p + 0x26 + i * 2, ss.Name[i]);
472       totalLen += curLen;
473       p += curLen;
474     }
475   }
476 
477   return totalLen;
478 }
479 
480 
481 struct CDb
482 {
483   CMetaItem DefaultDirItem;
484   const CStreamInfo *Hashes;
485   CObjectVector<CMetaItem> MetaItems;
486   CRecordVector<CUpdateItem> UpdateItems;
487   CUIntVector UpdateIndexes; /* indexes in UpdateItems in order of writing data streams
488                                 to disk (the order of tree items). */
489 
490   size_t WriteTree_Dummy(const CDir &tree) const;
491   void WriteTree(const CDir &tree, Byte *dest, size_t &pos)  const;
492   void WriteOrderList(const CDir &tree);
493 };
494 
495 
WriteTree_Dummy(const CDir & tree) const496 size_t CDb::WriteTree_Dummy(const CDir &tree) const
497 {
498   unsigned i;
499   size_t pos = 0;
500   for (i = 0; i < tree.Files.Size(); i++)
501     pos += WriteItem_Dummy(MetaItems[tree.Files[i]]);
502   for (i = 0; i < tree.Dirs.Size(); i++)
503   {
504     const CDir &subDir = tree.Dirs[i];
505     pos += WriteItem_Dummy(MetaItems[subDir.MetaIndex]);
506     pos += WriteTree_Dummy(subDir);
507   }
508   return pos + 8;
509 }
510 
511 
WriteTree(const CDir & tree,Byte * dest,size_t & pos) const512 void CDb::WriteTree(const CDir &tree, Byte *dest, size_t &pos) const
513 {
514   unsigned i;
515   for (i = 0; i < tree.Files.Size(); i++)
516     pos += WriteItem(Hashes, MetaItems[tree.Files[i]], dest + pos);
517 
518   size_t posStart = pos;
519   for (i = 0; i < tree.Dirs.Size(); i++)
520     pos += WriteItem_Dummy(MetaItems[tree.Dirs[i].MetaIndex]);
521 
522   Set64(dest + pos, 0);
523 
524   pos += 8;
525 
526   for (i = 0; i < tree.Dirs.Size(); i++)
527   {
528     const CDir &subDir = tree.Dirs[i];
529     const CMetaItem &metaItem = MetaItems[subDir.MetaIndex];
530     bool needCreateTree = (metaItem.Reparse.Size() == 0)
531         || !subDir.Files.IsEmpty()
532         || !subDir.Dirs.IsEmpty();
533     size_t len = WriteItem(Hashes, metaItem, dest + posStart);
534     posStart += len;
535     if (needCreateTree)
536     {
537       Set64(dest + posStart - len + 0x10, pos); // subdirOffset
538       WriteTree(subDir, dest, pos);
539     }
540   }
541 }
542 
543 
WriteOrderList(const CDir & tree)544 void CDb::WriteOrderList(const CDir &tree)
545 {
546   if (tree.MetaIndex >= 0)
547   {
548     const CMetaItem &mi = MetaItems[tree.MetaIndex];
549     if (mi.UpdateIndex >= 0)
550       UpdateIndexes.Add(mi.UpdateIndex);
551     FOR_VECTOR (si, mi.AltStreams)
552       UpdateIndexes.Add(mi.AltStreams[si].UpdateIndex);
553   }
554 
555   unsigned i;
556   for (i = 0; i < tree.Files.Size(); i++)
557   {
558     const CMetaItem &mi = MetaItems[tree.Files[i]];
559     UpdateIndexes.Add(mi.UpdateIndex);
560     FOR_VECTOR (si, mi.AltStreams)
561       UpdateIndexes.Add(mi.AltStreams[si].UpdateIndex);
562   }
563 
564   for (i = 0; i < tree.Dirs.Size(); i++)
565     WriteOrderList(tree.Dirs[i]);
566 }
567 
568 
AddTag_ToString(AString & s,const char * name,const char * value)569 static void AddTag_ToString(AString &s, const char *name, const char *value)
570 {
571   s += '<';
572   s += name;
573   s += '>';
574   s += value;
575   s += '<';
576   s += '/';
577   s += name;
578   s += '>';
579 }
580 
581 
AddTagUInt64_ToString(AString & s,const char * name,UInt64 value)582 static void AddTagUInt64_ToString(AString &s, const char *name, UInt64 value)
583 {
584   char temp[32];
585   ConvertUInt64ToString(value, temp);
586   AddTag_ToString(s, name, temp);
587 }
588 
589 
AddUniqueTag(CXmlItem & parentItem,const char * name)590 static CXmlItem &AddUniqueTag(CXmlItem &parentItem, const char *name)
591 {
592   int index = parentItem.FindSubTag(name);
593   if (index < 0)
594   {
595     CXmlItem &subItem = parentItem.SubItems.AddNew();
596     subItem.IsTag = true;
597     subItem.Name = name;
598     return subItem;
599   }
600   CXmlItem &subItem = parentItem.SubItems[index];
601   subItem.SubItems.Clear();
602   return subItem;
603 }
604 
605 
AddTag_UInt64_2(CXmlItem & item,UInt64 value)606 static void AddTag_UInt64_2(CXmlItem &item, UInt64 value)
607 {
608   CXmlItem &subItem = item.SubItems.AddNew();
609   subItem.IsTag = false;
610   char temp[32];
611   ConvertUInt64ToString(value, temp);
612   subItem.Name = temp;
613 }
614 
615 
AddTag_UInt64(CXmlItem & parentItem,const char * name,UInt64 value)616 static void AddTag_UInt64(CXmlItem &parentItem, const char *name, UInt64 value)
617 {
618   AddTag_UInt64_2(AddUniqueTag(parentItem, name), value);
619 }
620 
621 
AddTag_Hex(CXmlItem & item,const char * name,UInt32 value)622 static void AddTag_Hex(CXmlItem &item, const char *name, UInt32 value)
623 {
624   item.IsTag = true;
625   item.Name = name;
626   char temp[16];
627   temp[0] = '0';
628   temp[1] = 'x';
629   ConvertUInt32ToHex8Digits(value, temp + 2);
630   CXmlItem &subItem = item.SubItems.AddNew();
631   subItem.IsTag = false;
632   subItem.Name = temp;
633 }
634 
635 
AddTag_Time_2(CXmlItem & item,const FILETIME & ft)636 static void AddTag_Time_2(CXmlItem &item, const FILETIME &ft)
637 {
638   AddTag_Hex(item.SubItems.AddNew(), "HIGHPART", ft.dwHighDateTime);
639   AddTag_Hex(item.SubItems.AddNew(), "LOWPART", ft.dwLowDateTime);
640 }
641 
642 
AddTag_Time(CXmlItem & parentItem,const char * name,const FILETIME & ft)643 static void AddTag_Time(CXmlItem &parentItem, const char *name, const FILETIME &ft)
644 {
645   AddTag_Time_2(AddUniqueTag(parentItem, name), ft);
646 }
647 
648 
AddTag_String_IfEmpty(CXmlItem & parentItem,const char * name,const char * value)649 static void AddTag_String_IfEmpty(CXmlItem &parentItem, const char *name, const char *value)
650 {
651   int index = parentItem.FindSubTag(name);
652   if (index >= 0)
653     return;
654   CXmlItem &tag = parentItem.SubItems.AddNew();
655   tag.IsTag = true;
656   tag.Name = name;
657   CXmlItem &subItem = tag.SubItems.AddNew();
658   subItem.IsTag = false;
659   subItem.Name = value;
660 }
661 
662 
SetDefaultFields(bool useLZX)663 void CHeader::SetDefaultFields(bool useLZX)
664 {
665   Version = k_Version_NonSolid;
666   Flags = NHeaderFlags::kReparsePointFixup;
667   ChunkSize = 0;
668   if (useLZX)
669   {
670     Flags |= NHeaderFlags::kCompression | NHeaderFlags::kLZX;
671     ChunkSize = kChunkSize;
672     ChunkSizeBits = kChunkSizeBits;
673   }
674   g_RandomGenerator.Generate(Guid, 16);
675   PartNumber = 1;
676   NumParts = 1;
677   NumImages = 1;
678   BootIndex = 0;
679   OffsetResource.Clear();
680   XmlResource.Clear();
681   MetadataResource.Clear();
682   IntegrityResource.Clear();
683 }
684 
685 
AddTrees(CObjectVector<CDir> & trees,CObjectVector<CMetaItem> & metaItems,const CMetaItem & ri,int curTreeIndex)686 static void AddTrees(CObjectVector<CDir> &trees, CObjectVector<CMetaItem> &metaItems, const CMetaItem &ri, int curTreeIndex)
687 {
688   while (curTreeIndex >= (int)trees.Size())
689     trees.AddNew().Dirs.AddNew().MetaIndex = metaItems.Add(ri);
690 }
691 
692 
693 #define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z')
694 
695 
696 
UpdateItems(ISequentialOutStream * outSeqStream,UInt32 numItems,IArchiveUpdateCallback * callback)697 STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 numItems, IArchiveUpdateCallback *callback)
698 {
699   COM_TRY_BEGIN
700 
701   if (!IsUpdateSupported())
702     return E_NOTIMPL;
703 
704   bool isUpdate = (_volumes.Size() != 0);
705   int defaultImageIndex = _defaultImageNumber - 1;
706   bool showImageNumber;
707 
708   if (isUpdate)
709   {
710     showImageNumber = _showImageNumber;
711     if (!showImageNumber)
712       defaultImageIndex = _db.IndexOfUserImage;
713   }
714   else
715   {
716     showImageNumber = (_set_use_ShowImageNumber && _set_showImageNumber);
717     if (!showImageNumber)
718       defaultImageIndex = 0;
719   }
720 
721   if (defaultImageIndex >= kNumImagesMaxUpdate)
722     return E_NOTIMPL;
723 
724   CMyComPtr<IOutStream> outStream;
725   RINOK(outSeqStream->QueryInterface(IID_IOutStream, (void **)&outStream));
726   if (!outStream)
727     return E_NOTIMPL;
728   if (!callback)
729     return E_FAIL;
730 
731   CDb db;
732   CObjectVector<CDir> trees;
733 
734   CMetaItem ri; // default DIR item
735   FILETIME ftCur;
736   NTime::GetCurUtcFileTime(ftCur);
737   ri.MTime = ri.ATime = ri.CTime = ftCur;
738   ri.Attrib = FILE_ATTRIBUTE_DIRECTORY;
739   ri.IsDir = true;
740 
741 
742   // ---------- Detect changed images ----------
743 
744   unsigned i;
745   CBoolVector isChangedImage;
746   {
747     CUIntVector numUnchangedItemsInImage;
748     for (i = 0; i < _db.Images.Size(); i++)
749     {
750       numUnchangedItemsInImage.Add(0);
751       isChangedImage.Add(false);
752     }
753 
754     for (i = 0; i < numItems; i++)
755     {
756       UInt32 indexInArchive;
757       Int32 newData, newProps;
758       RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive));
759       if (newProps == 0)
760       {
761         if (indexInArchive >= _db.SortedItems.Size())
762           continue;
763         const CItem &item = _db.Items[_db.SortedItems[indexInArchive]];
764         if (newData == 0)
765         {
766           if (item.ImageIndex >= 0)
767             numUnchangedItemsInImage[item.ImageIndex]++;
768         }
769         else
770         {
771           // oldProps & newData. Current version of 7-Zip doesn't use it
772           if (item.ImageIndex >= 0)
773             isChangedImage[item.ImageIndex] = true;
774         }
775       }
776       else if (!showImageNumber)
777       {
778         if (defaultImageIndex >= 0 && defaultImageIndex < (int)isChangedImage.Size())
779           isChangedImage[defaultImageIndex] = true;
780       }
781       else
782       {
783         NCOM::CPropVariant prop;
784         RINOK(callback->GetProperty(i, kpidPath, &prop));
785 
786         if (prop.vt != VT_BSTR)
787           return E_INVALIDARG;
788         const wchar_t *path = prop.bstrVal;
789         if (!path)
790           return E_INVALIDARG;
791 
792         const wchar_t *end;
793         UInt64 val = ConvertStringToUInt64(path, &end);
794         if (end == path)
795           return E_INVALIDARG;
796         if (val == 0 || val > kNumImagesMaxUpdate)
797           return E_INVALIDARG;
798         wchar_t c = *end;
799         if (c != 0 && c != ':' && c != L'/' && c != WCHAR_PATH_SEPARATOR)
800           return E_INVALIDARG;
801         unsigned imageIndex = (unsigned)val - 1;
802         if (imageIndex < _db.Images.Size())
803           isChangedImage[imageIndex] = true;
804         if (_defaultImageNumber > 0 && val != (unsigned)_defaultImageNumber)
805           return E_INVALIDARG;
806       }
807     }
808 
809     for (i = 0; i < _db.Images.Size(); i++)
810       if (!isChangedImage[i])
811         isChangedImage[i] = _db.GetNumUserItemsInImage(i) != numUnchangedItemsInImage[i];
812   }
813 
814   if (defaultImageIndex >= 0)
815   {
816     for (i = 0; i < _db.Images.Size(); i++)
817       if ((int)i != defaultImageIndex)
818         isChangedImage[i] = false;
819   }
820 
821   CMyComPtr<IArchiveGetRawProps> getRawProps;
822   callback->QueryInterface(IID_IArchiveGetRawProps, (void **)&getRawProps);
823 
824   CMyComPtr<IArchiveGetRootProps> getRootProps;
825   callback->QueryInterface(IID_IArchiveGetRootProps, (void **)&getRootProps);
826 
827   CObjectVector<CUniqBlocks> secureBlocks;
828 
829   if (!showImageNumber && (getRootProps || isUpdate) &&
830       (
831         defaultImageIndex >= (int)isChangedImage.Size()
832         || defaultImageIndex < 0 // test it
833         || isChangedImage[defaultImageIndex]
834       ))
835   {
836     // Fill Root Item: Metadata and security
837     CMetaItem rootItem = ri;
838     {
839       const void *data = NULL;
840       UInt32 dataSize = 0;
841       UInt32 propType = 0;
842       if (getRootProps)
843       {
844         RINOK(getRootProps->GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType));
845       }
846       if (dataSize == 0 && isUpdate)
847       {
848         RINOK(GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType));
849       }
850       if (dataSize != 0)
851       {
852         if (propType != NPropDataType::kRaw)
853           return E_FAIL;
854         while (defaultImageIndex >= (int)secureBlocks.Size())
855           secureBlocks.AddNew();
856         CUniqBlocks &secUniqBlocks = secureBlocks[defaultImageIndex];
857         rootItem.SecurityId = secUniqBlocks.AddUniq((const Byte *)data, dataSize);
858       }
859     }
860 
861     IArchiveGetRootProps *thisGetRoot = isUpdate ? this : NULL;
862 
863     RINOK(GetRootTime(getRootProps, thisGetRoot, kpidCTime, rootItem.CTime));
864     RINOK(GetRootTime(getRootProps, thisGetRoot, kpidATime, rootItem.ATime));
865     RINOK(GetRootTime(getRootProps, thisGetRoot, kpidMTime, rootItem.MTime));
866 
867     {
868       NCOM::CPropVariant prop;
869       if (getRootProps)
870       {
871         RINOK(getRootProps->GetRootProp(kpidAttrib, &prop));
872         if (prop.vt == VT_UI4)
873           rootItem.Attrib = prop.ulVal;
874         else if (prop.vt != VT_EMPTY)
875           return E_INVALIDARG;
876       }
877       if (prop.vt == VT_EMPTY && thisGetRoot)
878       {
879         RINOK(GetRootProp(kpidAttrib, &prop));
880         if (prop.vt == VT_UI4)
881           rootItem.Attrib = prop.ulVal;
882         else if (prop.vt != VT_EMPTY)
883           return E_INVALIDARG;
884       }
885       rootItem.Attrib |= FILE_ATTRIBUTE_DIRECTORY;
886     }
887 
888     AddTrees(trees, db.MetaItems, ri, defaultImageIndex);
889     db.MetaItems[trees[defaultImageIndex].Dirs[0].MetaIndex] = rootItem;
890   }
891 
892   // ---------- Request Metadata for changed items ----------
893 
894   UString fileName;
895 
896   for (i = 0; i < numItems; i++)
897   {
898     CUpdateItem ui;
899     UInt32 indexInArchive;
900     Int32 newData, newProps;
901     RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive));
902 
903     if (newData == 0 || newProps == 0)
904     {
905       if (indexInArchive >= _db.SortedItems.Size())
906         continue;
907 
908       const CItem &item = _db.Items[_db.SortedItems[indexInArchive]];
909 
910       if (item.ImageIndex >= 0)
911       {
912         if (!isChangedImage[item.ImageIndex])
913         {
914           if (newData == 0 && newProps == 0)
915             continue;
916           return E_FAIL;
917         }
918       }
919       else
920       {
921         // if deleted item was not renamed, we just skip it
922         if (newProps == 0)
923           continue;
924         if (item.StreamIndex >= 0)
925         {
926           // we don't support property change for SolidBig streams
927           if (_db.DataStreams[item.StreamIndex].Resource.IsSolidBig())
928             return E_NOTIMPL;
929         }
930       }
931 
932       if (newData == 0)
933         ui.InArcIndex = indexInArchive;
934     }
935 
936     // we set arcIndex only if we must use old props
937     Int32 arcIndex = (newProps ? -1 : indexInArchive);
938 
939     bool isDir = false;
940     {
941       NCOM::CPropVariant prop;
942       RINOK(GetOutProperty(callback, i, arcIndex, kpidIsDir, &prop));
943       if (prop.vt == VT_BOOL)
944         isDir = (prop.boolVal != VARIANT_FALSE);
945       else if (prop.vt != VT_EMPTY)
946         return E_INVALIDARG;
947     }
948 
949     bool isAltStream = false;
950     {
951       NCOM::CPropVariant prop;
952       RINOK(GetOutProperty(callback, i, arcIndex, kpidIsAltStream, &prop));
953       if (prop.vt == VT_BOOL)
954         isAltStream = (prop.boolVal != VARIANT_FALSE);
955       else if (prop.vt != VT_EMPTY)
956         return E_INVALIDARG;
957     }
958 
959     if (isDir && isAltStream)
960       return E_INVALIDARG;
961 
962     UInt64 size = 0;
963     UInt64 iNode = 0;
964 
965     if (!isDir)
966     {
967       if (!newData)
968       {
969         NCOM::CPropVariant prop;
970         GetProperty(indexInArchive, kpidINode, &prop);
971         if (prop.vt == VT_UI8)
972           iNode = prop.uhVal.QuadPart;
973       }
974 
975       NCOM::CPropVariant prop;
976 
977       if (newData)
978       {
979         RINOK(callback->GetProperty(i, kpidSize, &prop));
980       }
981       else
982       {
983         RINOK(GetProperty(indexInArchive, kpidSize, &prop));
984       }
985 
986       if (prop.vt == VT_UI8)
987         size = prop.uhVal.QuadPart;
988       else if (prop.vt != VT_EMPTY)
989         return E_INVALIDARG;
990     }
991 
992     {
993       NCOM::CPropVariant propPath;
994       const wchar_t *path = NULL;
995       RINOK(GetOutProperty(callback, i, arcIndex, kpidPath, &propPath));
996       if (propPath.vt == VT_BSTR)
997         path = propPath.bstrVal;
998       else if (propPath.vt != VT_EMPTY)
999         return E_INVALIDARG;
1000 
1001     if (!path)
1002       return E_INVALIDARG;
1003 
1004     CDir *curItem = NULL;
1005     bool isRootImageDir = false;
1006     fileName.Empty();
1007 
1008     int imageIndex;
1009 
1010     if (!showImageNumber)
1011     {
1012       imageIndex = defaultImageIndex;
1013       AddTrees(trees, db.MetaItems, ri, imageIndex);
1014       curItem = &trees[imageIndex].Dirs[0];
1015     }
1016     else
1017     {
1018       const wchar_t *end;
1019       UInt64 val = ConvertStringToUInt64(path, &end);
1020       if (end == path)
1021         return E_INVALIDARG;
1022       if (val == 0 || val > kNumImagesMaxUpdate)
1023         return E_INVALIDARG;
1024 
1025       imageIndex = (int)val - 1;
1026       if (imageIndex < (int)isChangedImage.Size())
1027         if (!isChangedImage[imageIndex])
1028           return E_FAIL;
1029 
1030       AddTrees(trees, db.MetaItems, ri, imageIndex);
1031       curItem = &trees[imageIndex].Dirs[0];
1032       wchar_t c = *end;
1033 
1034       if (c == 0)
1035       {
1036         if (!isDir || isAltStream)
1037           return E_INVALIDARG;
1038         ui.MetaIndex = curItem->MetaIndex;
1039         isRootImageDir = true;
1040       }
1041       else if (c == ':')
1042       {
1043         if (isDir || !isAltStream)
1044           return E_INVALIDARG;
1045         ui.MetaIndex = curItem->MetaIndex;
1046         CAltStream ss;
1047         ss.Size = size;
1048         ss.Name = end + 1;
1049         ss.UpdateIndex = db.UpdateItems.Size();
1050         ui.AltStreamIndex = db.MetaItems[ui.MetaIndex].AltStreams.Add(ss);
1051       }
1052       else if (c == WCHAR_PATH_SEPARATOR || c == L'/')
1053       {
1054         path = end + 1;
1055         if (*path == 0)
1056           return E_INVALIDARG;
1057       }
1058       else
1059         return E_INVALIDARG;
1060     }
1061 
1062     if (ui.MetaIndex < 0)
1063     {
1064       for (;;)
1065       {
1066         wchar_t c = *path++;
1067         if (c == 0)
1068           break;
1069         if (c == WCHAR_PATH_SEPARATOR || c == L'/')
1070         {
1071           unsigned indexOfDir;
1072           if (!curItem->FindDir(db.MetaItems, fileName, indexOfDir))
1073           {
1074             CDir &dir = curItem->Dirs.InsertNew(indexOfDir);
1075             dir.MetaIndex = db.MetaItems.Add(ri);
1076             db.MetaItems.Back().Name = fileName;
1077           }
1078           curItem = &curItem->Dirs[indexOfDir];
1079           fileName.Empty();
1080         }
1081         else
1082           fileName += c;
1083       }
1084 
1085       if (isAltStream)
1086       {
1087         int colonPos = fileName.Find(L':');
1088         if (colonPos < 0)
1089           return E_INVALIDARG;
1090 
1091         // we want to support cases of c::substream, where c: is drive name
1092         if (colonPos == 1 && fileName[2] == L':' && IS_LETTER_CHAR(fileName[0]))
1093           colonPos = 2;
1094         const UString mainName = fileName.Left(colonPos);
1095         unsigned indexOfDir;
1096 
1097         if (mainName.IsEmpty())
1098           ui.MetaIndex = curItem->MetaIndex;
1099         else if (curItem->FindDir(db.MetaItems, mainName, indexOfDir))
1100           ui.MetaIndex = curItem->Dirs[indexOfDir].MetaIndex;
1101         else
1102         {
1103           for (int j = (int)curItem->Files.Size() - 1; j >= 0; j--)
1104           {
1105             int metaIndex = curItem->Files[j];
1106             const CMetaItem &mi = db.MetaItems[metaIndex];
1107             if (CompareFileNames(mainName, mi.Name) == 0)
1108             {
1109               ui.MetaIndex = metaIndex;
1110               break;
1111             }
1112           }
1113         }
1114 
1115         if (ui.MetaIndex >= 0)
1116         {
1117           CAltStream ss;
1118           ss.Size = size;
1119           ss.Name = fileName.Ptr(colonPos + 1);
1120           ss.UpdateIndex = db.UpdateItems.Size();
1121           ui.AltStreamIndex = db.MetaItems[ui.MetaIndex].AltStreams.Add(ss);
1122         }
1123       }
1124     }
1125 
1126 
1127     if (ui.MetaIndex < 0 || isRootImageDir)
1128     {
1129       if (!isRootImageDir)
1130       {
1131         ui.MetaIndex = db.MetaItems.Size();
1132         db.MetaItems.AddNew();
1133       }
1134 
1135       CMetaItem &mi = db.MetaItems[ui.MetaIndex];
1136       mi.Size = size;
1137       mi.IsDir = isDir;
1138       mi.Name = fileName;
1139       mi.UpdateIndex = db.UpdateItems.Size();
1140       {
1141         NCOM::CPropVariant prop;
1142         RINOK(GetOutProperty(callback, i, arcIndex, kpidAttrib, &prop));
1143         if (prop.vt == VT_EMPTY)
1144           mi.Attrib = 0;
1145         else if (prop.vt == VT_UI4)
1146           mi.Attrib = prop.ulVal;
1147         else
1148           return E_INVALIDARG;
1149         if (isDir)
1150           mi.Attrib |= FILE_ATTRIBUTE_DIRECTORY;
1151       }
1152       RINOK(GetTime(callback, i, arcIndex, kpidCTime, mi.CTime));
1153       RINOK(GetTime(callback, i, arcIndex, kpidATime, mi.ATime));
1154       RINOK(GetTime(callback, i, arcIndex, kpidMTime, mi.MTime));
1155 
1156       {
1157         NCOM::CPropVariant prop;
1158         RINOK(GetOutProperty(callback, i, arcIndex, kpidShortName, &prop));
1159         if (prop.vt == VT_BSTR)
1160           mi.ShortName.SetFromBstr(prop.bstrVal);
1161         else if (prop.vt != VT_EMPTY)
1162           return E_INVALIDARG;
1163       }
1164 
1165       while (imageIndex >= (int)secureBlocks.Size())
1166         secureBlocks.AddNew();
1167 
1168       if (!isAltStream && (getRawProps || arcIndex >= 0))
1169       {
1170         CUniqBlocks &secUniqBlocks = secureBlocks[imageIndex];
1171         const void *data;
1172         UInt32 dataSize;
1173         UInt32 propType;
1174 
1175         data = NULL;
1176         dataSize = 0;
1177         propType = 0;
1178 
1179         if (arcIndex >= 0)
1180         {
1181           GetRawProp(arcIndex, kpidNtSecure, &data, &dataSize, &propType);
1182         }
1183         else
1184         {
1185           getRawProps->GetRawProp(i, kpidNtSecure, &data, &dataSize, &propType);
1186         }
1187 
1188         if (dataSize != 0)
1189         {
1190           if (propType != NPropDataType::kRaw)
1191             return E_FAIL;
1192           mi.SecurityId = secUniqBlocks.AddUniq((const Byte *)data, dataSize);
1193         }
1194 
1195         data = NULL;
1196         dataSize = 0;
1197         propType = 0;
1198 
1199         if (arcIndex >= 0)
1200         {
1201           GetRawProp(arcIndex, kpidNtReparse, &data, &dataSize, &propType);
1202         }
1203         else
1204         {
1205           getRawProps->GetRawProp(i, kpidNtReparse, &data, &dataSize, &propType);
1206         }
1207 
1208         if (dataSize != 0)
1209         {
1210           if (propType != NPropDataType::kRaw)
1211             return E_FAIL;
1212           mi.Reparse.CopyFrom((const Byte *)data, dataSize);
1213         }
1214       }
1215 
1216       if (!isRootImageDir)
1217       {
1218         if (isDir)
1219         {
1220           unsigned indexOfDir;
1221           if (curItem->FindDir(db.MetaItems, fileName, indexOfDir))
1222             curItem->Dirs[indexOfDir].MetaIndex = ui.MetaIndex;
1223           else
1224             curItem->Dirs.InsertNew(indexOfDir).MetaIndex = ui.MetaIndex;
1225         }
1226         else
1227           curItem->Files.Add(ui.MetaIndex);
1228       }
1229     }
1230 
1231     }
1232 
1233     if (iNode != 0 && ui.MetaIndex >= 0 && ui.AltStreamIndex < 0)
1234       db.MetaItems[ui.MetaIndex].FileID = iNode;
1235 
1236     ui.CallbackIndex = i;
1237     db.UpdateItems.Add(ui);
1238   }
1239 
1240   unsigned numNewImages = trees.Size();
1241   for (i = numNewImages; i < isChangedImage.Size(); i++)
1242     if (!isChangedImage[i])
1243       numNewImages = i + 1;
1244 
1245   AddTrees(trees, db.MetaItems, ri, numNewImages - 1);
1246 
1247   for (i = 0; i < trees.Size(); i++)
1248     if (i >= isChangedImage.Size() || isChangedImage[i])
1249       db.WriteOrderList(trees[i]);
1250 
1251 
1252   UInt64 complexity = 0;
1253 
1254   unsigned numDataStreams = _db.DataStreams.Size();
1255   CUIntArr streamsRefs(numDataStreams);
1256   for (i = 0; i < numDataStreams; i++)
1257     streamsRefs[i] = 0;
1258 
1259   // ---------- Calculate Streams Refs Counts in unchanged images
1260 
1261   for (i = 0; i < _db.Images.Size(); i++)
1262   {
1263     if (isChangedImage[i])
1264       continue;
1265     complexity += _db.MetaStreams[i].Resource.PackSize;
1266     const CImage &image = _db.Images[i];
1267     unsigned endItem = image.StartItem + image.NumItems;
1268     for (unsigned k = image.StartItem; k < endItem; k++)
1269     {
1270       const CItem &item = _db.Items[k];
1271       if (item.StreamIndex >= 0)
1272         streamsRefs[(unsigned)item.StreamIndex]++;
1273     }
1274   }
1275 
1276 
1277   // ---------- Update Streams Refs Counts in changed images
1278 
1279   for (i = 0; i < db.UpdateIndexes.Size(); i++)
1280   {
1281     const CUpdateItem &ui = db.UpdateItems[db.UpdateIndexes[i]];
1282 
1283     if (ui.InArcIndex >= 0)
1284     {
1285       if ((unsigned)ui.InArcIndex >= _db.SortedItems.Size())
1286         continue;
1287       const CItem &item = _db.Items[_db.SortedItems[ui.InArcIndex]];
1288       if (item.StreamIndex >= 0)
1289         streamsRefs[(unsigned)item.StreamIndex]++;
1290     }
1291     else
1292     {
1293       const CMetaItem &mi = db.MetaItems[ui.MetaIndex];
1294       UInt64 size;
1295       if (ui.AltStreamIndex < 0)
1296         size = mi.Size;
1297       else
1298         size = mi.AltStreams[ui.AltStreamIndex].Size;
1299       complexity += size;
1300     }
1301   }
1302 
1303   // Clear ref counts for SolidBig streams
1304 
1305   for (i = 0; i < _db.DataStreams.Size(); i++)
1306     if (_db.DataStreams[i].Resource.IsSolidBig())
1307       streamsRefs[i] = 0;
1308 
1309   // Set ref counts for SolidBig streams
1310 
1311   for (i = 0; i < _db.DataStreams.Size(); i++)
1312     if (streamsRefs[i] != 0)
1313     {
1314       const CResource &rs = _db.DataStreams[i].Resource;
1315       if (rs.IsSolidSmall())
1316         streamsRefs[_db.Solids[rs.SolidIndex].StreamIndex] = 1;
1317     }
1318 
1319   for (i = 0; i < _db.DataStreams.Size(); i++)
1320     if (streamsRefs[i] != 0)
1321     {
1322       const CResource &rs = _db.DataStreams[i].Resource;
1323       if (!rs.IsSolidSmall())
1324         complexity += rs.PackSize;
1325     }
1326 
1327   RINOK(callback->SetTotal(complexity));
1328   UInt64 totalComplexity = complexity;
1329 
1330   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
1331   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
1332 
1333   CLocalProgress *lps = new CLocalProgress;
1334   CMyComPtr<ICompressProgressInfo> progress = lps;
1335   lps->Init(callback, true);
1336 
1337   complexity = 0;
1338 
1339   // bool useResourceCompression = false;
1340   // use useResourceCompression only if CHeader::Flags compression is also set
1341 
1342   CHeader header;
1343   header.SetDefaultFields(false);
1344 
1345   if (isUpdate)
1346   {
1347     const CHeader &srcHeader = _volumes[1].Header;
1348     header.Flags = srcHeader.Flags;
1349     header.Version = srcHeader.Version;
1350     header.ChunkSize = srcHeader.ChunkSize;
1351     header.ChunkSizeBits = srcHeader.ChunkSizeBits;
1352   }
1353 
1354   {
1355     Byte buf[kHeaderSizeMax];
1356     header.WriteTo(buf);
1357     RINOK(WriteStream(outStream, buf, kHeaderSizeMax));
1358   }
1359 
1360   UInt64 curPos = kHeaderSizeMax;
1361 
1362   CInStreamWithSha1 *inShaStreamSpec = new CInStreamWithSha1;
1363   CMyComPtr<ISequentialInStream> inShaStream = inShaStreamSpec;
1364 
1365   CLimitedSequentialInStream *inStreamLimitedSpec = NULL;
1366   CMyComPtr<CLimitedSequentialInStream> inStreamLimited;
1367   if (_volumes.Size() == 2)
1368   {
1369     inStreamLimitedSpec = new CLimitedSequentialInStream;
1370     inStreamLimited = inStreamLimitedSpec;
1371     inStreamLimitedSpec->SetStream(_volumes[1].Stream);
1372   }
1373 
1374 
1375   CRecordVector<CStreamInfo> streams;
1376   CUIntVector sortedHashes; // indexes to streams, sorted by SHA1
1377 
1378   // ---------- Copy unchanged data streams ----------
1379 
1380   UInt64 solidRunOffset = 0;
1381   UInt64 curSolidSize = 0;
1382 
1383   for (i = 0; i < _db.DataStreams.Size(); i++)
1384   {
1385     const CStreamInfo &siOld = _db.DataStreams[i];
1386     const CResource &rs = siOld.Resource;
1387 
1388     unsigned numRefs = streamsRefs[i];
1389 
1390     if (numRefs == 0)
1391     {
1392       if (!rs.IsSolidSmall())
1393         continue;
1394       if (streamsRefs[_db.Solids[rs.SolidIndex].StreamIndex] == 0)
1395         continue;
1396     }
1397 
1398     lps->InSize = lps->OutSize = complexity;
1399     RINOK(lps->SetCur());
1400 
1401     int streamIndex = streams.Size();
1402     CStreamInfo s;
1403     s.Resource = rs;
1404     s.PartNumber = 1;
1405     s.RefCount = numRefs;
1406 
1407     memcpy(s.Hash, siOld.Hash, kHashSize);
1408 
1409     if (rs.IsSolid())
1410     {
1411       CSolid &ss = _db.Solids[rs.SolidIndex];
1412       if (rs.IsSolidSmall())
1413       {
1414         UInt64 oldOffset = ss.SolidOffset;
1415         if (rs.Offset < oldOffset)
1416           return E_FAIL;
1417         UInt64 relatOffset = rs.Offset - oldOffset;
1418         s.Resource.Offset = solidRunOffset + relatOffset;
1419       }
1420       else
1421       {
1422         // IsSolidBig
1423         solidRunOffset += curSolidSize;
1424         curSolidSize = ss.UnpackSize;
1425       }
1426     }
1427     else
1428     {
1429       solidRunOffset = 0;
1430       curSolidSize = 0;
1431     }
1432 
1433     if (!rs.IsSolid() || rs.IsSolidSmall())
1434     {
1435       int find = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, streamIndex);
1436       if (find >= 0)
1437         return E_FAIL; // two streams with same SHA-1
1438     }
1439 
1440     if (!rs.IsSolid() || rs.IsSolidBig())
1441     {
1442       RINOK(_volumes[siOld.PartNumber].Stream->Seek(rs.Offset, STREAM_SEEK_SET, NULL));
1443       inStreamLimitedSpec->Init(rs.PackSize);
1444       RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress));
1445       if (copyCoderSpec->TotalSize != rs.PackSize)
1446         return E_FAIL;
1447       s.Resource.Offset = curPos;
1448       curPos += rs.PackSize;
1449       lps->ProgressOffset += rs.PackSize;
1450     }
1451 
1452     streams.Add(s);
1453   }
1454 
1455 
1456   // ---------- Write new items ----------
1457 
1458   CUIntVector hlIndexes; // sorted indexes for hard link items
1459 
1460   for (i = 0; i < db.UpdateIndexes.Size(); i++)
1461   {
1462     lps->InSize = lps->OutSize = complexity;
1463     RINOK(lps->SetCur());
1464     const CUpdateItem &ui = db.UpdateItems[db.UpdateIndexes[i]];
1465     CMetaItem &mi = db.MetaItems[ui.MetaIndex];
1466     UInt64 size = 0;
1467 
1468     if (ui.AltStreamIndex >= 0)
1469     {
1470       if (mi.Skip)
1471         continue;
1472       size = mi.AltStreams[ui.AltStreamIndex].Size;
1473     }
1474     else
1475     {
1476       size = mi.Size;
1477       if (mi.IsDir)
1478       {
1479         // we support LINK files here
1480         if (mi.Reparse.Size() == 0)
1481           continue;
1482       }
1483     }
1484 
1485     if (ui.InArcIndex >= 0)
1486     {
1487       // data streams with OLD Data were written already
1488       // we just need to find HashIndex in hashes.
1489 
1490       if ((unsigned)ui.InArcIndex >= _db.SortedItems.Size())
1491         return E_FAIL;
1492 
1493       const CItem &item = _db.Items[_db.SortedItems[ui.InArcIndex]];
1494 
1495       if (item.StreamIndex < 0)
1496       {
1497         if (size == 0)
1498           continue;
1499         // if (_db.ItemHasStream(item))
1500         return E_FAIL;
1501       }
1502 
1503       // We support empty file (size = 0, but with stream and SHA-1) from old archive
1504 
1505       const CStreamInfo &siOld = _db.DataStreams[item.StreamIndex];
1506 
1507       int index = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, -1);
1508       // we must have written that stream already
1509       if (index < 0)
1510         return E_FAIL;
1511 
1512       if (ui.AltStreamIndex < 0)
1513         mi.HashIndex = index;
1514       else
1515         mi.AltStreams[ui.AltStreamIndex].HashIndex = index;
1516 
1517       continue;
1518     }
1519 
1520     CMyComPtr<ISequentialInStream> fileInStream;
1521     HRESULT res = callback->GetStream(ui.CallbackIndex, &fileInStream);
1522 
1523     if (res == S_FALSE)
1524     {
1525       if (ui.AltStreamIndex >= 0)
1526       {
1527         mi.NumSkipAltStreams++;
1528         mi.AltStreams[ui.AltStreamIndex].Skip = true;
1529       }
1530       else
1531         mi.Skip = true;
1532     }
1533     else
1534     {
1535       RINOK(res);
1536 
1537       int miIndex = -1;
1538 
1539       if (!fileInStream)
1540       {
1541         if (!mi.IsDir)
1542           return E_INVALIDARG;
1543       }
1544       else if (ui.AltStreamIndex < 0)
1545       {
1546         CMyComPtr<IStreamGetProps2> getProps2;
1547         fileInStream->QueryInterface(IID_IStreamGetProps2, (void **)&getProps2);
1548         if (getProps2)
1549         {
1550           CStreamFileProps props;
1551           if (getProps2->GetProps2(&props) == S_OK)
1552           {
1553             mi.Attrib = props.Attrib;
1554             mi.CTime = props.CTime;
1555             mi.ATime = props.ATime;
1556             mi.MTime = props.MTime;
1557             mi.FileID = props.FileID_Low;
1558             if (props.NumLinks <= 1)
1559               mi.FileID = 0;
1560             mi.VolID = props.VolID;
1561             if (mi.FileID != 0)
1562               miIndex = AddToHardLinkList(db.MetaItems, ui.MetaIndex, hlIndexes);
1563 
1564             if (props.Size != size && props.Size != (UInt64)(Int64)-1)
1565             {
1566               Int64 delta = (Int64)props.Size - (Int64)size;
1567               Int64 newComplexity = totalComplexity + delta;
1568               if (newComplexity > 0)
1569               {
1570                 totalComplexity = newComplexity;
1571                 callback->SetTotal(totalComplexity);
1572               }
1573               mi.Size = props.Size;
1574               size = props.Size;
1575             }
1576           }
1577         }
1578       }
1579 
1580       if (miIndex >= 0)
1581       {
1582         mi.HashIndex = db.MetaItems[miIndex].HashIndex;
1583         if (mi.HashIndex >= 0)
1584           streams[mi.HashIndex].RefCount++;
1585         // fix for future: maybe we need to check also that real size is equal to size from IStreamGetProps2
1586       }
1587       else if (ui.AltStreamIndex < 0 && mi.Reparse.Size() != 0)
1588       {
1589         if (mi.Reparse.Size() < 8)
1590           return E_FAIL;
1591         NCrypto::NSha1::CContext sha1;
1592         sha1.Init();
1593         size_t packSize = mi.Reparse.Size() - 8;
1594         sha1.Update((const Byte *)mi.Reparse + 8, packSize);
1595         Byte hash[kHashSize];
1596         sha1.Final(hash);
1597 
1598         int index = AddUniqHash(&streams.Front(), sortedHashes, hash, streams.Size());
1599 
1600         if (index >= 0)
1601           streams[index].RefCount++;
1602         else
1603         {
1604           index = streams.Size();
1605           RINOK(WriteStream(outStream, (const Byte *)mi.Reparse + 8, packSize));
1606           CStreamInfo s;
1607           s.Resource.PackSize = packSize;
1608           s.Resource.Offset = curPos;
1609           s.Resource.UnpackSize = packSize;
1610           s.Resource.Flags = 0; // check it
1611           /*
1612             if (useResourceCompression)
1613               s.Resource.Flags = NResourceFlags::Compressed;
1614           */
1615           s.PartNumber = 1;
1616           s.RefCount = 1;
1617           memcpy(s.Hash, hash, kHashSize);
1618           curPos += packSize;
1619 
1620           streams.Add(s);
1621         }
1622 
1623         mi.HashIndex = index;
1624       }
1625       else
1626       {
1627         inShaStreamSpec->SetStream(fileInStream);
1628         fileInStream.Release();
1629         inShaStreamSpec->Init();
1630         UInt64 offsetBlockSize = 0;
1631         /*
1632         if (useResourceCompression)
1633         {
1634           for (UInt64 t = kChunkSize; t < size; t += kChunkSize)
1635           {
1636             Byte buf[8];
1637             SetUi32(buf, (UInt32)t);
1638             RINOK(WriteStream(outStream, buf, 4));
1639             offsetBlockSize += 4;
1640           }
1641         }
1642         */
1643 
1644         RINOK(copyCoder->Code(inShaStream, outStream, NULL, NULL, progress));
1645         size = copyCoderSpec->TotalSize;
1646 
1647         if (size != 0)
1648         {
1649           Byte hash[kHashSize];
1650           UInt64 packSize = offsetBlockSize + size;
1651           inShaStreamSpec->Final(hash);
1652 
1653           int index = AddUniqHash(&streams.Front(), sortedHashes, hash, streams.Size());
1654 
1655           if (index >= 0)
1656           {
1657             streams[index].RefCount++;
1658             outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos);
1659             outStream->SetSize(curPos);
1660           }
1661           else
1662           {
1663             index = streams.Size();
1664             CStreamInfo s;
1665             s.Resource.PackSize = packSize;
1666             s.Resource.Offset = curPos;
1667             s.Resource.UnpackSize = size;
1668             s.Resource.Flags = 0;
1669             /*
1670             if (useResourceCompression)
1671             s.Resource.Flags = NResourceFlags::Compressed;
1672             */
1673             s.PartNumber = 1;
1674             s.RefCount = 1;
1675             memcpy(s.Hash, hash, kHashSize);
1676             curPos += packSize;
1677 
1678             streams.Add(s);
1679           }
1680 
1681           if (ui.AltStreamIndex < 0)
1682             mi.HashIndex = index;
1683           else
1684             mi.AltStreams[ui.AltStreamIndex].HashIndex = index;
1685         }
1686       }
1687     }
1688     fileInStream.Release();
1689     complexity += size;
1690     RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
1691   }
1692 
1693   while (secureBlocks.Size() < numNewImages)
1694     secureBlocks.AddNew();
1695 
1696 
1697 
1698   // ---------- Write Images ----------
1699 
1700   for (i = 0; i < numNewImages; i++)
1701   {
1702     lps->InSize = lps->OutSize = complexity;
1703     RINOK(lps->SetCur());
1704     if (i < isChangedImage.Size() && !isChangedImage[i])
1705     {
1706       CStreamInfo s = _db.MetaStreams[i];
1707 
1708       RINOK(_volumes[1].Stream->Seek(s.Resource.Offset, STREAM_SEEK_SET, NULL));
1709       inStreamLimitedSpec->Init(s.Resource.PackSize);
1710       RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress));
1711       if (copyCoderSpec->TotalSize != s.Resource.PackSize)
1712         return E_FAIL;
1713 
1714       s.Resource.Offset = curPos;
1715       s.PartNumber = 1;
1716       s.RefCount = 1;
1717       streams.Add(s);
1718 
1719       if (_bootIndex != 0 && _bootIndex == (UInt32)i + 1)
1720       {
1721         header.MetadataResource = s.Resource;
1722         header.BootIndex = _bootIndex;
1723       }
1724 
1725       lps->ProgressOffset += s.Resource.PackSize;
1726       curPos += s.Resource.PackSize;
1727       // printf("\nWrite old image %x\n", i + 1);
1728       continue;
1729     }
1730 
1731     const CDir &tree = trees[i];
1732     const UInt32 kSecuritySize = 8;
1733 
1734     size_t pos = kSecuritySize;
1735 
1736     const CUniqBlocks &secUniqBlocks = secureBlocks[i];
1737     const CObjectVector<CByteBuffer> &secBufs = secUniqBlocks.Bufs;
1738     pos += (size_t)secUniqBlocks.GetTotalSizeInBytes();
1739     pos += secBufs.Size() * 8;
1740     pos = (pos + 7) & ~(size_t)7;
1741 
1742     db.DefaultDirItem = ri;
1743     pos += db.WriteTree_Dummy(tree);
1744 
1745     CByteArr meta(pos);
1746 
1747     Set32((Byte *)meta + 4, secBufs.Size()); // num security entries
1748     pos = kSecuritySize;
1749 
1750     if (secBufs.Size() == 0)
1751     {
1752       // we can write 0 here only if there is no security data, imageX does it,
1753       // but some programs expect size = 8
1754       Set32((Byte *)meta, 8); // size of security data
1755       // Set32((Byte *)meta, 0);
1756     }
1757     else
1758     {
1759       unsigned k;
1760       for (k = 0; k < secBufs.Size(); k++, pos += 8)
1761       {
1762         Set64(meta + pos, secBufs[k].Size());
1763       }
1764       for (k = 0; k < secBufs.Size(); k++)
1765       {
1766         const CByteBuffer &buf = secBufs[k];
1767         size_t size = buf.Size();
1768         if (size != 0)
1769         {
1770           memcpy(meta + pos, buf, size);
1771           pos += size;
1772         }
1773       }
1774       while ((pos & 7) != 0)
1775         meta[pos++] = 0;
1776       Set32((Byte *)meta, (UInt32)pos); // size of security data
1777     }
1778 
1779     db.Hashes = &streams.Front();
1780     db.WriteTree(tree, (Byte *)meta, pos);
1781 
1782     {
1783       NCrypto::NSha1::CContext sha;
1784       sha.Init();
1785       sha.Update((const Byte *)meta, pos);
1786 
1787       Byte digest[kHashSize];
1788       sha.Final(digest);
1789 
1790       CStreamInfo s;
1791       s.Resource.PackSize = pos;
1792       s.Resource.Offset = curPos;
1793       s.Resource.UnpackSize = pos;
1794       s.Resource.Flags = NResourceFlags::kMetadata;
1795       s.PartNumber = 1;
1796       s.RefCount = 1;
1797       memcpy(s.Hash, digest, kHashSize);
1798       streams.Add(s);
1799 
1800       if (_bootIndex != 0 && _bootIndex == (UInt32)i + 1)
1801       {
1802         header.MetadataResource = s.Resource;
1803         header.BootIndex = _bootIndex;
1804       }
1805 
1806       RINOK(WriteStream(outStream, (const Byte *)meta, pos));
1807       meta.Free();
1808       curPos += pos;
1809     }
1810   }
1811 
1812   lps->InSize = lps->OutSize = complexity;
1813   RINOK(lps->SetCur());
1814 
1815   header.OffsetResource.UnpackSize = header.OffsetResource.PackSize = (UInt64)streams.Size() * kStreamInfoSize;
1816   header.OffsetResource.Offset = curPos;
1817   header.OffsetResource.Flags = NResourceFlags::kMetadata;
1818 
1819 
1820 
1821   // ---------- Write Streams Info Tables ----------
1822 
1823   for (i = 0; i < streams.Size(); i++)
1824   {
1825     Byte buf[kStreamInfoSize];
1826     streams[i].WriteTo(buf);
1827     RINOK(WriteStream(outStream, buf, kStreamInfoSize));
1828     curPos += kStreamInfoSize;
1829   }
1830 
1831   AString xml ("<WIM>");
1832   AddTagUInt64_ToString(xml, "TOTALBYTES", curPos);
1833   for (i = 0; i < trees.Size(); i++)
1834   {
1835     CDir &tree = trees[i];
1836 
1837     CXmlItem item;
1838     if (_xmls.Size() == 1)
1839     {
1840       const CWimXml &_oldXml = _xmls[0];
1841       if ((int)i < _oldXml.Images.Size())
1842       {
1843         // int ttt = _oldXml.Images[i].ItemIndexInXml;
1844         item = _oldXml.Xml.Root.SubItems[_oldXml.Images[i].ItemIndexInXml];
1845       }
1846     }
1847     if (i >= isChangedImage.Size() || isChangedImage[i])
1848     {
1849       char temp[16];
1850       if (item.Name.IsEmpty())
1851       {
1852         ConvertUInt32ToString(i + 1, temp);
1853         item.Name = "IMAGE";
1854         item.IsTag = true;
1855         CXmlProp &prop = item.Props.AddNew();
1856         prop.Name = "INDEX";
1857         prop.Value = temp;
1858       }
1859 
1860       AddTag_String_IfEmpty(item, "NAME", temp);
1861       AddTag_UInt64(item, "DIRCOUNT", tree.GetNumDirs() - 1);
1862       AddTag_UInt64(item, "FILECOUNT", tree.GetNumFiles());
1863       AddTag_UInt64(item, "TOTALBYTES", tree.GetTotalSize(db.MetaItems));
1864 
1865       AddTag_Time(item, "CREATIONTIME", ftCur);
1866       AddTag_Time(item, "LASTMODIFICATIONTIME", ftCur);
1867     }
1868 
1869     item.AppendTo(xml);
1870   }
1871   xml += "</WIM>";
1872 
1873   size_t xmlSize;
1874   {
1875     UString utf16;
1876     if (!ConvertUTF8ToUnicode(xml, utf16))
1877       return S_FALSE;
1878     xmlSize = (utf16.Len() + 1) * 2;
1879 
1880     CByteArr xmlBuf(xmlSize);
1881     Set16((Byte *)xmlBuf, 0xFEFF);
1882     for (i = 0; i < (unsigned)utf16.Len(); i++)
1883       Set16((Byte *)xmlBuf + 2 + i * 2, utf16[i]);
1884     RINOK(WriteStream(outStream, (const Byte *)xmlBuf, xmlSize));
1885   }
1886 
1887   header.XmlResource.UnpackSize = header.XmlResource.PackSize = xmlSize;
1888   header.XmlResource.Offset = curPos;
1889   header.XmlResource.Flags = NResourceFlags::kMetadata;
1890 
1891   outStream->Seek(0, STREAM_SEEK_SET, NULL);
1892   header.NumImages = trees.Size();
1893   {
1894     Byte buf[kHeaderSizeMax];
1895     header.WriteTo(buf);
1896     return WriteStream(outStream, buf, kHeaderSizeMax);
1897   }
1898 
1899   COM_TRY_END
1900 }
1901 
1902 }}
1903