• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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