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 struct 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 
     43 static const wchar_t *k_DefaultHashMethod = L"CRC32";
     44 
     45 HRESULT 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 
    118 void 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 
    129 void 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 
    136 void CHashBundle::SetSize(UInt64 size)
    137 {
    138   CurSize = size;
    139 }
    140 
    141 static 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 
    152 void 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 
    202 HRESULT 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 
    330 static inline char GetHex(Byte value)
    331 {
    332   return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
    333 }
    334 
    335 void 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