Home | History | Annotate | Download | only in Windows
      1 // Windows/FileFind.cpp
      2 
      3 #include "StdAfx.h"
      4 
      5 #ifndef _UNICODE
      6 #include "../Common/StringConvert.h"
      7 #endif
      8 
      9 #include "FileFind.h"
     10 #include "FileIO.h"
     11 #include "FileName.h"
     12 
     13 #ifndef _UNICODE
     14 extern bool g_IsNT;
     15 #endif
     16 
     17 using namespace NWindows;
     18 using namespace NFile;
     19 using namespace NName;
     20 
     21 #if defined(_WIN32) && !defined(UNDER_CE)
     22 
     23 EXTERN_C_BEGIN
     24 
     25 typedef enum
     26 {
     27   My_FindStreamInfoStandard,
     28   My_FindStreamInfoMaxInfoLevel
     29 } MY_STREAM_INFO_LEVELS;
     30 
     31 typedef struct
     32 {
     33   LARGE_INTEGER StreamSize;
     34   WCHAR cStreamName[MAX_PATH + 36];
     35 } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
     36 
     37 typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
     38     LPVOID findStreamData, DWORD flags);
     39 
     40 typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);
     41 
     42 EXTERN_C_END
     43 
     44 #endif
     45 
     46 namespace NWindows {
     47 namespace NFile {
     48 
     49 #ifdef SUPPORT_DEVICE_FILE
     50 namespace NSystem
     51 {
     52 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
     53 }
     54 #endif
     55 
     56 namespace NFind {
     57 
     58 bool CFileInfo::IsDots() const throw()
     59 {
     60   if (!IsDir() || Name.IsEmpty())
     61     return false;
     62   if (Name[0] != '.')
     63     return false;
     64   return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.');
     65 }
     66 
     67 #define WIN_FD_TO_MY_FI(fi, fd) \
     68   fi.Attrib = fd.dwFileAttributes; \
     69   fi.CTime = fd.ftCreationTime; \
     70   fi.ATime = fd.ftLastAccessTime; \
     71   fi.MTime = fd.ftLastWriteTime; \
     72   fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
     73   fi.IsAltStream = false; \
     74   fi.IsDevice = false;
     75 
     76   /*
     77   #ifdef UNDER_CE
     78   fi.ObjectID = fd.dwOID;
     79   #else
     80   fi.ReparseTag = fd.dwReserved0;
     81   #endif
     82   */
     83 
     84 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
     85 {
     86   WIN_FD_TO_MY_FI(fi, fd);
     87   fi.Name = us2fs(fd.cFileName);
     88   #if defined(_WIN32) && !defined(UNDER_CE)
     89   // fi.ShortName = us2fs(fd.cAlternateFileName);
     90   #endif
     91 }
     92 
     93 #ifndef _UNICODE
     94 
     95 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
     96 {
     97   WIN_FD_TO_MY_FI(fi, fd);
     98   fi.Name = fas2fs(fd.cFileName);
     99   #if defined(_WIN32) && !defined(UNDER_CE)
    100   // fi.ShortName = fas2fs(fd.cAlternateFileName);
    101   #endif
    102 }
    103 #endif
    104 
    105 ////////////////////////////////
    106 // CFindFile
    107 
    108 bool CFindFileBase::Close() throw()
    109 {
    110   if (_handle == INVALID_HANDLE_VALUE)
    111     return true;
    112   if (!::FindClose(_handle))
    113     return false;
    114   _handle = INVALID_HANDLE_VALUE;
    115   return true;
    116 }
    117 
    118 /*
    119 WinXP-64 FindFirstFile():
    120   ""      -  ERROR_PATH_NOT_FOUND
    121   folder\ -  ERROR_FILE_NOT_FOUND
    122   \       -  ERROR_FILE_NOT_FOUND
    123   c:\     -  ERROR_FILE_NOT_FOUND
    124   c:      -  ERROR_FILE_NOT_FOUND, if current dir is ROOT     ( c:\ )
    125   c:      -  OK,                   if current dir is NOT ROOT ( c:\folder )
    126   folder  -  OK
    127 
    128   \\               - ERROR_INVALID_NAME
    129   \\Server         - ERROR_INVALID_NAME
    130   \\Server\        - ERROR_INVALID_NAME
    131 
    132   \\Server\Share            - ERROR_BAD_NETPATH
    133   \\Server\Share            - ERROR_BAD_NET_NAME (Win7).
    134              !!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon),
    135                  when we call it for "\\Server\Share"
    136 
    137   \\Server\Share\           - ERROR_FILE_NOT_FOUND
    138 
    139   \\?\UNC\Server\Share      - ERROR_INVALID_NAME
    140   \\?\UNC\Server\Share      - ERROR_BAD_PATHNAME (Win7)
    141   \\?\UNC\Server\Share\     - ERROR_FILE_NOT_FOUND
    142 
    143   \\Server\Share_RootDrive  - ERROR_INVALID_NAME
    144   \\Server\Share_RootDrive\ - ERROR_INVALID_NAME
    145 
    146   c:\* - ERROR_FILE_NOT_FOUND, if thare are no item in that folder
    147 */
    148 
    149 bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
    150 {
    151   if (!Close())
    152     return false;
    153   #ifndef _UNICODE
    154   if (!g_IsNT)
    155   {
    156     WIN32_FIND_DATAA fd;
    157     _handle = ::FindFirstFileA(fs2fas(path), &fd);
    158     if (_handle == INVALID_HANDLE_VALUE)
    159       return false;
    160     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    161   }
    162   else
    163   #endif
    164   {
    165     WIN32_FIND_DATAW fd;
    166 
    167     IF_USE_MAIN_PATH
    168       _handle = ::FindFirstFileW(fs2us(path), &fd);
    169     #ifdef WIN_LONG_PATH
    170     if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
    171     {
    172       UString superPath;
    173       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    174         _handle = ::FindFirstFileW(superPath, &fd);
    175     }
    176     #endif
    177     if (_handle == INVALID_HANDLE_VALUE)
    178       return false;
    179     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    180   }
    181   return true;
    182 }
    183 
    184 bool CFindFile::FindNext(CFileInfo &fi)
    185 {
    186   #ifndef _UNICODE
    187   if (!g_IsNT)
    188   {
    189     WIN32_FIND_DATAA fd;
    190     if (!::FindNextFileA(_handle, &fd))
    191       return false;
    192     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    193   }
    194   else
    195   #endif
    196   {
    197     WIN32_FIND_DATAW fd;
    198     if (!::FindNextFileW(_handle, &fd))
    199       return false;
    200     Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
    201   }
    202   return true;
    203 }
    204 
    205 #if defined(_WIN32) && !defined(UNDER_CE)
    206 
    207 ////////////////////////////////
    208 // AltStreams
    209 
    210 static FindFirstStreamW_Ptr g_FindFirstStreamW;
    211 static FindNextStreamW_Ptr g_FindNextStreamW;
    212 
    213 struct CFindStreamLoader
    214 {
    215   CFindStreamLoader()
    216   {
    217     g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW");
    218     g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW");
    219   }
    220 } g_FindStreamLoader;
    221 
    222 bool CStreamInfo::IsMainStream() const throw()
    223 {
    224   return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
    225 };
    226 
    227 UString CStreamInfo::GetReducedName() const
    228 {
    229   // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA"
    230   UString s = Name;
    231   if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA"))
    232     s.DeleteFrom(s.Len() - 6);
    233   return s;
    234 }
    235 
    236 /*
    237 UString CStreamInfo::GetReducedName2() const
    238 {
    239   UString s = GetReducedName();
    240   if (!s.IsEmpty() && s[0] == ':')
    241     s.Delete(0);
    242   return s;
    243 }
    244 */
    245 
    246 static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
    247 {
    248   si.Size = sd.StreamSize.QuadPart;
    249   si.Name = sd.cStreamName;
    250 }
    251 
    252 /*
    253   WinXP-64 FindFirstStream():
    254   ""      -  ERROR_PATH_NOT_FOUND
    255   folder\ -  OK
    256   folder  -  OK
    257   \       -  OK
    258   c:\     -  OK
    259   c:      -  OK, if current dir is ROOT     ( c:\ )
    260   c:      -  OK, if current dir is NOT ROOT ( c:\folder )
    261   \\Server\Share   - OK
    262   \\Server\Share\  - OK
    263 
    264   \\               - ERROR_INVALID_NAME
    265   \\Server         - ERROR_INVALID_NAME
    266   \\Server\        - ERROR_INVALID_NAME
    267 */
    268 
    269 bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
    270 {
    271   if (!Close())
    272     return false;
    273   if (!g_FindFirstStreamW)
    274   {
    275     ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    276     return false;
    277   }
    278   {
    279     MY_WIN32_FIND_STREAM_DATA sd;
    280     SetLastError(0);
    281     IF_USE_MAIN_PATH
    282       _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
    283     if (_handle == INVALID_HANDLE_VALUE)
    284     {
    285       if (::GetLastError() == ERROR_HANDLE_EOF)
    286         return false;
    287       // long name can be tricky for path like ".\dirName".
    288       #ifdef WIN_LONG_PATH
    289       if (USE_SUPER_PATH)
    290       {
    291         UString superPath;
    292         if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    293           _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0);
    294       }
    295       #endif
    296     }
    297     if (_handle == INVALID_HANDLE_VALUE)
    298       return false;
    299     Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
    300   }
    301   return true;
    302 }
    303 
    304 bool CFindStream::FindNext(CStreamInfo &si)
    305 {
    306   if (!g_FindNextStreamW)
    307   {
    308     ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    309     return false;
    310   }
    311   {
    312     MY_WIN32_FIND_STREAM_DATA sd;
    313     if (!g_FindNextStreamW(_handle, &sd))
    314       return false;
    315     Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
    316   }
    317   return true;
    318 }
    319 
    320 bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
    321 {
    322   bool res;
    323   if (_find.IsHandleAllocated())
    324     res = _find.FindNext(si);
    325   else
    326     res = _find.FindFirst(_filePath, si);
    327   if (res)
    328   {
    329     found = true;
    330     return true;
    331   }
    332   found = false;
    333   return (::GetLastError() == ERROR_HANDLE_EOF);
    334 }
    335 
    336 #endif
    337 
    338 
    339 #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
    340 
    341 void CFileInfoBase::ClearBase() throw()
    342 {
    343   Size = 0;
    344   MY_CLEAR_FILETIME(CTime);
    345   MY_CLEAR_FILETIME(ATime);
    346   MY_CLEAR_FILETIME(MTime);
    347   Attrib = 0;
    348   IsAltStream = false;
    349   IsDevice = false;
    350 }
    351 
    352 /*
    353 WinXP-64 GetFileAttributes():
    354   If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code
    355 
    356   \    - OK
    357   C:\  - OK, if there is such drive,
    358   D:\  - ERROR_PATH_NOT_FOUND, if there is no such drive,
    359 
    360   C:\folder     - OK
    361   C:\folder\    - OK
    362   C:\folderBad  - ERROR_FILE_NOT_FOUND
    363 
    364   \\Server\BadShare  - ERROR_BAD_NETPATH
    365   \\Server\Share     - WORKS OK, but MSDN says:
    366                           GetFileAttributes for a network share, the function fails, and GetLastError
    367                           returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share.
    368 */
    369 
    370 DWORD GetFileAttrib(CFSTR path)
    371 {
    372   #ifndef _UNICODE
    373   if (!g_IsNT)
    374     return ::GetFileAttributes(fs2fas(path));
    375   else
    376   #endif
    377   {
    378     IF_USE_MAIN_PATH
    379     {
    380       DWORD dw = ::GetFileAttributesW(fs2us(path));
    381       if (dw != INVALID_FILE_ATTRIBUTES)
    382         return dw;
    383     }
    384     #ifdef WIN_LONG_PATH
    385     if (USE_SUPER_PATH)
    386     {
    387       UString superPath;
    388       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    389         return ::GetFileAttributesW(superPath);
    390     }
    391     #endif
    392     return INVALID_FILE_ATTRIBUTES;
    393   }
    394 }
    395 
    396 /* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk
    397    so instead of absolute path we have relative path in Name. That is not good in some calls */
    398 
    399 /* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */
    400 
    401 /* CFileInfo::Find()
    402 We alow the following paths (as FindFirstFile):
    403   C:\folder
    404   c:                      - if current dir is NOT ROOT ( c:\folder )
    405 
    406 also we support paths that are not supported by FindFirstFile:
    407   \
    408   \\.\c:
    409   c:\                     - Name will be without tail slash ( c: )
    410   \\?\c:\                 - Name will be without tail slash ( c: )
    411   \\Server\Share
    412   \\?\UNC\Server\Share
    413 
    414   c:\folder:stream  - Name = folder:stream
    415   c:\:stream        - Name = :stream
    416   c::stream         - Name = c::stream
    417 */
    418 
    419 bool CFileInfo::Find(CFSTR path)
    420 {
    421   #ifdef SUPPORT_DEVICE_FILE
    422   if (IsDevicePath(path))
    423   {
    424     ClearBase();
    425     Name = path + 4;
    426     IsDevice = true;
    427 
    428     if (NName::IsDrivePath2(path + 4) && path[6] == 0)
    429     {
    430       FChar drive[4] = { path[4], ':', '\\', 0 };
    431       UInt64 clusterSize, totalSize, freeSize;
    432       if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
    433       {
    434         Size = totalSize;
    435         return true;
    436       }
    437     }
    438 
    439     NIO::CInFile inFile;
    440     // ::OutputDebugStringW(path);
    441     if (!inFile.Open(path))
    442       return false;
    443     // ::OutputDebugStringW(L"---");
    444     if (inFile.SizeDefined)
    445       Size = inFile.Size;
    446     return true;
    447   }
    448   #endif
    449 
    450   #if defined(_WIN32) && !defined(UNDER_CE)
    451 
    452   int colonPos = FindAltStreamColon(path);
    453   if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0)
    454   {
    455     UString streamName = fs2us(path + (unsigned)colonPos);
    456     FString filePath = path;
    457     filePath.DeleteFrom(colonPos);
    458     /* we allow both cases:
    459       name:stream
    460       name:stream:$DATA
    461     */
    462     const unsigned kPostfixSize = 6;
    463     if (streamName.Len() <= kPostfixSize
    464         || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA"))
    465       streamName += L":$DATA";
    466 
    467     bool isOk = true;
    468 
    469     if (IsDrivePath2(filePath) &&
    470         (colonPos == 2 || colonPos == 3 && filePath[2] == '\\'))
    471     {
    472       // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT)
    473       ClearBase();
    474       Name.Empty();
    475       if (colonPos == 2)
    476         Name = filePath;
    477     }
    478     else
    479       isOk = Find(filePath);
    480 
    481     if (isOk)
    482     {
    483       Attrib &= ~(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);
    484       Size = 0;
    485       CStreamEnumerator enumerator(filePath);
    486       for (;;)
    487       {
    488         CStreamInfo si;
    489         bool found;
    490         if (!enumerator.Next(si, found))
    491           return false;
    492         if (!found)
    493         {
    494           ::SetLastError(ERROR_FILE_NOT_FOUND);
    495           return false;
    496         }
    497         if (si.Name.IsEqualTo_NoCase(streamName))
    498         {
    499           // we delete postfix, if alt stream name is not "::$DATA"
    500           if (si.Name.Len() > kPostfixSize + 1)
    501             si.Name.DeleteFrom(si.Name.Len() - kPostfixSize);
    502           Name += us2fs(si.Name);
    503           Size = si.Size;
    504           IsAltStream = true;
    505           return true;
    506         }
    507       }
    508     }
    509   }
    510 
    511   #endif
    512 
    513   CFindFile finder;
    514 
    515   #if defined(_WIN32) && !defined(UNDER_CE)
    516   {
    517     /*
    518     DWORD lastError = GetLastError();
    519     if (lastError == ERROR_FILE_NOT_FOUND
    520         || lastError == ERROR_BAD_NETPATH  // XP64: "\\Server\Share"
    521         || lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share"
    522         || lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share"
    523         || lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share"
    524         )
    525     */
    526 
    527     unsigned rootSize = 0;
    528     if (IsSuperPath(path))
    529       rootSize = kSuperPathPrefixSize;
    530 
    531     if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0)
    532     {
    533       DWORD attrib = GetFileAttrib(path);
    534       if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
    535       {
    536         ClearBase();
    537         Attrib = attrib;
    538         Name = path + rootSize;
    539         Name.DeleteFrom(2); // we don't need backslash (C:)
    540         return true;
    541       }
    542     }
    543     else if (IS_PATH_SEPAR(path[0]))
    544       if (path[1] == 0)
    545       {
    546         DWORD attrib = GetFileAttrib(path);
    547         if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
    548         {
    549           ClearBase();
    550           Name.Empty();
    551           Attrib = attrib;
    552           return true;
    553         }
    554       }
    555       else
    556       {
    557         const unsigned prefixSize = GetNetworkServerPrefixSize(path);
    558         if (prefixSize > 0 && path[prefixSize] != 0)
    559         {
    560           if (NName::FindSepar(path + prefixSize) < 0)
    561           {
    562             FString s = path;
    563             s.Add_PathSepar();
    564             s += FCHAR_ANY_MASK;
    565 
    566             bool isOK = false;
    567             if (finder.FindFirst(s, *this))
    568             {
    569               if (Name == FTEXT("."))
    570               {
    571                 Name = path + prefixSize;
    572                 return true;
    573               }
    574               isOK = true;
    575               /* if "\\server\share" maps to root folder "d:\", there is no "." item.
    576                  But it's possible that there are another items */
    577             }
    578             {
    579               DWORD attrib = GetFileAttrib(path);
    580               if (isOK || attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
    581               {
    582                 ClearBase();
    583                 if (attrib != INVALID_FILE_ATTRIBUTES)
    584                   Attrib = attrib;
    585                 else
    586                   SetAsDir();
    587                 Name = path + prefixSize;
    588                 return true;
    589               }
    590             }
    591             // ::SetLastError(lastError);
    592           }
    593         }
    594       }
    595   }
    596   #endif
    597 
    598   return finder.FindFirst(path, *this);
    599 }
    600 
    601 
    602 bool DoesFileExist(CFSTR name)
    603 {
    604   CFileInfo fi;
    605   return fi.Find(name) && !fi.IsDir();
    606 }
    607 
    608 bool DoesDirExist(CFSTR name)
    609 {
    610   CFileInfo fi;
    611   return fi.Find(name) && fi.IsDir();
    612 }
    613 
    614 bool DoesFileOrDirExist(CFSTR name)
    615 {
    616   CFileInfo fi;
    617   return fi.Find(name);
    618 }
    619 
    620 
    621 bool CEnumerator::NextAny(CFileInfo &fi)
    622 {
    623   if (_findFile.IsHandleAllocated())
    624     return _findFile.FindNext(fi);
    625   else
    626     return _findFile.FindFirst(_wildcard, fi);
    627 }
    628 
    629 bool CEnumerator::Next(CFileInfo &fi)
    630 {
    631   for (;;)
    632   {
    633     if (!NextAny(fi))
    634       return false;
    635     if (!fi.IsDots())
    636       return true;
    637   }
    638 }
    639 
    640 bool CEnumerator::Next(CFileInfo &fi, bool &found)
    641 {
    642   if (Next(fi))
    643   {
    644     found = true;
    645     return true;
    646   }
    647   found = false;
    648   return (::GetLastError() == ERROR_NO_MORE_FILES);
    649 }
    650 
    651 ////////////////////////////////
    652 // CFindChangeNotification
    653 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
    654 
    655 bool CFindChangeNotification::Close() throw()
    656 {
    657   if (!IsHandleAllocated())
    658     return true;
    659   if (!::FindCloseChangeNotification(_handle))
    660     return false;
    661   _handle = INVALID_HANDLE_VALUE;
    662   return true;
    663 }
    664 
    665 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
    666 {
    667   #ifndef _UNICODE
    668   if (!g_IsNT)
    669     _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
    670   else
    671   #endif
    672   {
    673     IF_USE_MAIN_PATH
    674     _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
    675     #ifdef WIN_LONG_PATH
    676     if (!IsHandleAllocated())
    677     {
    678       UString superPath;
    679       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    680         _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);
    681     }
    682     #endif
    683   }
    684   return _handle;
    685 }
    686 
    687 #ifndef UNDER_CE
    688 
    689 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
    690 {
    691   driveStrings.Clear();
    692   #ifndef _UNICODE
    693   if (!g_IsNT)
    694   {
    695     driveStrings.Clear();
    696     UINT32 size = GetLogicalDriveStrings(0, NULL);
    697     if (size == 0)
    698       return false;
    699     CObjArray<char> buf(size);
    700     UINT32 newSize = GetLogicalDriveStrings(size, buf);
    701     if (newSize == 0 || newSize > size)
    702       return false;
    703     AString s;
    704     UINT32 prev = 0;
    705     for (UINT32 i = 0; i < newSize; i++)
    706     {
    707       if (buf[i] == 0)
    708       {
    709         s = buf + prev;
    710         prev = i + 1;
    711         driveStrings.Add(fas2fs(s));
    712       }
    713     }
    714     return prev == newSize;
    715   }
    716   else
    717   #endif
    718   {
    719     UINT32 size = GetLogicalDriveStringsW(0, NULL);
    720     if (size == 0)
    721       return false;
    722     CObjArray<wchar_t> buf(size);
    723     UINT32 newSize = GetLogicalDriveStringsW(size, buf);
    724     if (newSize == 0 || newSize > size)
    725       return false;
    726     UString s;
    727     UINT32 prev = 0;
    728     for (UINT32 i = 0; i < newSize; i++)
    729     {
    730       if (buf[i] == 0)
    731       {
    732         s = buf + prev;
    733         prev = i + 1;
    734         driveStrings.Add(us2fs(s));
    735       }
    736     }
    737     return prev == newSize;
    738   }
    739 }
    740 
    741 #endif
    742 
    743 }}}
    744