// HashCalc.cpp #include "StdAfx.h" #include "../../../../C/Alloc.h" #include "../../../Common/StringToInt.h" #include "../../Common/FileStreams.h" #include "../../Common/StreamUtils.h" #include "EnumDirItems.h" #include "HashCalc.h" using namespace NWindows; class CHashMidBuf { void *_data; public: CHashMidBuf(): _data(0) {} operator void *() { return _data; } bool Alloc(size_t size) { if (_data != 0) return false; _data = ::MidAlloc(size); return _data != 0; } ~CHashMidBuf() { ::MidFree(_data); } }; static const char *k_DefaultHashMethod = "CRC32"; HRESULT CHashBundle::SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &hashMethods) { UStringVector names = hashMethods; if (names.IsEmpty()) { UString s; s.SetFromAscii(k_DefaultHashMethod); names.Add(s); } CRecordVector ids; CObjectVector methods; unsigned i; for (i = 0; i < names.Size(); i++) { COneMethodInfo m; RINOK(m.ParseMethodFromString(names[i])); if (m.MethodName.IsEmpty()) m.MethodName = k_DefaultHashMethod; if (m.MethodName == "*") { CRecordVector tempMethods; GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods); methods.Clear(); ids.Clear(); FOR_VECTOR (t, tempMethods) { unsigned index = ids.AddToUniqueSorted(tempMethods[t]); if (ids.Size() != methods.Size()) methods.Insert(index, m); } break; } else { // m.MethodName.RemoveChar(L'-'); CMethodId id; if (!FindHashMethod(EXTERNAL_CODECS_LOC_VARS m.MethodName, id)) return E_NOTIMPL; unsigned index = ids.AddToUniqueSorted(id); if (ids.Size() != methods.Size()) methods.Insert(index, m); } } for (i = 0; i < ids.Size(); i++) { CMyComPtr hasher; AString name; RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher)); if (!hasher) throw "Can't create hasher"; const COneMethodInfo &m = methods[i]; { CMyComPtr scp; hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); if (scp) RINOK(m.SetCoderProps(scp, NULL)); } UInt32 digestSize = hasher->GetDigestSize(); if (digestSize > k_HashCalc_DigestSize_Max) return E_NOTIMPL; CHasherState &h = Hashers.AddNew(); h.Hasher = hasher; h.Name = name; h.DigestSize = digestSize; for (unsigned k = 0; k < k_HashCalc_NumGroups; k++) memset(h.Digests[k], 0, digestSize); } return S_OK; } void CHashBundle::InitForNewFile() { CurSize = 0; FOR_VECTOR (i, Hashers) { CHasherState &h = Hashers[i]; h.Hasher->Init(); memset(h.Digests[k_HashCalc_Index_Current], 0, h.DigestSize); } } void CHashBundle::Update(const void *data, UInt32 size) { CurSize += size; FOR_VECTOR (i, Hashers) Hashers[i].Hasher->Update(data, size); } void CHashBundle::SetSize(UInt64 size) { CurSize = size; } static void AddDigests(Byte *dest, const Byte *src, UInt32 size) { unsigned next = 0; for (UInt32 i = 0; i < size; i++) { next += (unsigned)dest[i] + (unsigned)src[i]; dest[i] = (Byte)next; next >>= 8; } } void CHashBundle::Final(bool isDir, bool isAltStream, const UString &path) { if (isDir) NumDirs++; else if (isAltStream) { NumAltStreams++; AltStreamsSize += CurSize; } else { NumFiles++; FilesSize += CurSize; } Byte pre[16]; memset(pre, 0, sizeof(pre)); if (isDir) pre[0] = 1; FOR_VECTOR (i, Hashers) { CHasherState &h = Hashers[i]; if (!isDir) { h.Hasher->Final(h.Digests[0]); if (!isAltStream) AddDigests(h.Digests[k_HashCalc_Index_DataSum], h.Digests[0], h.DigestSize); } h.Hasher->Init(); h.Hasher->Update(pre, sizeof(pre)); h.Hasher->Update(h.Digests[0], h.DigestSize); for (unsigned k = 0; k < path.Len(); k++) { wchar_t c = path[k]; Byte temp[2] = { (Byte)(c & 0xFF), (Byte)((c >> 8) & 0xFF) }; h.Hasher->Update(temp, 2); } Byte tempDigest[k_HashCalc_DigestSize_Max]; h.Hasher->Final(tempDigest); if (!isAltStream) AddDigests(h.Digests[k_HashCalc_Index_NamesSum], tempDigest, h.DigestSize); AddDigests(h.Digests[k_HashCalc_Index_StreamsSum], tempDigest, h.DigestSize); } } HRESULT HashCalc( DECL_EXTERNAL_CODECS_LOC_VARS const NWildcard::CCensor &censor, const CHashOptions &options, AString &errorInfo, IHashCallbackUI *callback) { CDirItems dirItems; dirItems.Callback = callback; if (options.StdInMode) { CDirItem di; di.Size = (UInt64)(Int64)-1; di.Attrib = 0; di.MTime.dwLowDateTime = 0; di.MTime.dwHighDateTime = 0; di.CTime = di.ATime = di.MTime; dirItems.Items.Add(di); } else { RINOK(callback->StartScanning()); dirItems.ScanAltStreams = options.AltStreamsMode; HRESULT res = EnumerateItems(censor, options.PathMode, UString(), dirItems); if (res != S_OK) { if (res != E_ABORT) errorInfo = "Scanning error"; return res; } RINOK(callback->FinishScanning(dirItems.Stat)); } unsigned i; CHashBundle hb; RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods)); hb.Init(); hb.NumErrors = dirItems.Stat.NumErrors; if (options.StdInMode) { RINOK(callback->SetNumFiles(1)); } else { RINOK(callback->SetTotal(dirItems.Stat.GetTotalBytes())); } const UInt32 kBufSize = 1 << 15; CHashMidBuf buf; if (!buf.Alloc(kBufSize)) return E_OUTOFMEMORY; UInt64 completeValue = 0; RINOK(callback->BeforeFirstFile(hb)); for (i = 0; i < dirItems.Items.Size(); i++) { CMyComPtr inStream; UString path; bool isDir = false; bool isAltStream = false; if (options.StdInMode) { inStream = new CStdInFileStream; } else { CInFileStream *inStreamSpec = new CInFileStream; inStream = inStreamSpec; const CDirItem &dirItem = dirItems.Items[i]; isDir = dirItem.IsDir(); isAltStream = dirItem.IsAltStream; path = dirItems.GetLogPath(i); if (!isDir) { FString phyPath = dirItems.GetPhyPath(i); if (!inStreamSpec->OpenShared(phyPath, options.OpenShareForWrite)) { HRESULT res = callback->OpenFileError(phyPath, ::GetLastError()); hb.NumErrors++; if (res != S_FALSE) return res; continue; } } } RINOK(callback->GetStream(path, isDir)); UInt64 fileSize = 0; hb.InitForNewFile(); if (!isDir) { for (UInt32 step = 0;; step++) { if ((step & 0xFF) == 0) RINOK(callback->SetCompleted(&completeValue)); UInt32 size; RINOK(inStream->Read(buf, kBufSize, &size)); if (size == 0) break; hb.Update(buf, size); fileSize += size; completeValue += size; } } hb.Final(isDir, isAltStream, path); RINOK(callback->SetOperationResult(fileSize, hb, !isDir)); RINOK(callback->SetCompleted(&completeValue)); } return callback->AfterLastFile(hb); } static inline char GetHex(unsigned v) { return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10))); } void AddHashHexToString(char *dest, const Byte *data, UInt32 size) { dest[size * 2] = 0; if (!data) { for (UInt32 i = 0; i < size; i++) { dest[0] = ' '; dest[1] = ' '; dest += 2; } return; } int step = 2; if (size <= 8) { step = -2; dest += size * 2 - 2; } for (UInt32 i = 0; i < size; i++) { unsigned b = data[i]; dest[0] = GetHex((b >> 4) & 0xF); dest[1] = GetHex(b & 0xF); dest += step; } }