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 
IsDots() const58 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 
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW & fd,CFileInfo & fi)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 
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA & fd,CFileInfo & fi)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 
Close()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 
FindFirst(CFSTR path,CFileInfo & fi)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 
FindNext(CFileInfo & fi)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 {
CFindStreamLoaderNWindows::NFile::NFind::CFindStreamLoader215   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 
IsMainStream() const222 bool CStreamInfo::IsMainStream() const throw()
223 {
224   return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
225 };
226 
GetReducedName() const227 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 
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA & sd,CStreamInfo & si)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 
FindFirst(CFSTR path,CStreamInfo & si)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 
FindNext(CStreamInfo & si)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 
Next(CStreamInfo & si,bool & found)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 
ClearBase()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 
GetFileAttrib(CFSTR path)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 
Find(CFSTR path)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 
DoesFileExist(CFSTR name)602 bool DoesFileExist(CFSTR name)
603 {
604   CFileInfo fi;
605   return fi.Find(name) && !fi.IsDir();
606 }
607 
DoesDirExist(CFSTR name)608 bool DoesDirExist(CFSTR name)
609 {
610   CFileInfo fi;
611   return fi.Find(name) && fi.IsDir();
612 }
613 
DoesFileOrDirExist(CFSTR name)614 bool DoesFileOrDirExist(CFSTR name)
615 {
616   CFileInfo fi;
617   return fi.Find(name);
618 }
619 
620 
SetDirPrefix(const FString & dirPrefix)621 void CEnumerator::SetDirPrefix(const FString &dirPrefix)
622 {
623   _wildcard = dirPrefix;
624   _wildcard += '*';
625 }
626 
NextAny(CFileInfo & fi)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 
Next(CFileInfo & fi)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 
Next(CFileInfo & fi,bool & found)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 
Close()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 
FindFirst(CFSTR path,bool watchSubtree,DWORD notifyFilter)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 
MyGetLogicalDriveStrings(CObjectVector<FString> & driveStrings)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 }}}
750