tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FileFind.cpp (19034B)


      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 += ":$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 += '*'; // CHAR_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 void CEnumerator::SetDirPrefix(const FString &dirPrefix)
    622 {
    623  _wildcard = dirPrefix;
    624  _wildcard += '*';
    625 }
    626 
    627 bool CEnumerator::NextAny(CFileInfo &fi)
    628 {
    629  if (_findFile.IsHandleAllocated())
    630    return _findFile.FindNext(fi);
    631  else
    632    return _findFile.FindFirst(_wildcard, fi);
    633 }
    634 
    635 bool CEnumerator::Next(CFileInfo &fi)
    636 {
    637  for (;;)
    638  {
    639    if (!NextAny(fi))
    640      return false;
    641    if (!fi.IsDots())
    642      return true;
    643  }
    644 }
    645 
    646 bool CEnumerator::Next(CFileInfo &fi, bool &found)
    647 {
    648  if (Next(fi))
    649  {
    650    found = true;
    651    return true;
    652  }
    653  found = false;
    654  return (::GetLastError() == ERROR_NO_MORE_FILES);
    655 }
    656 
    657 ////////////////////////////////
    658 // CFindChangeNotification
    659 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
    660 
    661 bool CFindChangeNotification::Close() throw()
    662 {
    663  if (!IsHandleAllocated())
    664    return true;
    665  if (!::FindCloseChangeNotification(_handle))
    666    return false;
    667  _handle = INVALID_HANDLE_VALUE;
    668  return true;
    669 }
    670           
    671 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
    672 {
    673  #ifndef _UNICODE
    674  if (!g_IsNT)
    675    _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
    676  else
    677  #endif
    678  {
    679    IF_USE_MAIN_PATH
    680    _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
    681    #ifdef WIN_LONG_PATH
    682    if (!IsHandleAllocated())
    683    {
    684      UString superPath;
    685      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    686        _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);
    687    }
    688    #endif
    689  }
    690  return _handle;
    691 }
    692 
    693 #ifndef UNDER_CE
    694 
    695 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
    696 {
    697  driveStrings.Clear();
    698  #ifndef _UNICODE
    699  if (!g_IsNT)
    700  {
    701    driveStrings.Clear();
    702    UINT32 size = GetLogicalDriveStrings(0, NULL);
    703    if (size == 0)
    704      return false;
    705    CObjArray<char> buf(size);
    706    UINT32 newSize = GetLogicalDriveStrings(size, buf);
    707    if (newSize == 0 || newSize > size)
    708      return false;
    709    AString s;
    710    UINT32 prev = 0;
    711    for (UINT32 i = 0; i < newSize; i++)
    712    {
    713      if (buf[i] == 0)
    714      {
    715        s = buf + prev;
    716        prev = i + 1;
    717        driveStrings.Add(fas2fs(s));
    718      }
    719    }
    720    return prev == newSize;
    721  }
    722  else
    723  #endif
    724  {
    725    UINT32 size = GetLogicalDriveStringsW(0, NULL);
    726    if (size == 0)
    727      return false;
    728    CObjArray<wchar_t> buf(size);
    729    UINT32 newSize = GetLogicalDriveStringsW(size, buf);
    730    if (newSize == 0 || newSize > size)
    731      return false;
    732    UString s;
    733    UINT32 prev = 0;
    734    for (UINT32 i = 0; i < newSize; i++)
    735    {
    736      if (buf[i] == 0)
    737      {
    738        s = buf + prev;
    739        prev = i + 1;
    740        driveStrings.Add(us2fs(s));
    741      }
    742    }
    743    return prev == newSize;
    744  }
    745 }
    746 
    747 #endif
    748 
    749 }}}