Home | History | Annotate | Download | only in Windows
      1 // Windows/FileFind.cpp
      2 
      3 #include "StdAfx.h"
      4 
      5 #include "FileFind.h"
      6 #include "FileIO.h"
      7 #include "FileName.h"
      8 #ifndef _UNICODE
      9 #include "../Common/StringConvert.h"
     10 #endif
     11 
     12 #ifndef _UNICODE
     13 extern bool g_IsNT;
     14 #endif
     15 
     16 using namespace NWindows;
     17 using namespace NFile;
     18 using namespace NName;
     19 
     20 #if defined(_WIN32) && !defined(UNDER_CE)
     21 
     22 EXTERN_C_BEGIN
     23 
     24 typedef enum
     25 {
     26   My_FindStreamInfoStandard,
     27   My_FindStreamInfoMaxInfoLevel
     28 } MY_STREAM_INFO_LEVELS;
     29 
     30 typedef struct
     31 {
     32   LARGE_INTEGER StreamSize;
     33   WCHAR cStreamName[MAX_PATH + 36];
     34 } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
     35 
     36 typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
     37     LPVOID findStreamData, DWORD flags);
     38 
     39 typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);
     40 
     41 EXTERN_C_END
     42 
     43 #endif
     44 
     45 namespace NWindows {
     46 namespace NFile {
     47 
     48 #ifdef SUPPORT_DEVICE_FILE
     49 namespace NSystem
     50 {
     51 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
     52 }
     53 #endif
     54 
     55 namespace NFind {
     56 
     57 bool CFileInfo::IsDots() const throw()
     58 {
     59   if (!IsDir() || Name.IsEmpty())
     60     return false;
     61   if (Name[0] != FTEXT('.'))
     62     return false;
     63   return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.'));
     64 }
     65 
     66 #define WIN_FD_TO_MY_FI(fi, fd) \
     67   fi.Attrib = fd.dwFileAttributes; \
     68   fi.CTime = fd.ftCreationTime; \
     69   fi.ATime = fd.ftLastAccessTime; \
     70   fi.MTime = fd.ftLastWriteTime; \
     71   fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
     72   fi.IsAltStream = false; \
     73   fi.IsDevice = false;
     74 
     75   /*
     76   #ifdef UNDER_CE
     77   fi.ObjectID = fd.dwOID;
     78   #else
     79   fi.ReparseTag = fd.dwReserved0;
     80   #endif
     81   */
     82 
     83 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
     84 {
     85   WIN_FD_TO_MY_FI(fi, fd);
     86   fi.Name = us2fs(fd.cFileName);
     87   #if defined(_WIN32) && !defined(UNDER_CE)
     88   // fi.ShortName = us2fs(fd.cAlternateFileName);
     89   #endif
     90 }
     91 
     92 #ifndef _UNICODE
     93 
     94 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
     95 {
     96   WIN_FD_TO_MY_FI(fi, fd);
     97   fi.Name = fas2fs(fd.cFileName);
     98   #if defined(_WIN32) && !defined(UNDER_CE)
     99   // fi.ShortName = fas2fs(fd.cAlternateFileName);
    100   #endif
    101 }
    102 #endif
    103 
    104 ////////////////////////////////
    105 // CFindFile
    106 
    107 bool CFindFileBase::Close() throw()
    108 {
    109   if (_handle == INVALID_HANDLE_VALUE)
    110     return true;
    111   if (!::FindClose(_handle))
    112     return false;
    113   _handle = INVALID_HANDLE_VALUE;
    114   return true;
    115 }
    116 
    117 bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
    118 {
    119   if (!Close())
    120     return false;
    121   #ifndef _UNICODE
    122   if (!g_IsNT)
    123   {
    124     WIN32_FIND_DATAA fd;
    125     _handle = ::FindFirstFileA(fs2fas(path), &fd);
    126     if (_handle == INVALID_HANDLE_VALUE)
    127       return false;
    128     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    129   }
    130   else
    131   #endif
    132   {
    133     WIN32_FIND_DATAW fd;
    134 
    135     IF_USE_MAIN_PATH
    136       _handle = ::FindFirstFileW(fs2us(path), &fd);
    137     #ifdef WIN_LONG_PATH
    138     if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
    139     {
    140       UString longPath;
    141       if (GetSuperPath(path, longPath, USE_MAIN_PATH))
    142         _handle = ::FindFirstFileW(longPath, &fd);
    143     }
    144     #endif
    145     if (_handle == INVALID_HANDLE_VALUE)
    146       return false;
    147     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    148   }
    149   return true;
    150 }
    151 
    152 bool CFindFile::FindNext(CFileInfo &fi)
    153 {
    154   #ifndef _UNICODE
    155   if (!g_IsNT)
    156   {
    157     WIN32_FIND_DATAA fd;
    158     if (!::FindNextFileA(_handle, &fd))
    159       return false;
    160     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    161   }
    162   else
    163   #endif
    164   {
    165     WIN32_FIND_DATAW fd;
    166     if (!::FindNextFileW(_handle, &fd))
    167       return false;
    168     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    169   }
    170   return true;
    171 }
    172 
    173 #if defined(_WIN32) && !defined(UNDER_CE)
    174 
    175 ////////////////////////////////
    176 // AltStreams
    177 
    178 static FindFirstStreamW_Ptr g_FindFirstStreamW;
    179 static FindNextStreamW_Ptr g_FindNextStreamW;
    180 
    181 struct CFindStreamLoader
    182 {
    183   CFindStreamLoader()
    184   {
    185     g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW");
    186     g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW");
    187   }
    188 } g_FindStreamLoader;
    189 
    190 bool CStreamInfo::IsMainStream() const throw()
    191 {
    192   return Name == L"::$DATA";
    193 };
    194 
    195 UString CStreamInfo::GetReducedName() const
    196 {
    197   UString s = Name;
    198   if (s.Len() >= 6)
    199     if (wcscmp(s.RightPtr(6), L":$DATA") == 0)
    200       s.DeleteFrom(s.Len() - 6);
    201   return s;
    202 }
    203 
    204 static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
    205 {
    206   si.Size = sd.StreamSize.QuadPart;
    207   si.Name = sd.cStreamName;
    208 }
    209 
    210 bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
    211 {
    212   if (!Close())
    213     return false;
    214   if (!g_FindFirstStreamW)
    215   {
    216     ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    217     return false;
    218   }
    219   {
    220     MY_WIN32_FIND_STREAM_DATA sd;
    221     IF_USE_MAIN_PATH
    222       _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
    223     if (_handle == INVALID_HANDLE_VALUE)
    224     {
    225       if (::GetLastError() == ERROR_HANDLE_EOF)
    226         return false;
    227       // long name can be tricky for path like ".\dirName".
    228       #ifdef WIN_LONG_PATH
    229       if (USE_SUPER_PATH)
    230       {
    231         UString longPath;
    232         if (GetSuperPath(path, longPath, USE_MAIN_PATH))
    233           _handle = g_FindFirstStreamW(longPath, My_FindStreamInfoStandard, &sd, 0);
    234       }
    235       #endif
    236     }
    237     if (_handle == INVALID_HANDLE_VALUE)
    238       return false;
    239     Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
    240   }
    241   return true;
    242 }
    243 
    244 bool CFindStream::FindNext(CStreamInfo &si)
    245 {
    246   if (!g_FindNextStreamW)
    247   {
    248     ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    249     return false;
    250   }
    251   {
    252     MY_WIN32_FIND_STREAM_DATA sd;
    253     if (!g_FindNextStreamW(_handle, &sd))
    254       return false;
    255     Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
    256   }
    257   return true;
    258 }
    259 
    260 bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
    261 {
    262   bool res;
    263   if (_find.IsHandleAllocated())
    264     res = _find.FindNext(si);
    265   else
    266     res = _find.FindFirst(_filePath, si);
    267   if (res)
    268   {
    269     found = true;
    270     return true;
    271   }
    272   found = false;
    273   return (::GetLastError() == ERROR_HANDLE_EOF);
    274 }
    275 
    276 #endif
    277 
    278 
    279 #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
    280 
    281 void CFileInfoBase::Clear() throw()
    282 {
    283   Size = 0;
    284   MY_CLEAR_FILETIME(CTime);
    285   MY_CLEAR_FILETIME(ATime);
    286   MY_CLEAR_FILETIME(MTime);
    287   Attrib = 0;
    288   IsAltStream = false;
    289   IsDevice = false;
    290 }
    291 
    292 #if defined(_WIN32) && !defined(UNDER_CE)
    293 
    294 static int FindAltStreamColon(CFSTR path)
    295 {
    296   for (int i = 0;; i++)
    297   {
    298     FChar c = path[i];
    299     if (c == 0)
    300       return -1;
    301     if (c == ':')
    302     {
    303       if (path[i + 1] == '\\')
    304         if (i == 1 || (i > 1 && path[i - 2] == '\\'))
    305         {
    306           wchar_t c0 = path[i - 1];
    307           if (c0 >= 'a' && c0 <= 'z' ||
    308               c0 >= 'A' && c0 <= 'Z')
    309             continue;
    310         }
    311       return i;
    312     }
    313   }
    314 }
    315 
    316 #endif
    317 
    318 bool CFileInfo::Find(CFSTR path)
    319 {
    320   #ifdef SUPPORT_DEVICE_FILE
    321   if (IsDevicePath(path))
    322   {
    323     Clear();
    324     Name = path + 4;
    325 
    326     IsDevice = true;
    327     if (/* path[0] == '\\' && path[1] == '\\' && path[2] == '.' && path[3] == '\\' && */
    328         path[5] == ':' && path[6] == 0)
    329     {
    330       FChar drive[4] = { path[4], ':', '\\', 0 };
    331       UInt64 clusterSize, totalSize, freeSize;
    332       if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
    333       {
    334         Size = totalSize;
    335         return true;
    336       }
    337     }
    338 
    339     NIO::CInFile inFile;
    340     // ::OutputDebugStringW(path);
    341     if (!inFile.Open(path))
    342       return false;
    343     // ::OutputDebugStringW(L"---");
    344     if (inFile.SizeDefined)
    345       Size = inFile.Size;
    346     return true;
    347   }
    348   #endif
    349 
    350   #if defined(_WIN32) && !defined(UNDER_CE)
    351 
    352   int colonPos = FindAltStreamColon(path);
    353   if (colonPos >= 0)
    354   {
    355     UString streamName = fs2us(path + (unsigned)colonPos);
    356     FString filePath = path;
    357     filePath.DeleteFrom(colonPos);
    358     streamName += L":$DATA"; // change it!!!!
    359     if (Find(filePath))
    360     {
    361       // if (IsDir())
    362         Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
    363       Size = 0;
    364       CStreamEnumerator enumerator(filePath);
    365       for (;;)
    366       {
    367         CStreamInfo si;
    368         bool found;
    369         if (!enumerator.Next(si, found))
    370           return false;
    371         if (!found)
    372         {
    373           ::SetLastError(ERROR_FILE_NOT_FOUND);
    374           return false;
    375         }
    376         if (si.Name.IsEqualToNoCase(streamName))
    377         {
    378           Name += us2fs(si.Name);
    379           Name.DeleteFrom(Name.Len() - 6);
    380           Size = si.Size;
    381           IsAltStream = true;
    382           return true;
    383         }
    384       }
    385     }
    386   }
    387 
    388   #endif
    389 
    390   CFindFile finder;
    391   if (finder.FindFirst(path, *this))
    392     return true;
    393   #ifdef _WIN32
    394   {
    395     DWORD lastError = GetLastError();
    396     if (lastError == ERROR_BAD_NETPATH ||
    397         lastError == ERROR_FILE_NOT_FOUND ||
    398         lastError == ERROR_INVALID_NAME // for "\\SERVER\shared" paths that are translated to "\\?\UNC\SERVER\shared"
    399         )
    400     {
    401       unsigned len = MyStringLen(path);
    402       if (len > 2 && path[0] == '\\' && path[1] == '\\')
    403       {
    404         int startPos = 2;
    405         if (len > kSuperUncPathPrefixSize && IsSuperUncPath(path))
    406           startPos = kSuperUncPathPrefixSize;
    407         int pos = FindCharPosInString(path + startPos, FTEXT('\\'));
    408         if (pos >= 0)
    409         {
    410           pos += startPos + 1;
    411           len -= pos;
    412           int pos2 = FindCharPosInString(path + pos, FTEXT('\\'));
    413           if (pos2 < 0 || pos2 == (int)len - 1)
    414           {
    415             FString s = path;
    416             if (pos2 < 0)
    417             {
    418               pos2 = len;
    419               s += FTEXT('\\');
    420             }
    421             s += FCHAR_ANY_MASK;
    422             if (finder.FindFirst(s, *this))
    423               if (Name == FTEXT("."))
    424               {
    425                 Name.SetFrom(s.Ptr(pos), pos2);
    426                 return true;
    427               }
    428             ::SetLastError(lastError);
    429           }
    430         }
    431       }
    432     }
    433   }
    434   #endif
    435   return false;
    436 }
    437 
    438 bool DoesFileExist(CFSTR name)
    439 {
    440   CFileInfo fi;
    441   return fi.Find(name) && !fi.IsDir();
    442 }
    443 
    444 bool DoesDirExist(CFSTR name)
    445 {
    446   CFileInfo fi;
    447   return fi.Find(name) && fi.IsDir();
    448 }
    449 bool DoesFileOrDirExist(CFSTR name)
    450 {
    451   CFileInfo fi;
    452   return fi.Find(name);
    453 }
    454 
    455 bool CEnumerator::NextAny(CFileInfo &fi)
    456 {
    457   if (_findFile.IsHandleAllocated())
    458     return _findFile.FindNext(fi);
    459   else
    460     return _findFile.FindFirst(_wildcard, fi);
    461 }
    462 
    463 bool CEnumerator::Next(CFileInfo &fi)
    464 {
    465   for (;;)
    466   {
    467     if (!NextAny(fi))
    468       return false;
    469     if (!fi.IsDots())
    470       return true;
    471   }
    472 }
    473 
    474 bool CEnumerator::Next(CFileInfo &fi, bool &found)
    475 {
    476   if (Next(fi))
    477   {
    478     found = true;
    479     return true;
    480   }
    481   found = false;
    482   return (::GetLastError() == ERROR_NO_MORE_FILES);
    483 }
    484 
    485 ////////////////////////////////
    486 // CFindChangeNotification
    487 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
    488 
    489 bool CFindChangeNotification::Close() throw()
    490 {
    491   if (!IsHandleAllocated())
    492     return true;
    493   if (!::FindCloseChangeNotification(_handle))
    494     return false;
    495   _handle = INVALID_HANDLE_VALUE;
    496   return true;
    497 }
    498 
    499 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
    500 {
    501   #ifndef _UNICODE
    502   if (!g_IsNT)
    503     _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
    504   else
    505   #endif
    506   {
    507     IF_USE_MAIN_PATH
    508     _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
    509     #ifdef WIN_LONG_PATH
    510     if (!IsHandleAllocated())
    511     {
    512       UString longPath;
    513       if (GetSuperPath(path, longPath, USE_MAIN_PATH))
    514         _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter);
    515     }
    516     #endif
    517   }
    518   return _handle;
    519 }
    520 
    521 #ifndef UNDER_CE
    522 
    523 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
    524 {
    525   driveStrings.Clear();
    526   #ifndef _UNICODE
    527   if (!g_IsNT)
    528   {
    529     driveStrings.Clear();
    530     UINT32 size = GetLogicalDriveStrings(0, NULL);
    531     if (size == 0)
    532       return false;
    533     AString buf;
    534     UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size));
    535     if (newSize == 0 || newSize > size)
    536       return false;
    537     AString s;
    538     for (UINT32 i = 0; i < newSize; i++)
    539     {
    540       char c = buf[i];
    541       if (c == '\0')
    542       {
    543         driveStrings.Add(fas2fs(s));
    544         s.Empty();
    545       }
    546       else
    547         s += c;
    548     }
    549     return s.IsEmpty();
    550   }
    551   else
    552   #endif
    553   {
    554     UINT32 size = GetLogicalDriveStringsW(0, NULL);
    555     if (size == 0)
    556       return false;
    557     UString buf;
    558     UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size));
    559     if (newSize == 0 || newSize > size)
    560       return false;
    561     UString s;
    562     for (UINT32 i = 0; i < newSize; i++)
    563     {
    564       WCHAR c = buf[i];
    565       if (c == L'\0')
    566       {
    567         driveStrings.Add(us2fs(s));
    568         s.Empty();
    569       }
    570       else
    571         s += c;
    572     }
    573     return s.IsEmpty();
    574   }
    575 }
    576 
    577 #endif
    578 
    579 }}}
    580