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
13extern bool g_IsNT;
14#endif
15
16using namespace NWindows;
17using namespace NFile;
18using namespace NName;
19
20#if defined(_WIN32) && !defined(UNDER_CE)
21
22EXTERN_C_BEGIN
23
24typedef enum
25{
26  My_FindStreamInfoStandard,
27  My_FindStreamInfoMaxInfoLevel
28} MY_STREAM_INFO_LEVELS;
29
30typedef struct
31{
32  LARGE_INTEGER StreamSize;
33  WCHAR cStreamName[MAX_PATH + 36];
34} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
35
36typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
37    LPVOID findStreamData, DWORD flags);
38
39typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);
40
41EXTERN_C_END
42
43#endif
44
45namespace NWindows {
46namespace NFile {
47
48#ifdef SUPPORT_DEVICE_FILE
49namespace NSystem
50{
51bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
52}
53#endif
54
55namespace NFind {
56
57bool 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
83static 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
94static 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
107bool 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
117bool 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
152bool 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
178static FindFirstStreamW_Ptr g_FindFirstStreamW;
179static FindNextStreamW_Ptr g_FindNextStreamW;
180
181struct 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
190bool CStreamInfo::IsMainStream() const throw()
191{
192  return Name == L"::$DATA";
193};
194
195UString 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
204static 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
210bool 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
244bool 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
260bool 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
281void 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
294static 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
318bool 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
438bool DoesFileExist(CFSTR name)
439{
440  CFileInfo fi;
441  return fi.Find(name) && !fi.IsDir();
442}
443
444bool DoesDirExist(CFSTR name)
445{
446  CFileInfo fi;
447  return fi.Find(name) && fi.IsDir();
448}
449bool DoesFileOrDirExist(CFSTR name)
450{
451  CFileInfo fi;
452  return fi.Find(name);
453}
454
455bool CEnumerator::NextAny(CFileInfo &fi)
456{
457  if (_findFile.IsHandleAllocated())
458    return _findFile.FindNext(fi);
459  else
460    return _findFile.FindFirst(_wildcard, fi);
461}
462
463bool 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
474bool 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
489bool 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
499HANDLE 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
523bool 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