// 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); } }; struct CEnumDirItemCallback_Hash: public IEnumDirItemCallback { IHashCallbackUI *Callback; HRESULT ScanProgress(UInt64 numFolders, UInt64 numFiles, UInt64 totalSize, const wchar_t *path, bool isDir) { return Callback->ScanProgress(numFolders, numFiles, totalSize, path, isDir); } }; static const wchar_t *k_DefaultHashMethod = L"CRC32"; HRESULT CHashBundle::SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &hashMethods) { UStringVector names = hashMethods; if (names.IsEmpty()) names.Add(k_DefaultHashMethod); CRecordVector<CMethodId> ids; CObjectVector<COneMethodInfo> 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 == L"*") { CRecordVector<CMethodId> tempMethods; GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods); methods.Clear(); ids.Clear(); FOR_VECTOR (t, tempMethods) { int 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; int index = ids.AddToUniqueSorted(id); if (ids.Size() != methods.Size()) methods.Insert(index, m); } } for (i = 0; i < ids.Size(); i++) { CMyComPtr<IHasher> hasher; UString name; RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher)); if (!hasher) throw "Can't create hasher"; const COneMethodInfo &m = methods[i]; { CMyComPtr<ICompressSetCoderProperties> 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 (int i = 0; i < k_HashCalc_NumGroups; i++) memset(h.Digests[i], 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, UString &errorInfo, IHashCallbackUI *callback) { CDirItems dirItems; UInt64 numErrors = 0; UInt64 totalBytes = 0; 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 { CEnumDirItemCallback_Hash enumCallback; enumCallback.Callback = callback; RINOK(callback->StartScanning()); dirItems.ScanAltStreams = options.AltStreamsMode; HRESULT res = EnumerateItems(censor, options.PathMode, UString(), dirItems, &enumCallback); totalBytes = dirItems.TotalSize; FOR_VECTOR (i, dirItems.ErrorPaths) { RINOK(callback->CanNotFindError(fs2us(dirItems.ErrorPaths[i]), dirItems.ErrorCodes[i])); } numErrors = dirItems.ErrorPaths.Size(); if (res != S_OK) { if (res != E_ABORT) errorInfo = L"Scanning error"; return res; } RINOK(callback->FinishScanning()); } unsigned i; CHashBundle hb; RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods)); hb.Init(); hb.NumErrors = numErrors; if (options.StdInMode) { RINOK(callback->SetNumFiles(1)); } else { RINOK(callback->SetTotal(totalBytes)); } 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<ISequentialInStream> 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) { UString phyPath = dirItems.GetPhyPath(i); if (!inStreamSpec->OpenShared(us2fs(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(Byte value) { return (char)((value < 10) ? ('0' + value) : ('A' + (value - 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++) { Byte b = data[i]; dest[0] = GetHex((Byte)((b >> 4) & 0xF)); dest[1] = GetHex((Byte)(b & 0xF)); dest += step; } }