blob: 5d9dea259c9a32e84f68a43ae2449880a4394e0f [file] [log] [blame]
// 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
}
}}