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
33static const char *k_DefaultHashMethod = "CRC32";
34
35HRESULT 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
111void 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
122void 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
129void CHashBundle::SetSize(UInt64 size)
130{
131  CurSize = size;
132}
133
134static 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
145void 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
195HRESULT 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
317static inline char GetHex(unsigned v)
318{
319  return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
320}
321
322void 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