diff options
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.cpp | 639 |
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 +} + +}} |