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:
CHashMidBuf()21 CHashMidBuf(): _data(0) {}
operator void*()22 operator void *() { return _data; }
Alloc(size_t size)23 bool Alloc(size_t size)
24 {
25 if (_data != 0)
26 return false;
27 _data = ::MidAlloc(size);
28 return _data != 0;
29 }
~CHashMidBuf()30 ~CHashMidBuf() { ::MidFree(_data); }
31 };
32
33 struct CEnumDirItemCallback_Hash: public IEnumDirItemCallback
34 {
35 IHashCallbackUI *Callback;
36
ScanProgressCEnumDirItemCallback_Hash37 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
43 static const wchar_t *k_DefaultHashMethod = L"CRC32";
44
SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector & hashMethods)45 HRESULT 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
InitForNewFile()118 void 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
Update(const void * data,UInt32 size)129 void 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
SetSize(UInt64 size)136 void CHashBundle::SetSize(UInt64 size)
137 {
138 CurSize = size;
139 }
140
AddDigests(Byte * dest,const Byte * src,UInt32 size)141 static 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
Final(bool isDir,bool isAltStream,const UString & path)152 void 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
HashCalc(DECL_EXTERNAL_CODECS_LOC_VARS const NWildcard::CCensor & censor,const CHashOptions & options,UString & errorInfo,IHashCallbackUI * callback)202 HRESULT 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
GetHex(Byte value)330 static inline char GetHex(Byte value)
331 {
332 return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
333 }
334
AddHashHexToString(char * dest,const Byte * data,UInt32 size)335 void 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