summaryrefslogtreecommitdiffstats
path: root/src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp')
-rw-r--r--src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp639
1 files changed, 639 insertions, 0 deletions
diff --git a/src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp b/src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp
new file mode 100644
index 000000000..50b879e79
--- /dev/null
+++ b/src/libs/7zip/win/CPP/7zip/Archive/Wim/WimHandlerOut.cpp
@@ -0,0 +1,639 @@
+// WimHandlerOut.cpp
+
+#include "StdAfx.h"
+
+#include "../../../../C/CpuArch.h"
+
+#include "Common/ComTry.h"
+#include "Common/IntToString.h"
+
+#include "Windows/PropVariant.h"
+#include "Windows/Time.h"
+
+#include "../../Common/LimitedStreams.h"
+#include "../../Common/ProgressUtils.h"
+#include "../../Common/StreamUtils.h"
+
+#include "../../Crypto/RandGen.h"
+#include "../../Crypto/Sha1.h"
+
+#include "WimHandler.h"
+
+using namespace NWindows;
+
+namespace NArchive {
+namespace NWim {
+
+struct CSha1Hash
+{
+ Byte Hash[kHashSize];
+};
+
+struct CHashList
+{
+ CRecordVector<CSha1Hash> Digests;
+ CIntVector Sorted;
+
+ int AddUnique(const CSha1Hash &h);
+};
+
+int CHashList::AddUnique(const CSha1Hash &h)
+{
+ int left = 0, right = Sorted.Size();
+ while (left != right)
+ {
+ int mid = (left + right) / 2;
+ int index = Sorted[mid];
+ UInt32 i;
+ const Byte *hash2 = Digests[index].Hash;
+ for (i = 0; i < kHashSize; i++)
+ if (h.Hash[i] != hash2[i])
+ break;
+ if (i == kHashSize)
+ return index;
+ if (h.Hash[i] < hash2[i])
+ right = mid;
+ else
+ left = mid + 1;
+ }
+ Sorted.Insert(left, Digests.Add(h));
+ return -1;
+}
+
+struct CUpdateItem
+{
+ UString Name;
+ UInt64 Size;
+ FILETIME CTime;
+ FILETIME ATime;
+ FILETIME MTime;
+ UInt32 Attrib;
+ bool IsDir;
+ int HashIndex;
+
+ CUpdateItem(): HashIndex(-1) {}
+};
+
+struct CDir
+{
+ int Index;
+ UString Name;
+ CObjectVector<CDir> Dirs;
+ CIntVector Files;
+
+ CDir(): Index(-1) {}
+ bool IsLeaf() const { return Index >= 0; }
+ UInt64 GetNumDirs() const;
+ UInt64 GetNumFiles() const;
+ CDir* AddDir(CObjectVector<CUpdateItem> &items, const UString &name, int index);
+};
+
+UInt64 CDir::GetNumDirs() const
+{
+ UInt64 num = Dirs.Size();
+ for (int i = 0; i < Dirs.Size(); i++)
+ num += Dirs[i].GetNumDirs();
+ return num;
+}
+
+UInt64 CDir::GetNumFiles() const
+{
+ UInt64 num = Files.Size();
+ for (int i = 0; i < Dirs.Size(); i++)
+ num += Dirs[i].GetNumFiles();
+ return num;
+}
+
+CDir* CDir::AddDir(CObjectVector<CUpdateItem> &items, const UString &name, int index)
+{
+ int left = 0, right = Dirs.Size();
+ while (left != right)
+ {
+ int mid = (left + right) / 2;
+ CDir &d = Dirs[mid];
+ int compare = name.CompareNoCase(d.IsLeaf() ? items[Dirs[mid].Index].Name : d.Name);
+ if (compare == 0)
+ {
+ if (index >= 0)
+ d.Index = index;
+ return &d;
+ }
+ if (compare < 0)
+ right = mid;
+ else
+ left = mid + 1;
+ }
+ Dirs.Insert(left, CDir());
+ CDir &d = Dirs[left];
+ d.Index = index;
+ if (index < 0)
+ d.Name = name;
+ return &d;
+}
+
+
+STDMETHODIMP COutHandler::GetFileTimeType(UInt32 *type)
+{
+ *type = NFileTimeType::kWindows;
+ return S_OK;
+}
+
+static HRESULT GetTime(IArchiveUpdateCallback *callback, int index, PROPID propID, FILETIME &ft)
+{
+ ft.dwLowDateTime = ft.dwHighDateTime = 0;
+ NCOM::CPropVariant prop;
+ RINOK(callback->GetProperty(index, propID, &prop));
+ if (prop.vt == VT_FILETIME)
+ ft = prop.filetime;
+ else 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);
+}
+
+class CInStreamWithSha1:
+ public ISequentialInStream,
+ public CMyUnknownImp
+{
+ CMyComPtr<ISequentialInStream> _stream;
+ UInt64 _size;
+ NCrypto::NSha1::CContext _sha;
+public:
+ MY_UNKNOWN_IMP1(IInStream)
+ STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
+
+ void SetStream(ISequentialInStream *stream) { _stream = stream; }
+ void Init()
+ {
+ _size = 0;
+ _sha.Init();
+ }
+ void ReleaseStream() { _stream.Release(); }
+ UInt64 GetSize() const { return _size; }
+ void Final(Byte *digest) { _sha.Final(digest); }
+};
+
+STDMETHODIMP CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize)
+{
+ UInt32 realProcessedSize;
+ HRESULT result = _stream->Read(data, size, &realProcessedSize);
+ _size += realProcessedSize;
+ _sha.Update((const Byte *)data, realProcessedSize);
+ if (processedSize != NULL)
+ *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(const CUpdateItem &item, Byte *p, const Byte *hash)
+{
+ int fileNameLen = item.Name.Length() * 2;
+ int fileNameLen2 = (fileNameLen == 0 ? fileNameLen : fileNameLen + 2);
+
+ size_t totalLen = ((kDirRecordSize + fileNameLen2 + 6) & ~7);
+ if (p)
+ {
+ memset(p, 0, totalLen);
+ Set64(p, totalLen);
+ Set64(p + 8, item.Attrib);
+ Set32(p + 0xC, (UInt32)(Int32)-1); // item.SecurityId
+ // Set64(p + 0x10, 0); // subdirOffset
+ SetFileTimeToMem(p + 0x28, item.CTime);
+ SetFileTimeToMem(p + 0x30, item.ATime);
+ SetFileTimeToMem(p + 0x38, item.MTime);
+ if (hash)
+ memcpy(p + 0x40, hash, kHashSize);
+ /*
+ else
+ memset(p + 0x40, 0, kHashSize);
+ */
+ // Set16(p + 98, 0); // shortNameLen
+ Set16(p + 100, (UInt16)fileNameLen);
+ for (int i = 0; i * 2 < fileNameLen; i++)
+ Set16(p + kDirRecordSize + i * 2, item.Name[i]);
+ }
+ return totalLen;
+}
+
+static void WriteTree(const CDir &tree, CRecordVector<CSha1Hash> &digests,
+ CUpdateItem &defaultDirItem,
+ CObjectVector<CUpdateItem> &updateItems, Byte *dest, size_t &pos)
+{
+ int i;
+ for (i = 0; i < tree.Files.Size(); i++)
+ {
+ const CUpdateItem &ui = updateItems[tree.Files[i]];
+ pos += WriteItem(ui, dest ? dest + pos : NULL,
+ ui.HashIndex >= 0 ? digests[ui.HashIndex].Hash : NULL);
+ }
+
+ size_t posStart = pos;
+ for (i = 0; i < tree.Dirs.Size(); i++)
+ {
+ const CDir &subfolder = tree.Dirs[i];
+ CUpdateItem *item = &defaultDirItem;
+ if (subfolder.IsLeaf())
+ item = &updateItems[subfolder.Index];
+ else
+ defaultDirItem.Name = subfolder.Name;
+ pos += WriteItem(*item, NULL, NULL);
+ }
+
+ if (dest)
+ Set64(dest + pos, 0);
+
+ pos += 8;
+
+ for (i = 0; i < tree.Dirs.Size(); i++)
+ {
+ const CDir &subfolder = tree.Dirs[i];
+ if (dest)
+ {
+ CUpdateItem *item = &defaultDirItem;
+ if (subfolder.IsLeaf())
+ item = &updateItems[subfolder.Index];
+ else
+ defaultDirItem.Name = subfolder.Name;
+ size_t len = WriteItem(*item, dest + posStart, NULL);
+ Set64(dest + posStart + 0x10, pos);
+ posStart += len;
+ }
+ WriteTree(subfolder, digests, defaultDirItem, updateItems, dest, pos);
+ }
+}
+
+static void AddTag(AString &s, const char *name, const AString &value)
+{
+ s += "<";
+ s += name;
+ s += ">";
+ s += value;
+ s += "</";
+ s += name;
+ s += ">";
+}
+
+static void AddTagUInt64(AString &s, const char *name, UInt64 value)
+{
+ char temp[32];
+ ConvertUInt64ToString(value, temp);
+ AddTag(s, name, temp);
+}
+
+static AString TimeToXml(FILETIME &ft)
+{
+ AString res;
+ char temp[16] = { '0', 'x' };
+ ConvertUInt32ToHexWithZeros(ft.dwHighDateTime, temp + 2);
+ AddTag(res, "HIGHPART", temp);
+ ConvertUInt32ToHexWithZeros(ft.dwLowDateTime, temp + 2);
+ AddTag(res, "LOWPART", temp);
+ return res;
+}
+
+void CHeader::SetDefaultFields(bool useLZX)
+{
+ Version = kWimVersion;
+ Flags = NHeaderFlags::kRpFix;
+ ChunkSize = 0;
+ if (useLZX)
+ {
+ Flags |= NHeaderFlags::kCompression | NHeaderFlags::kLZX;
+ ChunkSize = kChunkSize;
+ }
+ g_RandomGenerator.Generate(Guid, 16);
+ PartNumber = 1;
+ NumParts = 1;
+ NumImages = 1;
+ BootIndex = 0;
+ OffsetResource.Clear();
+ XmlResource.Clear();
+ MetadataResource.Clear();
+ IntegrityResource.Clear();
+}
+
+static HRESULT UpdateArchive(ISequentialOutStream *seqOutStream,
+ CDir &rootFolder,
+ CObjectVector<CUpdateItem> &updateItems,
+ IArchiveUpdateCallback *callback)
+{
+ CMyComPtr<IOutStream> outStream;
+ RINOK(seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStream));
+ if (!outStream)
+ return E_NOTIMPL;
+
+ UInt64 complexity = 0;
+
+ int i;
+ for (i = 0; i < updateItems.Size(); i++)
+ complexity += updateItems[i].Size;
+
+ RINOK(callback->SetTotal(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 useCompression = false;
+
+ CHeader header;
+ header.SetDefaultFields(useCompression);
+ Byte buf[kHeaderSizeMax];
+ header.WriteTo(buf);
+ RINOK(WriteStream(outStream, buf, kHeaderSizeMax));
+
+ CHashList hashes;
+ CObjectVector<CStreamInfo> streams;
+
+ UInt64 curPos = kHeaderSizeMax;
+ UInt64 unpackTotalSize = 0;
+ for (i = 0; i < updateItems.Size(); i++)
+ {
+ lps->InSize = lps->OutSize = complexity;
+ RINOK(lps->SetCur());
+
+ CUpdateItem &ui = updateItems[i];
+ if (ui.IsDir || ui.Size == 0)
+ continue;
+
+ CInStreamWithSha1 *inShaStreamSpec = new CInStreamWithSha1;
+ CMyComPtr<ISequentialInStream> inShaStream = inShaStreamSpec;
+
+ {
+ CMyComPtr<ISequentialInStream> fileInStream;
+ HRESULT res = callback->GetStream(i, &fileInStream);
+ if (res != S_FALSE)
+ {
+ RINOK(res);
+ inShaStreamSpec->SetStream(fileInStream);
+ fileInStream.Release();
+ inShaStreamSpec->Init();
+ UInt64 offsetBlockSize = 0;
+ if (useCompression)
+ {
+ for (UInt64 t = kChunkSize; t < ui.Size; t += kChunkSize)
+ {
+ Byte buf[8];
+ SetUi32(buf, (UInt32)t);
+ RINOK(WriteStream(outStream, buf, 4));
+ offsetBlockSize += 4;
+ }
+ }
+
+ RINOK(copyCoder->Code(inShaStream, outStream, NULL, NULL, progress));
+ ui.Size = copyCoderSpec->TotalSize;
+
+ CSha1Hash hash;
+ unpackTotalSize += ui.Size;
+ UInt64 packSize = offsetBlockSize + ui.Size;
+ inShaStreamSpec->Final(hash.Hash);
+ int index = hashes.AddUnique(hash);
+ if (index >= 0)
+ {
+ ui.HashIndex = index;
+ streams[index].RefCount++;
+ outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos);
+ outStream->SetSize(curPos);
+ }
+ else
+ {
+ ui.HashIndex = hashes.Digests.Size() - 1;
+ CStreamInfo s;
+ s.Resource.PackSize = packSize;
+ s.Resource.Offset = curPos;
+ s.Resource.UnpackSize = ui.Size;
+ s.Resource.Flags = 0;
+ if (useCompression)
+ s.Resource.Flags = NResourceFlags::Compressed;
+ s.PartNumber = 1;
+ s.RefCount = 1;
+ memcpy(s.Hash, hash.Hash, kHashSize);
+ streams.Add(s);
+ curPos += packSize;
+ }
+ }
+ fileInStream.Release();
+ complexity += ui.Size;
+ RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
+ }
+ }
+
+
+ CUpdateItem ri;
+ FILETIME ft;
+ NTime::GetCurUtcFileTime(ft);
+ ri.MTime = ri.ATime = ri.CTime = ft;
+ ri.Attrib = FILE_ATTRIBUTE_DIRECTORY;
+
+ const UInt32 kSecuritySize = 8;
+ size_t pos = kSecuritySize;
+ WriteTree(rootFolder, hashes.Digests, ri, updateItems, NULL, pos);
+
+ CByteBuffer meta;
+ meta.SetCapacity(pos);
+
+ // 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 + 4, 0); // num security entries
+
+ pos = kSecuritySize;
+ WriteTree(rootFolder, hashes.Digests, ri, updateItems, (Byte *)meta, pos);
+
+ {
+ NCrypto::NSha1::CContext sha;
+ sha.Init();
+ sha.Update((const Byte *)meta, pos);
+ CSha1Hash digest;
+ sha.Final(digest.Hash);
+
+ 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.Hash, kHashSize);
+ streams.Add(s);
+ RINOK(WriteStream(outStream, (const Byte *)meta, pos));
+ meta.Free();
+ curPos += pos;
+ }
+
+
+ header.OffsetResource.UnpackSize = header.OffsetResource.PackSize = (UInt64)streams.Size() * kStreamInfoSize;
+ header.OffsetResource.Offset = curPos;
+ header.OffsetResource.Flags = NResourceFlags::kMetadata;
+
+ 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(xml, "TOTALBYTES", curPos);
+ xml += "<IMAGE INDEX=\"1\"><NAME>1</NAME>";
+ AddTagUInt64(xml, "DIRCOUNT", rootFolder.GetNumDirs());
+ AddTagUInt64(xml, "FILECOUNT", rootFolder.GetNumFiles());
+ AddTagUInt64(xml, "TOTALBYTES", unpackTotalSize);
+ NTime::GetCurUtcFileTime(ft);
+ AddTag(xml, "CREATIONTIME", TimeToXml(ft));
+ AddTag(xml, "LASTMODIFICATIONTIME", TimeToXml(ft));
+ xml += "</IMAGE></WIM>";
+
+ size_t xmlSize = (xml.Length() + 1) * 2;
+ meta.SetCapacity(xmlSize);
+ Set16((Byte *)meta, 0xFEFF);
+ for (i = 0; i < xml.Length(); i++)
+ Set16((Byte *)meta + 2 + i * 2, xml[i]);
+ RINOK(WriteStream(outStream, (const Byte *)meta, xmlSize));
+ meta.Free();
+
+ header.XmlResource.UnpackSize = header.XmlResource.PackSize = xmlSize;
+ header.XmlResource.Offset = curPos;
+ header.XmlResource.Flags = NResourceFlags::kMetadata;
+
+ outStream->Seek(0, STREAM_SEEK_SET, NULL);
+ header.WriteTo(buf);
+ return WriteStream(outStream, buf, kHeaderSizeMax);
+}
+
+STDMETHODIMP COutHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
+ IArchiveUpdateCallback *callback)
+{
+ COM_TRY_BEGIN
+ CObjectVector<CUpdateItem> updateItems;
+ CDir tree;
+ tree.Dirs.Add(CDir());
+ CDir &rootFolder = tree.Dirs.Back();
+
+ for (UInt32 i = 0; i < numItems; i++)
+ {
+ CUpdateItem ui;
+ Int32 newData, newProps;
+ UInt32 indexInArchive;
+ if (!callback)
+ return E_FAIL;
+ RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive));
+
+ {
+ NCOM::CPropVariant prop;
+ RINOK(callback->GetProperty(i, kpidIsDir, &prop));
+ if (prop.vt == VT_EMPTY)
+ ui.IsDir = false;
+ else if (prop.vt != VT_BOOL)
+ return E_INVALIDARG;
+ else
+ ui.IsDir = (prop.boolVal != VARIANT_FALSE);
+ }
+
+ {
+ NCOM::CPropVariant prop;
+ RINOK(callback->GetProperty(i, kpidAttrib, &prop));
+ if (prop.vt == VT_EMPTY)
+ ui.Attrib = (ui.IsDir ? FILE_ATTRIBUTE_DIRECTORY : 0);
+ else if (prop.vt != VT_UI4)
+ return E_INVALIDARG;
+ else
+ ui.Attrib = prop.ulVal;
+ }
+
+ RINOK(GetTime(callback, i, kpidCTime, ui.CTime));
+ RINOK(GetTime(callback, i, kpidATime, ui.ATime));
+ RINOK(GetTime(callback, i, kpidMTime, ui.MTime));
+
+ {
+ NCOM::CPropVariant prop;
+ RINOK(callback->GetProperty(i, kpidSize, &prop));
+ if (prop.vt != VT_UI8)
+ return E_INVALIDARG;
+ ui.Size = prop.uhVal.QuadPart;
+ }
+
+ UString path;
+ NCOM::CPropVariant prop;
+ RINOK(callback->GetProperty(i, kpidPath, &prop));
+ if (prop.vt == VT_BSTR)
+ path = prop.bstrVal;
+ else if (prop.vt != VT_EMPTY)
+ return E_INVALIDARG;
+
+ CDir *curItem = &rootFolder;
+ int len = path.Length();
+ UString fileName;
+ for (int j = 0; j < len; j++)
+ {
+ wchar_t c = path[j];
+ if (c == WCHAR_PATH_SEPARATOR || c == L'/')
+ {
+ curItem = curItem->AddDir(updateItems, fileName, -1);
+ fileName.Empty();
+ }
+ else
+ fileName += c;
+ }
+
+ ui.Name = fileName;
+ updateItems.Add(ui);
+ if (ui.IsDir)
+ curItem->AddDir(updateItems, fileName, (int)i);
+ else
+ curItem->Files.Add(i);
+ }
+ return UpdateArchive(outStream, tree, updateItems, callback);
+ COM_TRY_END
+}
+
+}}