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 static const char *k_DefaultHashMethod = "CRC32";
34
SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector & hashMethods)35 HRESULT 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
InitForNewFile()111 void 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
Update(const void * data,UInt32 size)122 void 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
SetSize(UInt64 size)129 void CHashBundle::SetSize(UInt64 size)
130 {
131 CurSize = size;
132 }
133
AddDigests(Byte * dest,const Byte * src,UInt32 size)134 static 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
Final(bool isDir,bool isAltStream,const UString & path)145 void 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
HashCalc(DECL_EXTERNAL_CODECS_LOC_VARS const NWildcard::CCensor & censor,const CHashOptions & options,AString & errorInfo,IHashCallbackUI * callback)195 HRESULT 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
GetHex(unsigned v)317 static inline char GetHex(unsigned v)
318 {
319 return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
320 }
321
AddHashHexToString(char * dest,const Byte * data,UInt32 size)322 void 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