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