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