1// HashCalc.cpp 2 3#include "StdAfx.h" 4 5#include "../../../../C/Alloc.h" 6 7#include "../../../Common/StringToInt.h" 8 9#include "../../Common/FileStreams.h" 10#include "../../Common/StreamUtils.h" 11 12#include "EnumDirItems.h" 13#include "HashCalc.h" 14 15using namespace NWindows; 16 17class CHashMidBuf 18{ 19 void *_data; 20public: 21 CHashMidBuf(): _data(0) {} 22 operator void *() { return _data; } 23 bool Alloc(size_t size) 24 { 25 if (_data != 0) 26 return false; 27 _data = ::MidAlloc(size); 28 return _data != 0; 29 } 30 ~CHashMidBuf() { ::MidFree(_data); } 31}; 32 33struct CEnumDirItemCallback_Hash: public IEnumDirItemCallback 34{ 35 IHashCallbackUI *Callback; 36 37 HRESULT ScanProgress(UInt64 numFolders, UInt64 numFiles, UInt64 totalSize, const wchar_t *path, bool isDir) 38 { 39 return Callback->ScanProgress(numFolders, numFiles, totalSize, path, isDir); 40 } 41}; 42 43static const wchar_t *k_DefaultHashMethod = L"CRC32"; 44 45HRESULT CHashBundle::SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &hashMethods) 46{ 47 UStringVector names = hashMethods; 48 if (names.IsEmpty()) 49 names.Add(k_DefaultHashMethod); 50 51 CRecordVector<CMethodId> ids; 52 CObjectVector<COneMethodInfo> methods; 53 54 unsigned i; 55 for (i = 0; i < names.Size(); i++) 56 { 57 COneMethodInfo m; 58 RINOK(m.ParseMethodFromString(names[i])); 59 60 if (m.MethodName.IsEmpty()) 61 m.MethodName = k_DefaultHashMethod; 62 63 if (m.MethodName == L"*") 64 { 65 CRecordVector<CMethodId> tempMethods; 66 GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods); 67 methods.Clear(); 68 ids.Clear(); 69 FOR_VECTOR (t, tempMethods) 70 { 71 int index = ids.AddToUniqueSorted(tempMethods[t]); 72 if (ids.Size() != methods.Size()) 73 methods.Insert(index, m); 74 } 75 break; 76 } 77 else 78 { 79 // m.MethodName.RemoveChar(L'-'); 80 CMethodId id; 81 if (!FindHashMethod(EXTERNAL_CODECS_LOC_VARS m.MethodName, id)) 82 return E_NOTIMPL; 83 int index = ids.AddToUniqueSorted(id); 84 if (ids.Size() != methods.Size()) 85 methods.Insert(index, m); 86 } 87 } 88 89 for (i = 0; i < ids.Size(); i++) 90 { 91 CMyComPtr<IHasher> hasher; 92 UString name; 93 RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher)); 94 if (!hasher) 95 throw "Can't create hasher"; 96 const COneMethodInfo &m = methods[i]; 97 { 98 CMyComPtr<ICompressSetCoderProperties> scp; 99 hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); 100 if (scp) 101 { 102 RINOK(m.SetCoderProps(scp, NULL)); 103 } 104 } 105 UInt32 digestSize = hasher->GetDigestSize(); 106 if (digestSize > k_HashCalc_DigestSize_Max) 107 return E_NOTIMPL; 108 CHasherState &h = Hashers.AddNew(); 109 h.Hasher = hasher; 110 h.Name = name; 111 h.DigestSize = digestSize; 112 for (int i = 0; i < k_HashCalc_NumGroups; i++) 113 memset(h.Digests[i], 0, digestSize); 114 } 115 return S_OK; 116} 117 118void CHashBundle::InitForNewFile() 119{ 120 CurSize = 0; 121 FOR_VECTOR (i, Hashers) 122 { 123 CHasherState &h = Hashers[i]; 124 h.Hasher->Init(); 125 memset(h.Digests[k_HashCalc_Index_Current], 0, h.DigestSize); 126 } 127} 128 129void CHashBundle::Update(const void *data, UInt32 size) 130{ 131 CurSize += size; 132 FOR_VECTOR (i, Hashers) 133 Hashers[i].Hasher->Update(data, size); 134} 135 136void CHashBundle::SetSize(UInt64 size) 137{ 138 CurSize = size; 139} 140 141static void AddDigests(Byte *dest, const Byte *src, UInt32 size) 142{ 143 unsigned next = 0; 144 for (UInt32 i = 0; i < size; i++) 145 { 146 next += (unsigned)dest[i] + (unsigned)src[i]; 147 dest[i] = (Byte)next; 148 next >>= 8; 149 } 150} 151 152void CHashBundle::Final(bool isDir, bool isAltStream, const UString &path) 153{ 154 if (isDir) 155 NumDirs++; 156 else if (isAltStream) 157 { 158 NumAltStreams++; 159 AltStreamsSize += CurSize; 160 } 161 else 162 { 163 NumFiles++; 164 FilesSize += CurSize; 165 } 166 167 Byte pre[16]; 168 memset(pre, 0, sizeof(pre)); 169 if (isDir) 170 pre[0] = 1; 171 172 FOR_VECTOR (i, Hashers) 173 { 174 CHasherState &h = Hashers[i]; 175 if (!isDir) 176 { 177 h.Hasher->Final(h.Digests[0]); 178 if (!isAltStream) 179 AddDigests(h.Digests[k_HashCalc_Index_DataSum], h.Digests[0], h.DigestSize); 180 } 181 182 h.Hasher->Init(); 183 h.Hasher->Update(pre, sizeof(pre)); 184 h.Hasher->Update(h.Digests[0], h.DigestSize); 185 186 for (unsigned k = 0; k < path.Len(); k++) 187 { 188 wchar_t c = path[k]; 189 Byte temp[2] = { (Byte)(c & 0xFF), (Byte)((c >> 8) & 0xFF) }; 190 h.Hasher->Update(temp, 2); 191 } 192 193 Byte tempDigest[k_HashCalc_DigestSize_Max]; 194 h.Hasher->Final(tempDigest); 195 if (!isAltStream) 196 AddDigests(h.Digests[k_HashCalc_Index_NamesSum], tempDigest, h.DigestSize); 197 AddDigests(h.Digests[k_HashCalc_Index_StreamsSum], tempDigest, h.DigestSize); 198 } 199} 200 201 202HRESULT HashCalc( 203 DECL_EXTERNAL_CODECS_LOC_VARS 204 const NWildcard::CCensor &censor, 205 const CHashOptions &options, 206 UString &errorInfo, 207 IHashCallbackUI *callback) 208{ 209 CDirItems dirItems; 210 211 UInt64 numErrors = 0; 212 UInt64 totalBytes = 0; 213 if (options.StdInMode) 214 { 215 CDirItem di; 216 di.Size = (UInt64)(Int64)-1; 217 di.Attrib = 0; 218 di.MTime.dwLowDateTime = 0; 219 di.MTime.dwHighDateTime = 0; 220 di.CTime = di.ATime = di.MTime; 221 dirItems.Items.Add(di); 222 } 223 else 224 { 225 CEnumDirItemCallback_Hash enumCallback; 226 enumCallback.Callback = callback; 227 RINOK(callback->StartScanning()); 228 dirItems.ScanAltStreams = options.AltStreamsMode; 229 HRESULT res = EnumerateItems(censor, 230 options.PathMode, 231 UString(), 232 dirItems, &enumCallback); 233 totalBytes = dirItems.TotalSize; 234 FOR_VECTOR (i, dirItems.ErrorPaths) 235 { 236 RINOK(callback->CanNotFindError(fs2us(dirItems.ErrorPaths[i]), dirItems.ErrorCodes[i])); 237 } 238 numErrors = dirItems.ErrorPaths.Size(); 239 if (res != S_OK) 240 { 241 if (res != E_ABORT) 242 errorInfo = L"Scanning error"; 243 return res; 244 } 245 RINOK(callback->FinishScanning()); 246 } 247 248 unsigned i; 249 CHashBundle hb; 250 RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods)); 251 hb.Init(); 252 hb.NumErrors = numErrors; 253 254 if (options.StdInMode) 255 { 256 RINOK(callback->SetNumFiles(1)); 257 } 258 else 259 { 260 RINOK(callback->SetTotal(totalBytes)); 261 } 262 263 const UInt32 kBufSize = 1 << 15; 264 CHashMidBuf buf; 265 if (!buf.Alloc(kBufSize)) 266 return E_OUTOFMEMORY; 267 268 UInt64 completeValue = 0; 269 270 RINOK(callback->BeforeFirstFile(hb)); 271 272 for (i = 0; i < dirItems.Items.Size(); i++) 273 { 274 CMyComPtr<ISequentialInStream> inStream; 275 UString path; 276 bool isDir = false; 277 bool isAltStream = false; 278 if (options.StdInMode) 279 { 280 inStream = new CStdInFileStream; 281 } 282 else 283 { 284 CInFileStream *inStreamSpec = new CInFileStream; 285 inStream = inStreamSpec; 286 const CDirItem &dirItem = dirItems.Items[i]; 287 isDir = dirItem.IsDir(); 288 isAltStream = dirItem.IsAltStream; 289 path = dirItems.GetLogPath(i); 290 if (!isDir) 291 { 292 UString phyPath = dirItems.GetPhyPath(i); 293 if (!inStreamSpec->OpenShared(us2fs(phyPath), options.OpenShareForWrite)) 294 { 295 HRESULT res = callback->OpenFileError(phyPath, ::GetLastError()); 296 hb.NumErrors++; 297 if (res != S_FALSE) 298 return res; 299 continue; 300 } 301 } 302 } 303 RINOK(callback->GetStream(path, isDir)); 304 UInt64 fileSize = 0; 305 306 hb.InitForNewFile(); 307 if (!isDir) 308 { 309 for (UInt32 step = 0;; step++) 310 { 311 if ((step & 0xFF) == 0) 312 RINOK(callback->SetCompleted(&completeValue)); 313 UInt32 size; 314 RINOK(inStream->Read(buf, kBufSize, &size)); 315 if (size == 0) 316 break; 317 hb.Update(buf, size); 318 fileSize += size; 319 completeValue += size; 320 } 321 } 322 hb.Final(isDir, isAltStream, path); 323 RINOK(callback->SetOperationResult(fileSize, hb, !isDir)); 324 RINOK(callback->SetCompleted(&completeValue)); 325 } 326 return callback->AfterLastFile(hb); 327} 328 329 330static inline char GetHex(Byte value) 331{ 332 return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); 333} 334 335void AddHashHexToString(char *dest, const Byte *data, UInt32 size) 336{ 337 dest[size * 2] = 0; 338 if (!data) 339 { 340 for (UInt32 i = 0; i < size; i++) 341 { 342 dest[0] = ' '; 343 dest[1] = ' '; 344 dest += 2; 345 } 346 return; 347 } 348 int step = 2; 349 if (size <= 8) 350 { 351 step = -2; 352 dest += size * 2 - 2; 353 } 354 for (UInt32 i = 0; i < size; i++) 355 { 356 Byte b = data[i]; 357 dest[0] = GetHex((Byte)((b >> 4) & 0xF)); 358 dest[1] = GetHex((Byte)(b & 0xF)); 359 dest += step; 360 } 361} 362