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