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 * const 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     names.Add(UString(k_DefaultHashMethod));
     40 
     41   CRecordVector<CMethodId> ids;
     42   CObjectVector<COneMethodInfo> methods;
     43 
     44   unsigned i;
     45   for (i = 0; i < names.Size(); i++)
     46   {
     47     COneMethodInfo m;
     48     RINOK(m.ParseMethodFromString(names[i]));
     49 
     50     if (m.MethodName.IsEmpty())
     51       m.MethodName = k_DefaultHashMethod;
     52 
     53     if (m.MethodName == "*")
     54     {
     55       CRecordVector<CMethodId> tempMethods;
     56       GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods);
     57       methods.Clear();
     58       ids.Clear();
     59       FOR_VECTOR (t, tempMethods)
     60       {
     61         unsigned index = ids.AddToUniqueSorted(tempMethods[t]);
     62         if (ids.Size() != methods.Size())
     63           methods.Insert(index, m);
     64       }
     65       break;
     66     }
     67     else
     68     {
     69       // m.MethodName.RemoveChar(L'-');
     70       CMethodId id;
     71       if (!FindHashMethod(EXTERNAL_CODECS_LOC_VARS m.MethodName, id))
     72         return E_NOTIMPL;
     73       unsigned index = ids.AddToUniqueSorted(id);
     74       if (ids.Size() != methods.Size())
     75         methods.Insert(index, m);
     76     }
     77   }
     78 
     79   for (i = 0; i < ids.Size(); i++)
     80   {
     81     CMyComPtr<IHasher> hasher;
     82     AString name;
     83     RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher));
     84     if (!hasher)
     85       throw "Can't create hasher";
     86     const COneMethodInfo &m = methods[i];
     87     {
     88       CMyComPtr<ICompressSetCoderProperties> scp;
     89       hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp);
     90       if (scp)
     91         RINOK(m.SetCoderProps(scp, NULL));
     92     }
     93     UInt32 digestSize = hasher->GetDigestSize();
     94     if (digestSize > k_HashCalc_DigestSize_Max)
     95       return E_NOTIMPL;
     96     CHasherState &h = Hashers.AddNew();
     97     h.Hasher = hasher;
     98     h.Name = name;
     99     h.DigestSize = digestSize;
    100     for (unsigned k = 0; k < k_HashCalc_NumGroups; k++)
    101       memset(h.Digests[k], 0, digestSize);
    102   }
    103 
    104   return S_OK;
    105 }
    106 
    107 void CHashBundle::InitForNewFile()
    108 {
    109   CurSize = 0;
    110   FOR_VECTOR (i, Hashers)
    111   {
    112     CHasherState &h = Hashers[i];
    113     h.Hasher->Init();
    114     memset(h.Digests[k_HashCalc_Index_Current], 0, h.DigestSize);
    115   }
    116 }
    117 
    118 void CHashBundle::Update(const void *data, UInt32 size)
    119 {
    120   CurSize += size;
    121   FOR_VECTOR (i, Hashers)
    122     Hashers[i].Hasher->Update(data, size);
    123 }
    124 
    125 void CHashBundle::SetSize(UInt64 size)
    126 {
    127   CurSize = size;
    128 }
    129 
    130 static void AddDigests(Byte *dest, const Byte *src, UInt32 size)
    131 {
    132   unsigned next = 0;
    133   for (UInt32 i = 0; i < size; i++)
    134   {
    135     next += (unsigned)dest[i] + (unsigned)src[i];
    136     dest[i] = (Byte)next;
    137     next >>= 8;
    138   }
    139 }
    140 
    141 void CHashBundle::Final(bool isDir, bool isAltStream, const UString &path)
    142 {
    143   if (isDir)
    144     NumDirs++;
    145   else if (isAltStream)
    146   {
    147     NumAltStreams++;
    148     AltStreamsSize += CurSize;
    149   }
    150   else
    151   {
    152     NumFiles++;
    153     FilesSize += CurSize;
    154   }
    155 
    156   Byte pre[16];
    157   memset(pre, 0, sizeof(pre));
    158   if (isDir)
    159     pre[0] = 1;
    160 
    161   FOR_VECTOR (i, Hashers)
    162   {
    163     CHasherState &h = Hashers[i];
    164     if (!isDir)
    165     {
    166       h.Hasher->Final(h.Digests[0]);
    167       if (!isAltStream)
    168         AddDigests(h.Digests[k_HashCalc_Index_DataSum], h.Digests[0], h.DigestSize);
    169     }
    170 
    171     h.Hasher->Init();
    172     h.Hasher->Update(pre, sizeof(pre));
    173     h.Hasher->Update(h.Digests[0], h.DigestSize);
    174 
    175     for (unsigned k = 0; k < path.Len(); k++)
    176     {
    177       wchar_t c = path[k];
    178       Byte temp[2] = { (Byte)(c & 0xFF), (Byte)((c >> 8) & 0xFF) };
    179       h.Hasher->Update(temp, 2);
    180     }
    181 
    182     Byte tempDigest[k_HashCalc_DigestSize_Max];
    183     h.Hasher->Final(tempDigest);
    184     if (!isAltStream)
    185       AddDigests(h.Digests[k_HashCalc_Index_NamesSum], tempDigest, h.DigestSize);
    186     AddDigests(h.Digests[k_HashCalc_Index_StreamsSum], tempDigest, h.DigestSize);
    187   }
    188 }
    189 
    190 
    191 HRESULT HashCalc(
    192     DECL_EXTERNAL_CODECS_LOC_VARS
    193     const NWildcard::CCensor &censor,
    194     const CHashOptions &options,
    195     AString &errorInfo,
    196     IHashCallbackUI *callback)
    197 {
    198   CDirItems dirItems;
    199   dirItems.Callback = callback;
    200 
    201   if (options.StdInMode)
    202   {
    203     CDirItem di;
    204     di.Size = (UInt64)(Int64)-1;
    205     di.Attrib = 0;
    206     di.MTime.dwLowDateTime = 0;
    207     di.MTime.dwHighDateTime = 0;
    208     di.CTime = di.ATime = di.MTime;
    209     dirItems.Items.Add(di);
    210   }
    211   else
    212   {
    213     RINOK(callback->StartScanning());
    214     dirItems.ScanAltStreams = options.AltStreamsMode;
    215 
    216     HRESULT res = EnumerateItems(censor,
    217         options.PathMode,
    218         UString(),
    219         dirItems);
    220 
    221     if (res != S_OK)
    222     {
    223       if (res != E_ABORT)
    224         errorInfo = "Scanning error";
    225       return res;
    226     }
    227     RINOK(callback->FinishScanning(dirItems.Stat));
    228   }
    229 
    230   unsigned i;
    231   CHashBundle hb;
    232   RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods));
    233   // hb.Init();
    234 
    235   hb.NumErrors = dirItems.Stat.NumErrors;
    236 
    237   if (options.StdInMode)
    238   {
    239     RINOK(callback->SetNumFiles(1));
    240   }
    241   else
    242   {
    243     RINOK(callback->SetTotal(dirItems.Stat.GetTotalBytes()));
    244   }
    245 
    246   const UInt32 kBufSize = 1 << 15;
    247   CHashMidBuf buf;
    248   if (!buf.Alloc(kBufSize))
    249     return E_OUTOFMEMORY;
    250 
    251   UInt64 completeValue = 0;
    252 
    253   RINOK(callback->BeforeFirstFile(hb));
    254 
    255   for (i = 0; i < dirItems.Items.Size(); i++)
    256   {
    257     CMyComPtr<ISequentialInStream> inStream;
    258     UString path;
    259     bool isDir = false;
    260     bool isAltStream = false;
    261     if (options.StdInMode)
    262     {
    263       inStream = new CStdInFileStream;
    264     }
    265     else
    266     {
    267       CInFileStream *inStreamSpec = new CInFileStream;
    268       inStream = inStreamSpec;
    269       const CDirItem &dirItem = dirItems.Items[i];
    270       isDir = dirItem.IsDir();
    271       isAltStream = dirItem.IsAltStream;
    272       path = dirItems.GetLogPath(i);
    273       if (!isDir)
    274       {
    275         FString phyPath = dirItems.GetPhyPath(i);
    276         if (!inStreamSpec->OpenShared(phyPath, options.OpenShareForWrite))
    277         {
    278           HRESULT res = callback->OpenFileError(phyPath, ::GetLastError());
    279           hb.NumErrors++;
    280           if (res != S_FALSE)
    281             return res;
    282           continue;
    283         }
    284       }
    285     }
    286     RINOK(callback->GetStream(path, isDir));
    287     UInt64 fileSize = 0;
    288 
    289     hb.InitForNewFile();
    290     if (!isDir)
    291     {
    292       for (UInt32 step = 0;; step++)
    293       {
    294         if ((step & 0xFF) == 0)
    295           RINOK(callback->SetCompleted(&completeValue));
    296         UInt32 size;
    297         RINOK(inStream->Read(buf, kBufSize, &size));
    298         if (size == 0)
    299           break;
    300         hb.Update(buf, size);
    301         fileSize += size;
    302         completeValue += size;
    303       }
    304     }
    305     hb.Final(isDir, isAltStream, path);
    306     RINOK(callback->SetOperationResult(fileSize, hb, !isDir));
    307     RINOK(callback->SetCompleted(&completeValue));
    308   }
    309   return callback->AfterLastFile(hb);
    310 }
    311 
    312 
    313 static inline char GetHex(unsigned v)
    314 {
    315   return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10)));
    316 }
    317 
    318 void AddHashHexToString(char *dest, const Byte *data, UInt32 size)
    319 {
    320   dest[size * 2] = 0;
    321 
    322   if (!data)
    323   {
    324     for (UInt32 i = 0; i < size; i++)
    325     {
    326       dest[0] = ' ';
    327       dest[1] = ' ';
    328       dest += 2;
    329     }
    330     return;
    331   }
    332 
    333   int step = 2;
    334   if (size <= 8)
    335   {
    336     step = -2;
    337     dest += size * 2 - 2;
    338   }
    339 
    340   for (UInt32 i = 0; i < size; i++)
    341   {
    342     unsigned b = data[i];
    343     dest[0] = GetHex((b >> 4) & 0xF);
    344     dest[1] = GetHex(b & 0xF);
    345     dest += step;
    346   }
    347 }
    348