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 * const 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     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 
InitForNewFile()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 
Update(const void * data,UInt32 size)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 
SetSize(UInt64 size)125 void CHashBundle::SetSize(UInt64 size)
126 {
127   CurSize = size;
128 }
129 
AddDigests(Byte * dest,const Byte * src,UInt32 size)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 
Final(bool isDir,bool isAltStream,const UString & path)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 
HashCalc(DECL_EXTERNAL_CODECS_LOC_VARS const NWildcard::CCensor & censor,const CHashOptions & options,AString & errorInfo,IHashCallbackUI * callback)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 
GetHex(unsigned v)313 static inline char GetHex(unsigned v)
314 {
315   return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
316 }
317 
AddHashHexToString(char * dest,const Byte * data,UInt32 size)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