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 
IsDots() const57 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 
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW & fd,CFileInfo & fi)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 
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA & fd,CFileInfo & fi)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 
Close()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 
FindFirst(CFSTR path,CFileInfo & fi)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 
FindNext(CFileInfo & fi)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 {
CFindStreamLoaderNWindows::NFile::NFind::CFindStreamLoader183   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 
IsMainStream() const190 bool CStreamInfo::IsMainStream() const throw()
191 {
192   return Name == L"::$DATA";
193 };
194 
GetReducedName() const195 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 
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA & sd,CStreamInfo & si)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 
FindFirst(CFSTR path,CStreamInfo & si)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 
FindNext(CStreamInfo & si)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 
Next(CStreamInfo & si,bool & found)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 
Clear()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 
FindAltStreamColon(CFSTR path)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 
Find(CFSTR path)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 
DoesFileExist(CFSTR name)438 bool DoesFileExist(CFSTR name)
439 {
440   CFileInfo fi;
441   return fi.Find(name) && !fi.IsDir();
442 }
443 
DoesDirExist(CFSTR name)444 bool DoesDirExist(CFSTR name)
445 {
446   CFileInfo fi;
447   return fi.Find(name) && fi.IsDir();
448 }
DoesFileOrDirExist(CFSTR name)449 bool DoesFileOrDirExist(CFSTR name)
450 {
451   CFileInfo fi;
452   return fi.Find(name);
453 }
454 
NextAny(CFileInfo & fi)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 
Next(CFileInfo & fi)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 
Next(CFileInfo & fi,bool & found)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 
Close()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 
FindFirst(CFSTR path,bool watchSubtree,DWORD notifyFilter)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 
MyGetLogicalDriveStrings(CObjectVector<FString> & driveStrings)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