| // WimHandlerOut.cpp |
| |
| #include "StdAfx.h" |
| |
| #include "../../../Common/ComTry.h" |
| #include "../../../Common/IntToString.h" |
| #include "../../../Common/MyBuffer2.h" |
| #include "../../../Common/StringToInt.h" |
| #include "../../../Common/UTFConvert.h" |
| #include "../../../Common/Wildcard.h" |
| |
| #include "../../../Windows/PropVariant.h" |
| #include "../../../Windows/TimeUtils.h" |
| |
| #include "../../Common/LimitedStreams.h" |
| #include "../../Common/ProgressUtils.h" |
| #include "../../Common/StreamUtils.h" |
| #include "../../Common/UniqBlocks.h" |
| |
| #include "../../Crypto/RandGen.h" |
| #include "../../Crypto/Sha1Cls.h" |
| |
| #include "WimHandler.h" |
| |
| using namespace NWindows; |
| |
| namespace NArchive { |
| namespace NWim { |
| |
| static const unsigned k_NumSubVectors_Bits = 12; // must be <= 16 |
| |
| struct CSortedIndex |
| { |
| CObjectVector<CUIntVector> Vectors; |
| |
| CSortedIndex() |
| { |
| const unsigned k_NumSubVectors = 1 << k_NumSubVectors_Bits; |
| Vectors.ClearAndReserve(k_NumSubVectors); |
| for (unsigned i = 0; i < k_NumSubVectors; i++) |
| Vectors.AddNew(); |
| } |
| }; |
| |
| static int AddUniqHash(const CStreamInfo *streams, CSortedIndex &sorted2, const Byte *h, int streamIndexForInsert) |
| { |
| const unsigned hash = (((unsigned)h[0] << 8) | (unsigned)h[1]) >> (16 - k_NumSubVectors_Bits); |
| CUIntVector &sorted = sorted2.Vectors[hash]; |
| unsigned left = 0, right = sorted.Size(); |
| while (left != right) |
| { |
| const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); |
| const unsigned index = sorted[mid]; |
| const Byte *hash2 = streams[index].Hash; |
| |
| unsigned i; |
| for (i = 0; i < kHashSize; i++) |
| if (h[i] != hash2[i]) |
| break; |
| |
| if (i == kHashSize) |
| return (int)index; |
| |
| if (h[i] < hash2[i]) |
| right = mid; |
| else |
| left = mid + 1; |
| } |
| |
| if (streamIndexForInsert != -1) |
| sorted.Insert(left, (unsigned)streamIndexForInsert); |
| |
| return -1; |
| } |
| |
| |
| struct CAltStream |
| { |
| int UpdateIndex; |
| int HashIndex; |
| UInt64 Size; |
| UString Name; |
| bool Skip; |
| |
| CAltStream(): UpdateIndex(-1), HashIndex(-1), Skip(false) {} |
| }; |
| |
| |
| struct CMetaItem |
| { |
| int UpdateIndex; |
| int HashIndex; |
| |
| UInt64 Size; |
| FILETIME CTime; |
| FILETIME ATime; |
| FILETIME MTime; |
| UInt64 FileID; |
| UInt64 VolID; |
| |
| UString Name; |
| UString ShortName; |
| |
| UInt32 Attrib; |
| int SecurityId; // -1: means no secutity ID |
| bool IsDir; |
| bool Skip; |
| unsigned NumSkipAltStreams; |
| CObjectVector<CAltStream> AltStreams; |
| |
| CByteBuffer Reparse; |
| |
| unsigned GetNumAltStreams() const { return AltStreams.Size() - NumSkipAltStreams; } |
| CMetaItem(): |
| UpdateIndex(-1) |
| , HashIndex(-1) |
| , Size(0) |
| , FileID(0) |
| , VolID(0) |
| , Attrib(0) |
| , SecurityId(-1) |
| , IsDir(false) |
| , Skip(false) |
| , NumSkipAltStreams(0) |
| { |
| FILETIME_Clear(CTime); |
| FILETIME_Clear(ATime); |
| FILETIME_Clear(MTime); |
| } |
| }; |
| |
| |
| static int Compare_HardLink_MetaItems(const CMetaItem &a1, const CMetaItem &a2) |
| { |
| if (a1.VolID < a2.VolID) return -1; |
| if (a1.VolID > a2.VolID) return 1; |
| if (a1.FileID < a2.FileID) return -1; |
| if (a1.FileID > a2.FileID) return 1; |
| if (a1.Size < a2.Size) return -1; |
| if (a1.Size > a2.Size) return 1; |
| return ::CompareFileTime(&a1.MTime, &a2.MTime); |
| } |
| |
| |
| static int AddToHardLinkList(const CObjectVector<CMetaItem> &metaItems, unsigned indexOfItem, CUIntVector &indexes) |
| { |
| const CMetaItem &mi = metaItems[indexOfItem]; |
| unsigned left = 0, right = indexes.Size(); |
| while (left != right) |
| { |
| const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); |
| const unsigned index = indexes[mid]; |
| const int comp = Compare_HardLink_MetaItems(mi, metaItems[index]); |
| if (comp == 0) |
| return (int)index; |
| if (comp < 0) |
| right = mid; |
| else |
| left = mid + 1; |
| } |
| indexes.Insert(left, indexOfItem); |
| return -1; |
| } |
| |
| |
| struct CUpdateItem |
| { |
| unsigned CallbackIndex; // index in callback |
| |
| int MetaIndex; // index in in MetaItems[] |
| |
| int AltStreamIndex; // index in CMetaItem::AltStreams vector |
| // -1: if not alt stream? |
| |
| int InArcIndex; // >= 0, if we use OLD Data |
| // -1, if we use NEW Data |
| |
| CUpdateItem(): MetaIndex(-1), AltStreamIndex(-1), InArcIndex(-1) {} |
| }; |
| |
| |
| struct CDir |
| { |
| int MetaIndex; |
| CObjectVector<CDir> Dirs; |
| CUIntVector Files; // indexes in MetaItems[] |
| |
| CDir(): MetaIndex(-1) {} |
| unsigned GetNumDirs() const; |
| unsigned GetNumFiles() const; |
| UInt64 GetTotalSize(const CObjectVector<CMetaItem> &metaItems) const; |
| bool FindDir(const CObjectVector<CMetaItem> &items, const UString &name, unsigned &index); |
| }; |
| |
| /* imagex counts Junctions as files (not as dirs). |
| We suppose that it's not correct */ |
| |
| unsigned CDir::GetNumDirs() const |
| { |
| unsigned num = Dirs.Size(); |
| FOR_VECTOR (i, Dirs) |
| num += Dirs[i].GetNumDirs(); |
| return num; |
| } |
| |
| unsigned CDir::GetNumFiles() const |
| { |
| unsigned num = Files.Size(); |
| FOR_VECTOR (i, Dirs) |
| num += Dirs[i].GetNumFiles(); |
| return num; |
| } |
| |
| UInt64 CDir::GetTotalSize(const CObjectVector<CMetaItem> &metaItems) const |
| { |
| UInt64 sum = 0; |
| unsigned i; |
| for (i = 0; i < Files.Size(); i++) |
| sum += metaItems[Files[i]].Size; |
| for (i = 0; i < Dirs.Size(); i++) |
| sum += Dirs[i].GetTotalSize(metaItems); |
| return sum; |
| } |
| |
| bool CDir::FindDir(const CObjectVector<CMetaItem> &items, const UString &name, unsigned &index) |
| { |
| unsigned left = 0, right = Dirs.Size(); |
| while (left != right) |
| { |
| const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); |
| const int comp = CompareFileNames(name, items[Dirs[mid].MetaIndex].Name); |
| if (comp == 0) |
| { |
| index = mid; |
| return true; |
| } |
| if (comp < 0) |
| right = mid; |
| else |
| left = mid + 1; |
| } |
| index = left; |
| return false; |
| } |
| |
| |
| Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) |
| { |
| *type = NFileTimeType::kWindows; |
| return S_OK; |
| } |
| |
| |
| HRESULT CHandler::GetOutProperty(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, PROPVARIANT *value) |
| { |
| if (arcIndex != -1) |
| return GetProperty((UInt32)arcIndex, propID, value); |
| return callback->GetProperty(callbackIndex, propID, value); |
| } |
| |
| |
| HRESULT CHandler::GetTime(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, FILETIME &ft) |
| { |
| ft.dwLowDateTime = ft.dwHighDateTime = 0; |
| NCOM::CPropVariant prop; |
| RINOK(GetOutProperty(callback, callbackIndex, arcIndex, propID, &prop)) |
| if (prop.vt == VT_FILETIME) |
| ft = prop.filetime; |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| return S_OK; |
| } |
| |
| |
| static HRESULT GetRootTime( |
| IArchiveGetRootProps *callback, |
| IArchiveGetRootProps *arcRoot, |
| PROPID propID, FILETIME &ft) |
| { |
| NCOM::CPropVariant prop; |
| if (callback) |
| { |
| RINOK(callback->GetRootProp(propID, &prop)) |
| if (prop.vt == VT_FILETIME) |
| { |
| ft = prop.filetime; |
| return S_OK; |
| } |
| if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| if (arcRoot) |
| { |
| RINOK(arcRoot->GetRootProp(propID, &prop)) |
| if (prop.vt == VT_FILETIME) |
| { |
| ft = prop.filetime; |
| return S_OK; |
| } |
| if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| return S_OK; |
| } |
| |
| #define Set16(p, d) SetUi16(p, d) |
| #define Set32(p, d) SetUi32(p, d) |
| #define Set64(p, d) SetUi64(p, d) |
| |
| void CResource::WriteTo(Byte *p) const |
| { |
| Set64(p, PackSize) |
| p[7] = Flags; |
| Set64(p + 8, Offset) |
| Set64(p + 16, UnpackSize) |
| } |
| |
| |
| void CHeader::WriteTo(Byte *p) const |
| { |
| memcpy(p, kSignature, kSignatureSize); |
| Set32(p + 8, kHeaderSizeMax) |
| Set32(p + 0xC, Version) |
| Set32(p + 0x10, Flags) |
| Set32(p + 0x14, ChunkSize) |
| memcpy(p + 0x18, Guid, 16); |
| Set16(p + 0x28, PartNumber) |
| Set16(p + 0x2A, NumParts) |
| Set32(p + 0x2C, NumImages) |
| OffsetResource.WriteTo(p + 0x30); |
| XmlResource.WriteTo(p + 0x48); |
| MetadataResource.WriteTo(p + 0x60); |
| IntegrityResource.WriteTo(p + 0x7C); |
| Set32(p + 0x78, BootIndex) |
| memset(p + 0x94, 0, 60); |
| } |
| |
| |
| void CStreamInfo::WriteTo(Byte *p) const |
| { |
| Resource.WriteTo(p); |
| Set16(p + 0x18, PartNumber) |
| Set32(p + 0x1A, RefCount) |
| memcpy(p + 0x1E, Hash, kHashSize); |
| } |
| |
| |
| Z7_CLASS_IMP_NOQIB_1( |
| CInStreamWithSha1 |
| , ISequentialInStream |
| ) |
| CMyComPtr<ISequentialInStream> _stream; |
| UInt64 _size; |
| // NCrypto::NSha1::CContext _sha; |
| CAlignedBuffer1 _sha; |
| CSha1 *Sha() { return (CSha1 *)(void *)(Byte *)_sha; } |
| public: |
| CInStreamWithSha1(): _sha(sizeof(CSha1)) {} |
| void SetStream(ISequentialInStream *stream) { _stream = stream; } |
| void Init() |
| { |
| _size = 0; |
| Sha1_Init(Sha()); |
| } |
| void ReleaseStream() { _stream.Release(); } |
| UInt64 GetSize() const { return _size; } |
| void Final(Byte *digest) { Sha1_Final(Sha(), digest); } |
| }; |
| |
| Z7_COM7F_IMF(CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize)) |
| { |
| UInt32 realProcessedSize; |
| const HRESULT result = _stream->Read(data, size, &realProcessedSize); |
| _size += realProcessedSize; |
| Sha1_Update(Sha(), (const Byte *)data, realProcessedSize); |
| if (processedSize) |
| *processedSize = realProcessedSize; |
| return result; |
| } |
| |
| |
| static void SetFileTimeToMem(Byte *p, const FILETIME &ft) |
| { |
| Set32(p, ft.dwLowDateTime) |
| Set32(p + 4, ft.dwHighDateTime) |
| } |
| |
| static size_t WriteItem_Dummy(const CMetaItem &item) |
| { |
| if (item.Skip) |
| return 0; |
| unsigned fileNameLen = item.Name.Len() * 2; |
| // we write fileNameLen + 2 + 2 to be same as original WIM. |
| unsigned fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2); |
| |
| const unsigned shortNameLen = item.ShortName.Len() * 2; |
| const unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4); |
| |
| size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~(unsigned)7); |
| if (item.GetNumAltStreams() != 0) |
| { |
| if (!item.IsDir) |
| { |
| const UInt32 curLen = (((0x26 + 0) + 6) & ~(unsigned)7); |
| totalLen += curLen; |
| } |
| FOR_VECTOR (i, item.AltStreams) |
| { |
| const CAltStream &ss = item.AltStreams[i]; |
| if (ss.Skip) |
| continue; |
| fileNameLen = ss.Name.Len() * 2; |
| fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2); |
| const UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~(unsigned)7); |
| totalLen += curLen; |
| } |
| } |
| return totalLen; |
| } |
| |
| |
| static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte *p) |
| { |
| if (item.Skip) |
| return 0; |
| unsigned fileNameLen = item.Name.Len() * 2; |
| unsigned fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2); |
| unsigned shortNameLen = item.ShortName.Len() * 2; |
| unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4); |
| |
| size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~(unsigned)7); |
| |
| memset(p, 0, totalLen); |
| Set64(p, totalLen) |
| Set64(p + 8, item.Attrib) |
| Set32(p + 0xC, (UInt32)(Int32)item.SecurityId) |
| SetFileTimeToMem(p + 0x28, item.CTime); |
| SetFileTimeToMem(p + 0x30, item.ATime); |
| SetFileTimeToMem(p + 0x38, item.MTime); |
| |
| /* WIM format probably doesn't support hard links to symbolic links. |
| In these cases it just stores symbolic links (REPARSE TAGS). |
| Check it in new versions of WIM software form MS !!! |
| We also follow that scheme */ |
| |
| if (item.Reparse.Size() != 0) |
| { |
| UInt32 tag = GetUi32(item.Reparse); |
| Set32(p + 0x58, tag) |
| // Set32(p + 0x5C, 0); // probably it's always ZERO |
| } |
| else if (item.FileID != 0) |
| { |
| Set64(p + 0x58, item.FileID) |
| } |
| |
| Set16(p + 0x62, (UInt16)shortNameLen) |
| Set16(p + 0x64, (UInt16)fileNameLen) |
| unsigned i; |
| for (i = 0; i * 2 < fileNameLen; i++) |
| Set16(p + kDirRecordSize + i * 2, (UInt16)item.Name[i]) |
| for (i = 0; i * 2 < shortNameLen; i++) |
| Set16(p + kDirRecordSize + fileNameLen2 + i * 2, (UInt16)item.ShortName[i]) |
| |
| if (item.GetNumAltStreams() == 0) |
| { |
| if (item.HashIndex >= 0) |
| memcpy(p + 0x40, streams[item.HashIndex].Hash, kHashSize); |
| } |
| else |
| { |
| Set16(p + 0x60, (UInt16)(item.GetNumAltStreams() + (item.IsDir ? 0 : 1))) |
| p += totalLen; |
| |
| if (!item.IsDir) |
| { |
| const UInt32 curLen = (((0x26 + 0) + 6) & ~(unsigned)7); |
| memset(p, 0, curLen); |
| Set64(p, curLen) |
| if (item.HashIndex >= 0) |
| memcpy(p + 0x10, streams[item.HashIndex].Hash, kHashSize); |
| totalLen += curLen; |
| p += curLen; |
| } |
| |
| FOR_VECTOR (si, item.AltStreams) |
| { |
| const CAltStream &ss = item.AltStreams[si]; |
| if (ss.Skip) |
| continue; |
| |
| fileNameLen = ss.Name.Len() * 2; |
| fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2); |
| UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~(unsigned)7); |
| memset(p, 0, curLen); |
| |
| Set64(p, curLen) |
| if (ss.HashIndex >= 0) |
| memcpy(p + 0x10, streams[ss.HashIndex].Hash, kHashSize); |
| Set16(p + 0x24, (UInt16)fileNameLen) |
| for (i = 0; i * 2 < fileNameLen; i++) |
| Set16(p + 0x26 + i * 2, (UInt16)ss.Name[i]) |
| totalLen += curLen; |
| p += curLen; |
| } |
| } |
| |
| return totalLen; |
| } |
| |
| |
| struct CDb |
| { |
| CMetaItem DefaultDirItem; |
| const CStreamInfo *Hashes; |
| CObjectVector<CMetaItem> MetaItems; |
| CRecordVector<CUpdateItem> UpdateItems; |
| CUIntVector UpdateIndexes; /* indexes in UpdateItems in order of writing data streams |
| to disk (the order of tree items). */ |
| |
| size_t WriteTree_Dummy(const CDir &tree) const; |
| void WriteTree(const CDir &tree, Byte *dest, size_t &pos) const; |
| void WriteOrderList(const CDir &tree); |
| }; |
| |
| |
| size_t CDb::WriteTree_Dummy(const CDir &tree) const |
| { |
| unsigned i; |
| size_t pos = 0; |
| for (i = 0; i < tree.Files.Size(); i++) |
| pos += WriteItem_Dummy(MetaItems[tree.Files[i]]); |
| for (i = 0; i < tree.Dirs.Size(); i++) |
| { |
| const CDir &subDir = tree.Dirs[i]; |
| pos += WriteItem_Dummy(MetaItems[subDir.MetaIndex]); |
| pos += WriteTree_Dummy(subDir); |
| } |
| return pos + 8; |
| } |
| |
| |
| void CDb::WriteTree(const CDir &tree, Byte *dest, size_t &pos) const |
| { |
| unsigned i; |
| for (i = 0; i < tree.Files.Size(); i++) |
| pos += WriteItem(Hashes, MetaItems[tree.Files[i]], dest + pos); |
| |
| size_t posStart = pos; |
| for (i = 0; i < tree.Dirs.Size(); i++) |
| pos += WriteItem_Dummy(MetaItems[tree.Dirs[i].MetaIndex]); |
| |
| Set64(dest + pos, 0) |
| |
| pos += 8; |
| |
| for (i = 0; i < tree.Dirs.Size(); i++) |
| { |
| const CDir &subDir = tree.Dirs[i]; |
| const CMetaItem &metaItem = MetaItems[subDir.MetaIndex]; |
| bool needCreateTree = (metaItem.Reparse.Size() == 0) |
| || !subDir.Files.IsEmpty() |
| || !subDir.Dirs.IsEmpty(); |
| size_t len = WriteItem(Hashes, metaItem, dest + posStart); |
| posStart += len; |
| if (needCreateTree) |
| { |
| Set64(dest + posStart - len + 0x10, pos) // subdirOffset |
| WriteTree(subDir, dest, pos); |
| } |
| } |
| } |
| |
| |
| void CDb::WriteOrderList(const CDir &tree) |
| { |
| if (tree.MetaIndex >= 0) |
| { |
| const CMetaItem &mi = MetaItems[tree.MetaIndex]; |
| if (mi.UpdateIndex >= 0) |
| UpdateIndexes.Add((unsigned)mi.UpdateIndex); |
| FOR_VECTOR (si, mi.AltStreams) |
| UpdateIndexes.Add((unsigned)mi.AltStreams[si].UpdateIndex); |
| } |
| |
| unsigned i; |
| for (i = 0; i < tree.Files.Size(); i++) |
| { |
| const CMetaItem &mi = MetaItems[tree.Files[i]]; |
| UpdateIndexes.Add((unsigned)mi.UpdateIndex); |
| FOR_VECTOR (si, mi.AltStreams) |
| UpdateIndexes.Add((unsigned)mi.AltStreams[si].UpdateIndex); |
| } |
| |
| for (i = 0; i < tree.Dirs.Size(); i++) |
| WriteOrderList(tree.Dirs[i]); |
| } |
| |
| |
| static void AddTag_ToString(AString &s, const char *name, const char *value) |
| { |
| s += '<'; |
| s += name; |
| s += '>'; |
| s += value; |
| s += '<'; |
| s += '/'; |
| s += name; |
| s += '>'; |
| } |
| |
| |
| static void AddTagUInt64_ToString(AString &s, const char *name, UInt64 value) |
| { |
| char temp[32]; |
| ConvertUInt64ToString(value, temp); |
| AddTag_ToString(s, name, temp); |
| } |
| |
| |
| static CXmlItem &AddUniqueTag(CXmlItem &parentItem, const char *name) |
| { |
| int index = parentItem.FindSubTag(name); |
| if (index < 0) |
| { |
| CXmlItem &subItem = parentItem.SubItems.AddNew(); |
| subItem.IsTag = true; |
| subItem.Name = name; |
| return subItem; |
| } |
| CXmlItem &subItem = parentItem.SubItems[index]; |
| subItem.SubItems.Clear(); |
| return subItem; |
| } |
| |
| |
| static void AddTag_UInt64_2(CXmlItem &item, UInt64 value) |
| { |
| CXmlItem &subItem = item.SubItems.AddNew(); |
| subItem.IsTag = false; |
| char temp[32]; |
| ConvertUInt64ToString(value, temp); |
| subItem.Name = temp; |
| } |
| |
| |
| static void AddTag_UInt64(CXmlItem &parentItem, const char *name, UInt64 value) |
| { |
| AddTag_UInt64_2(AddUniqueTag(parentItem, name), value); |
| } |
| |
| |
| static void AddTag_Hex(CXmlItem &item, const char *name, UInt32 value) |
| { |
| item.IsTag = true; |
| item.Name = name; |
| char temp[16]; |
| temp[0] = '0'; |
| temp[1] = 'x'; |
| ConvertUInt32ToHex8Digits(value, temp + 2); |
| CXmlItem &subItem = item.SubItems.AddNew(); |
| subItem.IsTag = false; |
| subItem.Name = temp; |
| } |
| |
| |
| static void AddTag_Time_2(CXmlItem &item, const FILETIME &ft) |
| { |
| AddTag_Hex(item.SubItems.AddNew(), "HIGHPART", ft.dwHighDateTime); |
| AddTag_Hex(item.SubItems.AddNew(), "LOWPART", ft.dwLowDateTime); |
| } |
| |
| |
| static void AddTag_Time(CXmlItem &parentItem, const char *name, const FILETIME &ft) |
| { |
| AddTag_Time_2(AddUniqueTag(parentItem, name), ft); |
| } |
| |
| |
| static void AddTag_String_IfEmpty(CXmlItem &parentItem, const char *name, const char *value) |
| { |
| int index = parentItem.FindSubTag(name); |
| if (index >= 0) |
| return; |
| CXmlItem &tag = parentItem.SubItems.AddNew(); |
| tag.IsTag = true; |
| tag.Name = name; |
| CXmlItem &subItem = tag.SubItems.AddNew(); |
| subItem.IsTag = false; |
| subItem.Name = value; |
| } |
| |
| |
| void CHeader::SetDefaultFields(bool useLZX) |
| { |
| Version = k_Version_NonSolid; |
| Flags = NHeaderFlags::kReparsePointFixup; |
| ChunkSize = 0; |
| if (useLZX) |
| { |
| Flags |= NHeaderFlags::kCompression | NHeaderFlags::kLZX; |
| ChunkSize = kChunkSize; |
| ChunkSizeBits = kChunkSizeBits; |
| } |
| MY_RAND_GEN(Guid, 16); |
| PartNumber = 1; |
| NumParts = 1; |
| NumImages = 1; |
| BootIndex = 0; |
| OffsetResource.Clear(); |
| XmlResource.Clear(); |
| MetadataResource.Clear(); |
| IntegrityResource.Clear(); |
| } |
| |
| |
| static void AddTrees(CObjectVector<CDir> &trees, CObjectVector<CMetaItem> &metaItems, const CMetaItem &ri, int curTreeIndex) |
| { |
| while (curTreeIndex >= (int)trees.Size()) |
| trees.AddNew().Dirs.AddNew().MetaIndex = (int)metaItems.Add(ri); |
| } |
| |
| |
| #define IS_LETTER_CHAR(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) |
| |
| |
| |
| Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 numItems, IArchiveUpdateCallback *callback)) |
| { |
| COM_TRY_BEGIN |
| |
| if (!IsUpdateSupported()) |
| return E_NOTIMPL; |
| |
| bool isUpdate = (_volumes.Size() != 0); |
| int defaultImageIndex = _defaultImageNumber - 1; |
| bool showImageNumber; |
| |
| if (isUpdate) |
| { |
| showImageNumber = _showImageNumber; |
| if (!showImageNumber) |
| defaultImageIndex = _db.IndexOfUserImage; |
| } |
| else |
| { |
| showImageNumber = (_set_use_ShowImageNumber && _set_showImageNumber); |
| if (!showImageNumber) |
| defaultImageIndex = 0; |
| } |
| |
| if (defaultImageIndex >= kNumImagesMaxUpdate) |
| return E_NOTIMPL; |
| |
| CMyComPtr<IOutStream> outStream; |
| RINOK(outSeqStream->QueryInterface(IID_IOutStream, (void **)&outStream)) |
| if (!outStream) |
| return E_NOTIMPL; |
| if (!callback) |
| return E_FAIL; |
| |
| CDb db; |
| CObjectVector<CDir> trees; |
| |
| CMetaItem ri; // default DIR item |
| FILETIME ftCur; |
| NTime::GetCurUtcFileTime(ftCur); |
| // ri.MTime = ri.ATime = ri.CTime = ftCur; |
| ri.Attrib = FILE_ATTRIBUTE_DIRECTORY; |
| ri.IsDir = true; |
| |
| |
| // ---------- Detect changed images ---------- |
| |
| unsigned i; |
| CBoolVector isChangedImage; |
| { |
| CUIntVector numUnchangedItemsInImage; |
| for (i = 0; i < _db.Images.Size(); i++) |
| { |
| numUnchangedItemsInImage.Add(0); |
| isChangedImage.Add(false); |
| } |
| |
| for (i = 0; i < numItems; i++) |
| { |
| UInt32 indexInArchive; |
| Int32 newData, newProps; |
| RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)) |
| if (newProps == 0) |
| { |
| if (indexInArchive >= _db.SortedItems.Size()) |
| continue; |
| const CItem &item = _db.Items[_db.SortedItems[indexInArchive]]; |
| if (newData == 0) |
| { |
| if (item.ImageIndex >= 0) |
| numUnchangedItemsInImage[item.ImageIndex]++; |
| } |
| else |
| { |
| // oldProps & newData. Current version of 7-Zip doesn't use it |
| if (item.ImageIndex >= 0) |
| isChangedImage[item.ImageIndex] = true; |
| } |
| } |
| else if (!showImageNumber) |
| { |
| if (defaultImageIndex >= 0 && defaultImageIndex < (int)isChangedImage.Size()) |
| isChangedImage[defaultImageIndex] = true; |
| } |
| else |
| { |
| NCOM::CPropVariant prop; |
| RINOK(callback->GetProperty(i, kpidPath, &prop)) |
| |
| if (prop.vt != VT_BSTR) |
| return E_INVALIDARG; |
| const wchar_t *path = prop.bstrVal; |
| if (!path) |
| return E_INVALIDARG; |
| |
| const wchar_t *end; |
| UInt64 val = ConvertStringToUInt64(path, &end); |
| if (end == path) |
| return E_INVALIDARG; |
| if (val == 0 || val > kNumImagesMaxUpdate) |
| return E_INVALIDARG; |
| wchar_t c = *end; |
| if (c != 0 && c != ':' && c != L'/' && c != WCHAR_PATH_SEPARATOR) |
| return E_INVALIDARG; |
| unsigned imageIndex = (unsigned)val - 1; |
| if (imageIndex < _db.Images.Size()) |
| isChangedImage[imageIndex] = true; |
| if (_defaultImageNumber > 0 && val != (unsigned)_defaultImageNumber) |
| return E_INVALIDARG; |
| } |
| } |
| |
| for (i = 0; i < _db.Images.Size(); i++) |
| if (!isChangedImage[i]) |
| isChangedImage[i] = _db.GetNumUserItemsInImage(i) != numUnchangedItemsInImage[i]; |
| } |
| |
| if (defaultImageIndex >= 0) |
| { |
| for (i = 0; i < _db.Images.Size(); i++) |
| if ((int)i != defaultImageIndex) |
| isChangedImage[i] = false; |
| } |
| |
| CMyComPtr<IArchiveGetRawProps> getRawProps; |
| callback->QueryInterface(IID_IArchiveGetRawProps, (void **)&getRawProps); |
| |
| CMyComPtr<IArchiveGetRootProps> getRootProps; |
| callback->QueryInterface(IID_IArchiveGetRootProps, (void **)&getRootProps); |
| |
| CObjectVector<CUniqBlocks> secureBlocks; |
| |
| if (!showImageNumber && (getRootProps || isUpdate) && |
| ( |
| defaultImageIndex >= (int)isChangedImage.Size() |
| || defaultImageIndex < 0 // test it |
| || isChangedImage[defaultImageIndex] |
| )) |
| { |
| // Fill Root Item: Metadata and security |
| CMetaItem rootItem = ri; |
| { |
| const void *data = NULL; |
| UInt32 dataSize = 0; |
| UInt32 propType = 0; |
| if (getRootProps) |
| { |
| RINOK(getRootProps->GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)) |
| } |
| if (dataSize == 0 && isUpdate) |
| { |
| RINOK(GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)) |
| } |
| if (dataSize != 0) |
| { |
| if (propType != NPropDataType::kRaw) |
| return E_FAIL; |
| while (defaultImageIndex >= (int)secureBlocks.Size()) |
| secureBlocks.AddNew(); |
| CUniqBlocks &secUniqBlocks = secureBlocks[defaultImageIndex]; |
| rootItem.SecurityId = (int)secUniqBlocks.AddUniq((const Byte *)data, dataSize); |
| } |
| } |
| |
| IArchiveGetRootProps *thisGetRoot = isUpdate ? this : NULL; |
| |
| if (_timeOptions.Write_CTime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidCTime, rootItem.CTime)) |
| if (_timeOptions.Write_ATime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidATime, rootItem.ATime)) |
| if (_timeOptions.Write_MTime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidMTime, rootItem.MTime)) |
| |
| { |
| NCOM::CPropVariant prop; |
| if (getRootProps) |
| { |
| RINOK(getRootProps->GetRootProp(kpidAttrib, &prop)) |
| if (prop.vt == VT_UI4) |
| rootItem.Attrib = prop.ulVal; |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| if (prop.vt == VT_EMPTY && thisGetRoot) |
| { |
| RINOK(GetRootProp(kpidAttrib, &prop)) |
| if (prop.vt == VT_UI4) |
| rootItem.Attrib = prop.ulVal; |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| rootItem.Attrib |= FILE_ATTRIBUTE_DIRECTORY; |
| } |
| |
| AddTrees(trees, db.MetaItems, ri, defaultImageIndex); |
| db.MetaItems[trees[defaultImageIndex].Dirs[0].MetaIndex] = rootItem; |
| } |
| |
| // ---------- Request Metadata for changed items ---------- |
| |
| UString fileName; |
| |
| for (i = 0; i < numItems; i++) |
| { |
| CUpdateItem ui; |
| UInt32 indexInArchive; |
| Int32 newData, newProps; |
| RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)) |
| |
| if (newData == 0 || newProps == 0) |
| { |
| if (indexInArchive >= _db.SortedItems.Size()) |
| continue; |
| |
| const CItem &item = _db.Items[_db.SortedItems[indexInArchive]]; |
| |
| if (item.ImageIndex >= 0) |
| { |
| if (!isChangedImage[item.ImageIndex]) |
| { |
| if (newData == 0 && newProps == 0) |
| continue; |
| return E_FAIL; |
| } |
| } |
| else |
| { |
| // if deleted item was not renamed, we just skip it |
| if (newProps == 0) |
| continue; |
| if (item.StreamIndex >= 0) |
| { |
| // we don't support property change for SolidBig streams |
| if (_db.DataStreams[item.StreamIndex].Resource.IsSolidBig()) |
| return E_NOTIMPL; |
| } |
| } |
| |
| if (newData == 0) |
| ui.InArcIndex = (Int32)indexInArchive; |
| } |
| |
| // we set arcIndex only if we must use old props |
| const Int32 arcIndex = (newProps ? -1 : (Int32)indexInArchive); |
| |
| bool isDir = false; |
| { |
| NCOM::CPropVariant prop; |
| RINOK(GetOutProperty(callback, i, arcIndex, kpidIsDir, &prop)) |
| if (prop.vt == VT_BOOL) |
| isDir = (prop.boolVal != VARIANT_FALSE); |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| |
| bool isAltStream = false; |
| { |
| NCOM::CPropVariant prop; |
| RINOK(GetOutProperty(callback, i, arcIndex, kpidIsAltStream, &prop)) |
| if (prop.vt == VT_BOOL) |
| isAltStream = (prop.boolVal != VARIANT_FALSE); |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| |
| if (isDir && isAltStream) |
| return E_INVALIDARG; |
| |
| UInt64 size = 0; |
| UInt64 iNode = 0; |
| |
| if (!isDir) |
| { |
| if (!newData) |
| { |
| NCOM::CPropVariant prop; |
| GetProperty(indexInArchive, kpidINode, &prop); |
| if (prop.vt == VT_UI8) |
| iNode = prop.uhVal.QuadPart; |
| } |
| |
| NCOM::CPropVariant prop; |
| |
| if (newData) |
| { |
| RINOK(callback->GetProperty(i, kpidSize, &prop)) |
| } |
| else |
| { |
| RINOK(GetProperty(indexInArchive, kpidSize, &prop)) |
| } |
| |
| if (prop.vt == VT_UI8) |
| size = prop.uhVal.QuadPart; |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| |
| { |
| NCOM::CPropVariant propPath; |
| const wchar_t *path = NULL; |
| RINOK(GetOutProperty(callback, i, arcIndex, kpidPath, &propPath)) |
| if (propPath.vt == VT_BSTR) |
| path = propPath.bstrVal; |
| else if (propPath.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| |
| if (!path) |
| return E_INVALIDARG; |
| |
| CDir *curItem = NULL; |
| bool isRootImageDir = false; |
| fileName.Empty(); |
| |
| int imageIndex; |
| |
| if (!showImageNumber) |
| { |
| imageIndex = defaultImageIndex; |
| AddTrees(trees, db.MetaItems, ri, imageIndex); |
| curItem = &trees[imageIndex].Dirs[0]; |
| } |
| else |
| { |
| const wchar_t *end; |
| UInt64 val = ConvertStringToUInt64(path, &end); |
| if (end == path) |
| return E_INVALIDARG; |
| if (val == 0 || val > kNumImagesMaxUpdate) |
| return E_INVALIDARG; |
| |
| imageIndex = (int)val - 1; |
| if (imageIndex < (int)isChangedImage.Size()) |
| if (!isChangedImage[imageIndex]) |
| return E_FAIL; |
| |
| AddTrees(trees, db.MetaItems, ri, imageIndex); |
| curItem = &trees[imageIndex].Dirs[0]; |
| wchar_t c = *end; |
| |
| if (c == 0) |
| { |
| if (!isDir || isAltStream) |
| return E_INVALIDARG; |
| ui.MetaIndex = curItem->MetaIndex; |
| isRootImageDir = true; |
| } |
| else if (c == ':') |
| { |
| if (isDir || !isAltStream) |
| return E_INVALIDARG; |
| ui.MetaIndex = curItem->MetaIndex; |
| CAltStream ss; |
| ss.Size = size; |
| ss.Name = end + 1; |
| ss.UpdateIndex = (int)db.UpdateItems.Size(); |
| ui.AltStreamIndex = (int)db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); |
| } |
| else if (c == WCHAR_PATH_SEPARATOR || c == L'/') |
| { |
| path = end + 1; |
| if (*path == 0) |
| return E_INVALIDARG; |
| } |
| else |
| return E_INVALIDARG; |
| } |
| |
| if (ui.MetaIndex < 0) |
| { |
| for (;;) |
| { |
| wchar_t c = *path++; |
| if (c == 0) |
| break; |
| if (c == WCHAR_PATH_SEPARATOR || c == L'/') |
| { |
| unsigned indexOfDir; |
| if (!curItem->FindDir(db.MetaItems, fileName, indexOfDir)) |
| { |
| CDir &dir = curItem->Dirs.InsertNew(indexOfDir); |
| dir.MetaIndex = (int)db.MetaItems.Add(ri); |
| db.MetaItems.Back().Name = fileName; |
| } |
| curItem = &curItem->Dirs[indexOfDir]; |
| fileName.Empty(); |
| } |
| else |
| { |
| /* |
| #if WCHAR_MAX > 0xffff |
| if (c >= 0x10000) |
| { |
| c -= 0x10000; |
| |
| if (c < (1 << 20)) |
| { |
| wchar_t c0 = 0xd800 + ((c >> 10) & 0x3FF); |
| fileName += c0; |
| c = 0xdc00 + (c & 0x3FF); |
| } |
| else |
| c = '_'; // we change character unsupported by UTF16 |
| } |
| #endif |
| */ |
| |
| fileName += c; |
| } |
| } |
| |
| if (isAltStream) |
| { |
| int colonPos = fileName.Find(L':'); |
| if (colonPos < 0) |
| return E_INVALIDARG; |
| |
| // we want to support cases of c::substream, where c: is drive name |
| if (colonPos == 1 && fileName[2] == L':' && IS_LETTER_CHAR(fileName[0])) |
| colonPos = 2; |
| const UString mainName = fileName.Left((unsigned)colonPos); |
| unsigned indexOfDir; |
| |
| if (mainName.IsEmpty()) |
| ui.MetaIndex = curItem->MetaIndex; |
| else if (curItem->FindDir(db.MetaItems, mainName, indexOfDir)) |
| ui.MetaIndex = curItem->Dirs[indexOfDir].MetaIndex; |
| else |
| { |
| for (int j = (int)curItem->Files.Size() - 1; j >= 0; j--) |
| { |
| const unsigned metaIndex = curItem->Files[j]; |
| const CMetaItem &mi = db.MetaItems[metaIndex]; |
| if (CompareFileNames(mainName, mi.Name) == 0) |
| { |
| ui.MetaIndex = (int)metaIndex; |
| break; |
| } |
| } |
| } |
| |
| if (ui.MetaIndex >= 0) |
| { |
| CAltStream ss; |
| ss.Size = size; |
| ss.Name = fileName.Ptr(colonPos + 1); |
| ss.UpdateIndex = (int)db.UpdateItems.Size(); |
| ui.AltStreamIndex = (int)db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); |
| } |
| } |
| } |
| |
| |
| if (ui.MetaIndex < 0 || isRootImageDir) |
| { |
| if (!isRootImageDir) |
| { |
| ui.MetaIndex = (int)db.MetaItems.Size(); |
| db.MetaItems.AddNew(); |
| } |
| |
| CMetaItem &mi = db.MetaItems[ui.MetaIndex]; |
| mi.Size = size; |
| mi.IsDir = isDir; |
| mi.Name = fileName; |
| mi.UpdateIndex = (int)db.UpdateItems.Size(); |
| { |
| NCOM::CPropVariant prop; |
| RINOK(GetOutProperty(callback, i, arcIndex, kpidAttrib, &prop)) |
| if (prop.vt == VT_EMPTY) |
| mi.Attrib = 0; |
| else if (prop.vt == VT_UI4) |
| mi.Attrib = prop.ulVal; |
| else |
| return E_INVALIDARG; |
| if (isDir) |
| mi.Attrib |= FILE_ATTRIBUTE_DIRECTORY; |
| } |
| |
| if (arcIndex != -1 || _timeOptions.Write_CTime.Val) |
| RINOK(GetTime(callback, i, arcIndex, kpidCTime, mi.CTime)) |
| if (arcIndex != -1 || _timeOptions.Write_ATime.Val) |
| RINOK(GetTime(callback, i, arcIndex, kpidATime, mi.ATime)) |
| if (arcIndex != -1 || _timeOptions.Write_MTime.Val) |
| RINOK(GetTime(callback, i, arcIndex, kpidMTime, mi.MTime)) |
| |
| { |
| NCOM::CPropVariant prop; |
| RINOK(GetOutProperty(callback, i, arcIndex, kpidShortName, &prop)) |
| if (prop.vt == VT_BSTR) |
| mi.ShortName.SetFromBstr(prop.bstrVal); |
| else if (prop.vt != VT_EMPTY) |
| return E_INVALIDARG; |
| } |
| |
| while (imageIndex >= (int)secureBlocks.Size()) |
| secureBlocks.AddNew(); |
| |
| if (!isAltStream && (getRawProps || arcIndex >= 0)) |
| { |
| CUniqBlocks &secUniqBlocks = secureBlocks[imageIndex]; |
| const void *data; |
| UInt32 dataSize; |
| UInt32 propType; |
| |
| data = NULL; |
| dataSize = 0; |
| propType = 0; |
| |
| if (arcIndex >= 0) |
| { |
| GetRawProp((UInt32)arcIndex, kpidNtSecure, &data, &dataSize, &propType); |
| } |
| else |
| { |
| getRawProps->GetRawProp(i, kpidNtSecure, &data, &dataSize, &propType); |
| } |
| |
| if (dataSize != 0) |
| { |
| if (propType != NPropDataType::kRaw) |
| return E_FAIL; |
| mi.SecurityId = (int)secUniqBlocks.AddUniq((const Byte *)data, dataSize); |
| } |
| |
| data = NULL; |
| dataSize = 0; |
| propType = 0; |
| |
| if (arcIndex >= 0) |
| { |
| GetRawProp((UInt32)arcIndex, kpidNtReparse, &data, &dataSize, &propType); |
| } |
| else |
| { |
| getRawProps->GetRawProp(i, kpidNtReparse, &data, &dataSize, &propType); |
| } |
| |
| if (dataSize != 0) |
| { |
| if (propType != NPropDataType::kRaw) |
| return E_FAIL; |
| mi.Reparse.CopyFrom((const Byte *)data, dataSize); |
| } |
| } |
| |
| if (!isRootImageDir) |
| { |
| if (isDir) |
| { |
| unsigned indexOfDir; |
| if (curItem->FindDir(db.MetaItems, fileName, indexOfDir)) |
| curItem->Dirs[indexOfDir].MetaIndex = ui.MetaIndex; |
| else |
| curItem->Dirs.InsertNew(indexOfDir).MetaIndex = ui.MetaIndex; |
| } |
| else |
| curItem->Files.Add((unsigned)ui.MetaIndex); |
| } |
| } |
| |
| } |
| |
| if (iNode != 0 && ui.MetaIndex >= 0 && ui.AltStreamIndex < 0) |
| db.MetaItems[ui.MetaIndex].FileID = iNode; |
| |
| ui.CallbackIndex = i; |
| db.UpdateItems.Add(ui); |
| } |
| |
| unsigned numNewImages = trees.Size(); |
| for (i = numNewImages; i < isChangedImage.Size(); i++) |
| if (!isChangedImage[i]) |
| numNewImages = i + 1; |
| |
| AddTrees(trees, db.MetaItems, ri, (int)numNewImages - 1); |
| |
| for (i = 0; i < trees.Size(); i++) |
| if (i >= isChangedImage.Size() || isChangedImage[i]) |
| db.WriteOrderList(trees[i]); |
| |
| |
| UInt64 complexity = 0; |
| |
| unsigned numDataStreams = _db.DataStreams.Size(); |
| CUIntArr streamsRefs(numDataStreams); |
| for (i = 0; i < numDataStreams; i++) |
| streamsRefs[i] = 0; |
| |
| // ---------- Calculate Streams Refs Counts in unchanged images |
| |
| for (i = 0; i < _db.Images.Size(); i++) |
| { |
| if (isChangedImage[i]) |
| continue; |
| complexity += _db.MetaStreams[i].Resource.PackSize; |
| const CImage &image = _db.Images[i]; |
| unsigned endItem = image.StartItem + image.NumItems; |
| for (unsigned k = image.StartItem; k < endItem; k++) |
| { |
| const CItem &item = _db.Items[k]; |
| if (item.StreamIndex >= 0) |
| streamsRefs[(unsigned)item.StreamIndex]++; |
| } |
| } |
| |
| |
| // ---------- Update Streams Refs Counts in changed images |
| |
| for (i = 0; i < db.UpdateIndexes.Size(); i++) |
| { |
| const CUpdateItem &ui = db.UpdateItems[db.UpdateIndexes[i]]; |
| |
| if (ui.InArcIndex >= 0) |
| { |
| if ((unsigned)ui.InArcIndex >= _db.SortedItems.Size()) |
| continue; |
| const CItem &item = _db.Items[_db.SortedItems[ui.InArcIndex]]; |
| if (item.StreamIndex >= 0) |
| streamsRefs[(unsigned)item.StreamIndex]++; |
| } |
| else |
| { |
| const CMetaItem &mi = db.MetaItems[ui.MetaIndex]; |
| UInt64 size; |
| if (ui.AltStreamIndex < 0) |
| size = mi.Size; |
| else |
| size = mi.AltStreams[ui.AltStreamIndex].Size; |
| complexity += size; |
| } |
| } |
| |
| // Clear ref counts for SolidBig streams |
| |
| for (i = 0; i < _db.DataStreams.Size(); i++) |
| if (_db.DataStreams[i].Resource.IsSolidBig()) |
| streamsRefs[i] = 0; |
| |
| // Set ref counts for SolidBig streams |
| |
| for (i = 0; i < _db.DataStreams.Size(); i++) |
| if (streamsRefs[i] != 0) |
| { |
| const CResource &rs = _db.DataStreams[i].Resource; |
| if (rs.IsSolidSmall()) |
| streamsRefs[_db.Solids[rs.SolidIndex].StreamIndex] = 1; |
| } |
| |
| for (i = 0; i < _db.DataStreams.Size(); i++) |
| if (streamsRefs[i] != 0) |
| { |
| const CResource &rs = _db.DataStreams[i].Resource; |
| if (!rs.IsSolidSmall()) |
| complexity += rs.PackSize; |
| } |
| |
| RINOK(callback->SetTotal(complexity)) |
| UInt64 totalComplexity = complexity; |
| |
| NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; |
| CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec; |
| |
| CLocalProgress *lps = new CLocalProgress; |
| CMyComPtr<ICompressProgressInfo> progress = lps; |
| lps->Init(callback, true); |
| |
| complexity = 0; |
| |
| // bool useResourceCompression = false; |
| // use useResourceCompression only if CHeader::Flags compression is also set |
| |
| CHeader header; |
| header.SetDefaultFields(false); |
| |
| if (isUpdate) |
| { |
| const CHeader &srcHeader = _volumes[1].Header; |
| header.Flags = srcHeader.Flags; |
| header.Version = srcHeader.Version; |
| header.ChunkSize = srcHeader.ChunkSize; |
| header.ChunkSizeBits = srcHeader.ChunkSizeBits; |
| } |
| |
| CMyComPtr<IStreamSetRestriction> setRestriction; |
| outSeqStream->QueryInterface(IID_IStreamSetRestriction, (void **)&setRestriction); |
| if (setRestriction) |
| RINOK(setRestriction->SetRestriction(0, kHeaderSizeMax)) |
| |
| { |
| Byte buf[kHeaderSizeMax]; |
| header.WriteTo(buf); |
| RINOK(WriteStream(outStream, buf, kHeaderSizeMax)) |
| } |
| |
| UInt64 curPos = kHeaderSizeMax; |
| |
| CInStreamWithSha1 *inShaStreamSpec = new CInStreamWithSha1; |
| CMyComPtr<ISequentialInStream> inShaStream = inShaStreamSpec; |
| |
| CLimitedSequentialInStream *inStreamLimitedSpec = NULL; |
| CMyComPtr<ISequentialInStream> inStreamLimited; |
| if (_volumes.Size() == 2) |
| { |
| inStreamLimitedSpec = new CLimitedSequentialInStream; |
| inStreamLimited = inStreamLimitedSpec; |
| inStreamLimitedSpec->SetStream(_volumes[1].Stream); |
| } |
| |
| |
| CRecordVector<CStreamInfo> streams; |
| CSortedIndex sortedHashes; // indexes to streams, sorted by SHA1 |
| |
| // ---------- Copy unchanged data streams ---------- |
| |
| UInt64 solidRunOffset = 0; |
| UInt64 curSolidSize = 0; |
| |
| for (i = 0; i < _db.DataStreams.Size(); i++) |
| { |
| const CStreamInfo &siOld = _db.DataStreams[i]; |
| const CResource &rs = siOld.Resource; |
| |
| const unsigned numRefs = streamsRefs[i]; |
| |
| if (numRefs == 0) |
| { |
| if (!rs.IsSolidSmall()) |
| continue; |
| if (streamsRefs[_db.Solids[rs.SolidIndex].StreamIndex] == 0) |
| continue; |
| } |
| |
| lps->InSize = lps->OutSize = complexity; |
| RINOK(lps->SetCur()) |
| |
| const unsigned streamIndex = streams.Size(); |
| CStreamInfo s; |
| s.Resource = rs; |
| s.PartNumber = 1; |
| s.RefCount = numRefs; |
| |
| memcpy(s.Hash, siOld.Hash, kHashSize); |
| |
| if (rs.IsSolid()) |
| { |
| CSolid &ss = _db.Solids[rs.SolidIndex]; |
| if (rs.IsSolidSmall()) |
| { |
| UInt64 oldOffset = ss.SolidOffset; |
| if (rs.Offset < oldOffset) |
| return E_FAIL; |
| UInt64 relatOffset = rs.Offset - oldOffset; |
| s.Resource.Offset = solidRunOffset + relatOffset; |
| } |
| else |
| { |
| // IsSolidBig |
| solidRunOffset += curSolidSize; |
| curSolidSize = ss.UnpackSize; |
| } |
| } |
| else |
| { |
| solidRunOffset = 0; |
| curSolidSize = 0; |
| } |
| |
| if (!rs.IsSolid() || rs.IsSolidSmall()) |
| { |
| const int find = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, (int)streamIndex); |
| if (find != -1) |
| return E_FAIL; // two streams with same SHA-1 |
| } |
| |
| if (!rs.IsSolid() || rs.IsSolidBig()) |
| { |
| RINOK(InStream_SeekSet(_volumes[siOld.PartNumber].Stream, rs.Offset)) |
| inStreamLimitedSpec->Init(rs.PackSize); |
| RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)) |
| if (copyCoderSpec->TotalSize != rs.PackSize) |
| return E_FAIL; |
| s.Resource.Offset = curPos; |
| curPos += rs.PackSize; |
| lps->ProgressOffset += rs.PackSize; |
| } |
| |
| streams.Add(s); |
| } |
| |
| |
| // ---------- Write new items ---------- |
| |
| CUIntVector hlIndexes; // sorted indexes for hard link items |
| |
| for (i = 0; i < db.UpdateIndexes.Size(); i++) |
| { |
| lps->InSize = lps->OutSize = complexity; |
| RINOK(lps->SetCur()) |
| const CUpdateItem &ui = db.UpdateItems[db.UpdateIndexes[i]]; |
| CMetaItem &mi = db.MetaItems[ui.MetaIndex]; |
| UInt64 size = 0; |
| |
| if (ui.AltStreamIndex >= 0) |
| { |
| if (mi.Skip) |
| continue; |
| size = mi.AltStreams[ui.AltStreamIndex].Size; |
| } |
| else |
| { |
| size = mi.Size; |
| if (mi.IsDir) |
| { |
| // we support LINK files here |
| if (mi.Reparse.Size() == 0) |
| continue; |
| } |
| } |
| |
| if (ui.InArcIndex >= 0) |
| { |
| // data streams with OLD Data were written already |
| // we just need to find HashIndex in hashes. |
| |
| if ((unsigned)ui.InArcIndex >= _db.SortedItems.Size()) |
| return E_FAIL; |
| |
| const CItem &item = _db.Items[_db.SortedItems[ui.InArcIndex]]; |
| |
| if (item.StreamIndex < 0) |
| { |
| if (size == 0) |
| continue; |
| // if (_db.ItemHasStream(item)) |
| return E_FAIL; |
| } |
| |
| // We support empty file (size = 0, but with stream and SHA-1) from old archive |
| |
| const CStreamInfo &siOld = _db.DataStreams[item.StreamIndex]; |
| |
| const int index = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, -1); |
| // we must have written that stream already |
| if (index == -1) |
| return E_FAIL; |
| |
| if (ui.AltStreamIndex < 0) |
| mi.HashIndex = index; |
| else |
| mi.AltStreams[ui.AltStreamIndex].HashIndex = index; |
| |
| continue; |
| } |
| |
| CMyComPtr<ISequentialInStream> fileInStream; |
| HRESULT res = callback->GetStream(ui.CallbackIndex, &fileInStream); |
| |
| if (res == S_FALSE) |
| { |
| if (ui.AltStreamIndex >= 0) |
| { |
| mi.NumSkipAltStreams++; |
| mi.AltStreams[ui.AltStreamIndex].Skip = true; |
| } |
| else |
| mi.Skip = true; |
| } |
| else |
| { |
| RINOK(res) |
| |
| int miIndex = -1; |
| |
| if (!fileInStream) |
| { |
| if (!mi.IsDir) |
| return E_INVALIDARG; |
| } |
| else if (ui.AltStreamIndex < 0) |
| { |
| CMyComPtr<IStreamGetProps2> getProps2; |
| fileInStream->QueryInterface(IID_IStreamGetProps2, (void **)&getProps2); |
| if (getProps2) |
| { |
| CStreamFileProps props; |
| if (getProps2->GetProps2(&props) == S_OK) |
| { |
| mi.Attrib = props.Attrib; |
| if (_timeOptions.Write_CTime.Val) mi.CTime = props.CTime; |
| if (_timeOptions.Write_ATime.Val) mi.ATime = props.ATime; |
| if (_timeOptions.Write_MTime.Val) mi.MTime = props.MTime; |
| mi.FileID = props.FileID_Low; |
| if (props.NumLinks <= 1) |
| mi.FileID = 0; |
| mi.VolID = props.VolID; |
| if (mi.FileID != 0) |
| miIndex = AddToHardLinkList(db.MetaItems, (unsigned)ui.MetaIndex, hlIndexes); |
| |
| if (props.Size != size && props.Size != (UInt64)(Int64)-1) |
| { |
| const Int64 delta = (Int64)props.Size - (Int64)size; |
| const Int64 newComplexity = (Int64)totalComplexity + delta; |
| if (newComplexity > 0) |
| { |
| totalComplexity = (UInt64)newComplexity; |
| callback->SetTotal(totalComplexity); |
| } |
| mi.Size = props.Size; |
| size = props.Size; |
| } |
| } |
| } |
| } |
| |
| if (miIndex >= 0) |
| { |
| mi.HashIndex = db.MetaItems[miIndex].HashIndex; |
| if (mi.HashIndex >= 0) |
| streams[mi.HashIndex].RefCount++; |
| // fix for future: maybe we need to check also that real size is equal to size from IStreamGetProps2 |
| } |
| else if (ui.AltStreamIndex < 0 && mi.Reparse.Size() != 0) |
| { |
| if (mi.Reparse.Size() < 8) |
| return E_FAIL; |
| NCrypto::NSha1::CContext sha1; |
| sha1.Init(); |
| const size_t packSize = mi.Reparse.Size() - 8; |
| sha1.Update((const Byte *)mi.Reparse + 8, packSize); |
| Byte hash[kHashSize]; |
| sha1.Final(hash); |
| |
| int index = AddUniqHash(&streams.Front(), sortedHashes, hash, (int)streams.Size()); |
| |
| if (index != -1) |
| streams[index].RefCount++; |
| else |
| { |
| index = (int)streams.Size(); |
| RINOK(WriteStream(outStream, (const Byte *)mi.Reparse + 8, packSize)) |
| CStreamInfo s; |
| s.Resource.PackSize = packSize; |
| s.Resource.Offset = curPos; |
| s.Resource.UnpackSize = packSize; |
| s.Resource.Flags = 0; // check it |
| /* |
| if (useResourceCompression) |
| s.Resource.Flags = NResourceFlags::Compressed; |
| */ |
| s.PartNumber = 1; |
| s.RefCount = 1; |
| memcpy(s.Hash, hash, kHashSize); |
| curPos += packSize; |
| |
| streams.Add(s); |
| } |
| |
| mi.HashIndex = index; |
| } |
| else |
| { |
| inShaStreamSpec->SetStream(fileInStream); |
| |
| CMyComPtr<IInStream> inSeekStream; |
| fileInStream.QueryInterface(IID_IInStream, (void **)&inSeekStream); |
| |
| fileInStream.Release(); |
| inShaStreamSpec->Init(); |
| UInt64 offsetBlockSize = 0; |
| /* |
| if (useResourceCompression) |
| { |
| for (UInt64 t = kChunkSize; t < size; t += kChunkSize) |
| { |
| Byte buf[8]; |
| SetUi32(buf, (UInt32)t); |
| RINOK(WriteStream(outStream, buf, 4)); |
| offsetBlockSize += 4; |
| } |
| } |
| */ |
| |
| // 22.02: we use additional read-only pass to calculate SHA-1 |
| bool needWritePass = true; |
| int index = -1; |
| |
| if (inSeekStream /* && !sortedHashes.IsEmpty() */) |
| { |
| RINOK(copyCoder->Code(inShaStream, NULL, NULL, NULL, progress)) |
| size = copyCoderSpec->TotalSize; |
| if (size == 0) |
| needWritePass = false; |
| else |
| { |
| Byte hash[kHashSize]; |
| inShaStreamSpec->Final(hash); |
| |
| index = AddUniqHash(&streams.Front(), sortedHashes, hash, -1); |
| if (index != -1) |
| { |
| streams[index].RefCount++; |
| needWritePass = false; |
| } |
| else |
| { |
| RINOK(InStream_SeekToBegin(inSeekStream)) |
| inShaStreamSpec->Init(); |
| } |
| } |
| } |
| |
| if (needWritePass) |
| { |
| RINOK(copyCoder->Code(inShaStream, outStream, NULL, NULL, progress)) |
| size = copyCoderSpec->TotalSize; |
| } |
| |
| if (size != 0) |
| { |
| if (needWritePass) |
| { |
| Byte hash[kHashSize]; |
| const UInt64 packSize = offsetBlockSize + size; |
| inShaStreamSpec->Final(hash); |
| |
| index = AddUniqHash(&streams.Front(), sortedHashes, hash, (int)streams.Size()); |
| |
| if (index != -1) |
| { |
| streams[index].RefCount++; |
| outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos); |
| outStream->SetSize(curPos); |
| } |
| else |
| { |
| index = (int)streams.Size(); |
| CStreamInfo s; |
| s.Resource.PackSize = packSize; |
| s.Resource.Offset = curPos; |
| s.Resource.UnpackSize = size; |
| s.Resource.Flags = 0; |
| /* |
| if (useResourceCompression) |
| s.Resource.Flags = NResourceFlags::Compressed; |
| */ |
| s.PartNumber = 1; |
| s.RefCount = 1; |
| memcpy(s.Hash, hash, kHashSize); |
| curPos += packSize; |
| |
| streams.Add(s); |
| } |
| } // needWritePass |
| if (ui.AltStreamIndex < 0) |
| mi.HashIndex = index; |
| else |
| mi.AltStreams[ui.AltStreamIndex].HashIndex = index; |
| } // (size != 0) |
| } |
| } |
| fileInStream.Release(); |
| complexity += size; |
| RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) |
| } |
| |
| while (secureBlocks.Size() < numNewImages) |
| secureBlocks.AddNew(); |
| |
| |
| |
| // ---------- Write Images ---------- |
| |
| for (i = 0; i < numNewImages; i++) |
| { |
| lps->InSize = lps->OutSize = complexity; |
| RINOK(lps->SetCur()) |
| if (i < isChangedImage.Size() && !isChangedImage[i]) |
| { |
| CStreamInfo s = _db.MetaStreams[i]; |
| |
| RINOK(InStream_SeekSet(_volumes[1].Stream, s.Resource.Offset)) |
| inStreamLimitedSpec->Init(s.Resource.PackSize); |
| RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)) |
| if (copyCoderSpec->TotalSize != s.Resource.PackSize) |
| return E_FAIL; |
| |
| s.Resource.Offset = curPos; |
| s.PartNumber = 1; |
| s.RefCount = 1; |
| streams.Add(s); |
| |
| if (_bootIndex != 0 && _bootIndex == (UInt32)i + 1) |
| { |
| header.MetadataResource = s.Resource; |
| header.BootIndex = _bootIndex; |
| } |
| |
| lps->ProgressOffset += s.Resource.PackSize; |
| curPos += s.Resource.PackSize; |
| // printf("\nWrite old image %x\n", i + 1); |
| continue; |
| } |
| |
| const CDir &tree = trees[i]; |
| const UInt32 kSecuritySize = 8; |
| |
| size_t pos = kSecuritySize; |
| |
| const CUniqBlocks &secUniqBlocks = secureBlocks[i]; |
| const CObjectVector<CByteBuffer> &secBufs = secUniqBlocks.Bufs; |
| pos += (size_t)secUniqBlocks.GetTotalSizeInBytes(); |
| pos += secBufs.Size() * 8; |
| pos = (pos + 7) & ~(size_t)7; |
| |
| db.DefaultDirItem = ri; |
| pos += db.WriteTree_Dummy(tree); |
| |
| CByteArr meta(pos); |
| |
| Set32((Byte *)meta + 4, secBufs.Size()) // num security entries |
| pos = kSecuritySize; |
| |
| if (secBufs.Size() == 0) |
| { |
| // we can write 0 here only if there is no security data, imageX does it, |
| // but some programs expect size = 8 |
| Set32((Byte *)meta, 8) // size of security data |
| // Set32((Byte *)meta, 0); |
| } |
| else |
| { |
| unsigned k; |
| for (k = 0; k < secBufs.Size(); k++, pos += 8) |
| { |
| Set64(meta + pos, secBufs[k].Size()) |
| } |
| for (k = 0; k < secBufs.Size(); k++) |
| { |
| const CByteBuffer &buf = secBufs[k]; |
| size_t size = buf.Size(); |
| if (size != 0) |
| { |
| memcpy(meta + pos, buf, size); |
| pos += size; |
| } |
| } |
| while ((pos & 7) != 0) |
| meta[pos++] = 0; |
| Set32((Byte *)meta, (UInt32)pos) // size of security data |
| } |
| |
| db.Hashes = &streams.Front(); |
| db.WriteTree(tree, (Byte *)meta, pos); |
| |
| { |
| NCrypto::NSha1::CContext sha; |
| sha.Init(); |
| sha.Update((const Byte *)meta, pos); |
| |
| Byte digest[kHashSize]; |
| sha.Final(digest); |
| |
| CStreamInfo s; |
| s.Resource.PackSize = pos; |
| s.Resource.Offset = curPos; |
| s.Resource.UnpackSize = pos; |
| s.Resource.Flags = NResourceFlags::kMetadata; |
| s.PartNumber = 1; |
| s.RefCount = 1; |
| memcpy(s.Hash, digest, kHashSize); |
| streams.Add(s); |
| |
| if (_bootIndex != 0 && _bootIndex == (UInt32)i + 1) |
| { |
| header.MetadataResource = s.Resource; |
| header.BootIndex = _bootIndex; |
| } |
| |
| RINOK(WriteStream(outStream, (const Byte *)meta, pos)) |
| meta.Free(); |
| curPos += pos; |
| } |
| } |
| |
| lps->InSize = lps->OutSize = complexity; |
| RINOK(lps->SetCur()) |
| |
| header.OffsetResource.UnpackSize = header.OffsetResource.PackSize = (UInt64)streams.Size() * kStreamInfoSize; |
| header.OffsetResource.Offset = curPos; |
| header.OffsetResource.Flags = NResourceFlags::kMetadata; |
| |
| |
| |
| // ---------- Write Streams Info Tables ---------- |
| |
| for (i = 0; i < streams.Size(); i++) |
| { |
| Byte buf[kStreamInfoSize]; |
| streams[i].WriteTo(buf); |
| RINOK(WriteStream(outStream, buf, kStreamInfoSize)) |
| curPos += kStreamInfoSize; |
| } |
| |
| AString xml ("<WIM>"); |
| AddTagUInt64_ToString(xml, "TOTALBYTES", curPos); |
| for (i = 0; i < trees.Size(); i++) |
| { |
| const CDir &tree = trees[i]; |
| |
| CXmlItem item; |
| if (_xmls.Size() == 1) |
| { |
| const CWimXml &_oldXml = _xmls[0]; |
| if (i < _oldXml.Images.Size()) |
| { |
| // int ttt = _oldXml.Images[i].ItemIndexInXml; |
| item = _oldXml.Xml.Root.SubItems[_oldXml.Images[i].ItemIndexInXml]; |
| } |
| } |
| if (i >= isChangedImage.Size() || isChangedImage[i]) |
| { |
| char temp[16]; |
| if (item.Name.IsEmpty()) |
| { |
| ConvertUInt32ToString(i + 1, temp); |
| item.Name = "IMAGE"; |
| item.IsTag = true; |
| CXmlProp &prop = item.Props.AddNew(); |
| prop.Name = "INDEX"; |
| prop.Value = temp; |
| } |
| |
| AddTag_String_IfEmpty(item, "NAME", temp); |
| AddTag_UInt64(item, "DIRCOUNT", tree.GetNumDirs() - 1); |
| AddTag_UInt64(item, "FILECOUNT", tree.GetNumFiles()); |
| AddTag_UInt64(item, "TOTALBYTES", tree.GetTotalSize(db.MetaItems)); |
| |
| AddTag_Time(item, "CREATIONTIME", ftCur); |
| AddTag_Time(item, "LASTMODIFICATIONTIME", ftCur); |
| } |
| |
| item.AppendTo(xml); |
| } |
| xml += "</WIM>"; |
| |
| size_t xmlSize; |
| { |
| UString utf16; |
| if (!ConvertUTF8ToUnicode(xml, utf16)) |
| return S_FALSE; |
| xmlSize = ((size_t)utf16.Len() + 1) * 2; |
| |
| CByteArr xmlBuf(xmlSize); |
| Set16((Byte *)xmlBuf, 0xFEFF) |
| for (i = 0; i < (unsigned)utf16.Len(); i++) |
| { |
| Set16((Byte *)xmlBuf + 2 + (size_t)i * 2, (UInt16)utf16[i]) |
| } |
| RINOK(WriteStream(outStream, (const Byte *)xmlBuf, xmlSize)) |
| } |
| |
| header.XmlResource.UnpackSize = |
| header.XmlResource.PackSize = xmlSize; |
| header.XmlResource.Offset = curPos; |
| header.XmlResource.Flags = NResourceFlags::kMetadata; |
| |
| outStream->Seek(0, STREAM_SEEK_SET, NULL); |
| header.NumImages = trees.Size(); |
| { |
| Byte buf[kHeaderSizeMax]; |
| header.WriteTo(buf); |
| RINOK(WriteStream(outStream, buf, kHeaderSizeMax)) |
| } |
| |
| if (setRestriction) |
| RINOK(setRestriction->SetRestriction(0, 0)) |
| |
| return S_OK; |
| |
| COM_TRY_END |
| } |
| |
| }} |