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
15using namespace NWindows;
16
17class CHashMidBuf
18{
19  void *_data;
20public:
21  CHashMidBuf(): _data(0) {}
22  operator void *() { return _data; }
23  bool Alloc(size_t size)
24  {
25    if (_data != 0)
26      return false;
27    _data = ::MidAlloc(size);
28    return _data != 0;
29  }
30  ~CHashMidBuf() { ::MidFree(_data); }
31};
32
33struct CEnumDirItemCallback_Hash: public IEnumDirItemCallback
34{
35  IHashCallbackUI *Callback;
36
37  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
43static const wchar_t *k_DefaultHashMethod = L"CRC32";
44
45HRESULT 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
118void 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
129void 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
136void CHashBundle::SetSize(UInt64 size)
137{
138  CurSize = size;
139}
140
141static 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
152void 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
202HRESULT 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
330static inline char GetHex(Byte value)
331{
332  return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
333}
334
335void 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