Home | History | Annotate | Download | only in Common
      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:
     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 
     33 static const char *k_DefaultHashMethod = "CRC32";
     34 
     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 
    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 
    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 
    129 void CHashBundle::SetSize(UInt64 size)
    130 {
    131   CurSize = size;
    132 }
    133 
    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 
    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 
    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 
    317 static inline char GetHex(unsigned v)
    318 {
    319   return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
    320 }
    321 
    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