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