xref: /third_party/lzma/CPP/Windows/FileFind.cpp (revision 370b324c)
1// Windows/FileFind.cpp
2
3#include "StdAfx.h"
4
5// #include <stdio.h>
6
7#ifndef _WIN32
8#include <fcntl.h>           /* Definition of AT_* constants */
9#include "TimeUtils.h"
10// for major
11// #include <sys/sysmacros.h>
12#endif
13
14#include "FileFind.h"
15#include "FileIO.h"
16#include "FileName.h"
17
18#ifndef _UNICODE
19extern bool g_IsNT;
20#endif
21
22using namespace NWindows;
23using namespace NFile;
24using namespace NName;
25
26#if defined(_WIN32) && !defined(UNDER_CE)
27
28EXTERN_C_BEGIN
29
30typedef enum
31{
32  My_FindStreamInfoStandard,
33  My_FindStreamInfoMaxInfoLevel
34} MY_STREAM_INFO_LEVELS;
35
36typedef struct
37{
38  LARGE_INTEGER StreamSize;
39  WCHAR cStreamName[MAX_PATH + 36];
40} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
41
42typedef HANDLE (WINAPI *Func_FindFirstStreamW)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
43    LPVOID findStreamData, DWORD flags);
44
45typedef BOOL (APIENTRY *Func_FindNextStreamW)(HANDLE findStream, LPVOID findStreamData);
46
47EXTERN_C_END
48
49#endif // defined(_WIN32) && !defined(UNDER_CE)
50
51
52namespace NWindows {
53namespace NFile {
54
55
56#ifdef _WIN32
57#ifdef Z7_DEVICE_FILE
58namespace NSystem
59{
60bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
61}
62#endif
63#endif
64
65namespace NFind {
66
67/*
68#ifdef _WIN32
69#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
70#else
71#define MY_CLEAR_FILETIME(ft) ft.tv_sec = 0;  ft.tv_nsec = 0;
72#endif
73*/
74
75void CFileInfoBase::ClearBase() throw()
76{
77  Size = 0;
78  FiTime_Clear(CTime);
79  FiTime_Clear(ATime);
80  FiTime_Clear(MTime);
81
82 #ifdef _WIN32
83  Attrib = 0;
84  // ReparseTag = 0;
85  IsAltStream = false;
86  IsDevice = false;
87 #else
88  dev = 0;
89  ino = 0;
90  mode = 0;
91  nlink = 0;
92  uid = 0;
93  gid = 0;
94  rdev = 0;
95 #endif
96}
97
98bool CFileInfo::IsDots() const throw()
99{
100  if (!IsDir() || Name.IsEmpty())
101    return false;
102  if (Name[0] != '.')
103    return false;
104  return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.');
105}
106
107
108#ifdef _WIN32
109
110
111#define WIN_FD_TO_MY_FI(fi, fd) \
112  fi.Attrib = fd.dwFileAttributes; \
113  fi.CTime = fd.ftCreationTime; \
114  fi.ATime = fd.ftLastAccessTime; \
115  fi.MTime = fd.ftLastWriteTime; \
116  fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
117  /* fi.ReparseTag = fd.dwReserved0; */ \
118  fi.IsAltStream = false; \
119  fi.IsDevice = false;
120
121  /*
122  #ifdef UNDER_CE
123  fi.ObjectID = fd.dwOID;
124  #else
125  fi.ReparseTag = fd.dwReserved0;
126  #endif
127  */
128
129static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
130{
131  WIN_FD_TO_MY_FI(fi, fd)
132  fi.Name = us2fs(fd.cFileName);
133  #if defined(_WIN32) && !defined(UNDER_CE)
134  // fi.ShortName = us2fs(fd.cAlternateFileName);
135  #endif
136}
137
138#ifndef _UNICODE
139static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
140{
141  WIN_FD_TO_MY_FI(fi, fd)
142  fi.Name = fas2fs(fd.cFileName);
143  #if defined(_WIN32) && !defined(UNDER_CE)
144  // fi.ShortName = fas2fs(fd.cAlternateFileName);
145  #endif
146}
147#endif
148
149////////////////////////////////
150// CFindFile
151
152bool CFindFileBase::Close() throw()
153{
154  if (_handle == INVALID_HANDLE_VALUE)
155    return true;
156  if (!::FindClose(_handle))
157    return false;
158  _handle = INVALID_HANDLE_VALUE;
159  return true;
160}
161
162/*
163WinXP-64 FindFirstFile():
164  ""      -  ERROR_PATH_NOT_FOUND
165  folder\ -  ERROR_FILE_NOT_FOUND
166  \       -  ERROR_FILE_NOT_FOUND
167  c:\     -  ERROR_FILE_NOT_FOUND
168  c:      -  ERROR_FILE_NOT_FOUND, if current dir is ROOT     ( c:\ )
169  c:      -  OK,                   if current dir is NOT ROOT ( c:\folder )
170  folder  -  OK
171
172  \\               - ERROR_INVALID_NAME
173  \\Server         - ERROR_INVALID_NAME
174  \\Server\        - ERROR_INVALID_NAME
175
176  \\Server\Share            - ERROR_BAD_NETPATH
177  \\Server\Share            - ERROR_BAD_NET_NAME (Win7).
178             !!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon),
179                 when we call it for "\\Server\Share"
180
181  \\Server\Share\           - ERROR_FILE_NOT_FOUND
182
183  \\?\UNC\Server\Share      - ERROR_INVALID_NAME
184  \\?\UNC\Server\Share      - ERROR_BAD_PATHNAME (Win7)
185  \\?\UNC\Server\Share\     - ERROR_FILE_NOT_FOUND
186
187  \\Server\Share_RootDrive  - ERROR_INVALID_NAME
188  \\Server\Share_RootDrive\ - ERROR_INVALID_NAME
189
190  e:\* - ERROR_FILE_NOT_FOUND, if there are no items in that root folder
191  w:\* - ERROR_PATH_NOT_FOUND, if there is no such drive w:
192*/
193
194bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
195{
196  if (!Close())
197    return false;
198  #ifndef _UNICODE
199  if (!g_IsNT)
200  {
201    WIN32_FIND_DATAA fd;
202    _handle = ::FindFirstFileA(fs2fas(path), &fd);
203    if (_handle == INVALID_HANDLE_VALUE)
204      return false;
205    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
206  }
207  else
208  #endif
209  {
210    WIN32_FIND_DATAW fd;
211
212    IF_USE_MAIN_PATH
213      _handle = ::FindFirstFileW(fs2us(path), &fd);
214    #ifdef Z7_LONG_PATH
215    if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
216    {
217      UString superPath;
218      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
219        _handle = ::FindFirstFileW(superPath, &fd);
220    }
221    #endif
222    if (_handle == INVALID_HANDLE_VALUE)
223      return false;
224    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
225  }
226  return true;
227}
228
229bool CFindFile::FindNext(CFileInfo &fi)
230{
231  #ifndef _UNICODE
232  if (!g_IsNT)
233  {
234    WIN32_FIND_DATAA fd;
235    if (!::FindNextFileA(_handle, &fd))
236      return false;
237    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
238  }
239  else
240  #endif
241  {
242    WIN32_FIND_DATAW fd;
243    if (!::FindNextFileW(_handle, &fd))
244      return false;
245    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
246  }
247  return true;
248}
249
250#if defined(_WIN32) && !defined(UNDER_CE)
251
252////////////////////////////////
253// AltStreams
254
255static Func_FindFirstStreamW g_FindFirstStreamW;
256static Func_FindNextStreamW g_FindNextStreamW;
257
258static struct CFindStreamLoader
259{
260  CFindStreamLoader()
261  {
262    const HMODULE hm = ::GetModuleHandleA("kernel32.dll");
263       g_FindFirstStreamW = Z7_GET_PROC_ADDRESS(
264    Func_FindFirstStreamW, hm,
265        "FindFirstStreamW");
266       g_FindNextStreamW = Z7_GET_PROC_ADDRESS(
267    Func_FindNextStreamW, hm,
268        "FindNextStreamW");
269  }
270} g_FindStreamLoader;
271
272bool CStreamInfo::IsMainStream() const throw()
273{
274  return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
275}
276
277UString CStreamInfo::GetReducedName() const
278{
279  // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA"
280  UString s (Name);
281  if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA"))
282    s.DeleteFrom(s.Len() - 6);
283  return s;
284}
285
286/*
287UString CStreamInfo::GetReducedName2() const
288{
289  UString s = GetReducedName();
290  if (!s.IsEmpty() && s[0] == ':')
291    s.Delete(0);
292  return s;
293}
294*/
295
296static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
297{
298  si.Size = (UInt64)sd.StreamSize.QuadPart;
299  si.Name = sd.cStreamName;
300}
301
302/*
303  WinXP-64 FindFirstStream():
304  ""      -  ERROR_PATH_NOT_FOUND
305  folder\ -  OK
306  folder  -  OK
307  \       -  OK
308  c:\     -  OK
309  c:      -  OK, if current dir is ROOT     ( c:\ )
310  c:      -  OK, if current dir is NOT ROOT ( c:\folder )
311  \\Server\Share   - OK
312  \\Server\Share\  - OK
313
314  \\               - ERROR_INVALID_NAME
315  \\Server         - ERROR_INVALID_NAME
316  \\Server\        - ERROR_INVALID_NAME
317*/
318
319bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
320{
321  if (!Close())
322    return false;
323  if (!g_FindFirstStreamW)
324  {
325    ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
326    return false;
327  }
328  {
329    MY_WIN32_FIND_STREAM_DATA sd;
330    SetLastError(0);
331    IF_USE_MAIN_PATH
332      _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
333    if (_handle == INVALID_HANDLE_VALUE)
334    {
335      if (::GetLastError() == ERROR_HANDLE_EOF)
336        return false;
337      // long name can be tricky for path like ".\dirName".
338      #ifdef Z7_LONG_PATH
339      if (USE_SUPER_PATH)
340      {
341        UString superPath;
342        if (GetSuperPath(path, superPath, USE_MAIN_PATH))
343          _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0);
344      }
345      #endif
346    }
347    if (_handle == INVALID_HANDLE_VALUE)
348      return false;
349    Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
350  }
351  return true;
352}
353
354bool CFindStream::FindNext(CStreamInfo &si)
355{
356  if (!g_FindNextStreamW)
357  {
358    ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
359    return false;
360  }
361  {
362    MY_WIN32_FIND_STREAM_DATA sd;
363    if (!g_FindNextStreamW(_handle, &sd))
364      return false;
365    Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
366  }
367  return true;
368}
369
370bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
371{
372  bool res;
373  if (_find.IsHandleAllocated())
374    res = _find.FindNext(si);
375  else
376    res = _find.FindFirst(_filePath, si);
377  if (res)
378  {
379    found = true;
380    return true;
381  }
382  found = false;
383  return (::GetLastError() == ERROR_HANDLE_EOF);
384}
385
386#endif
387
388
389/*
390WinXP-64 GetFileAttributes():
391  If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code
392
393  \    - OK
394  C:\  - OK, if there is such drive,
395  D:\  - ERROR_PATH_NOT_FOUND, if there is no such drive,
396
397  C:\folder     - OK
398  C:\folder\    - OK
399  C:\folderBad  - ERROR_FILE_NOT_FOUND
400
401  \\Server\BadShare  - ERROR_BAD_NETPATH
402  \\Server\Share     - WORKS OK, but MSDN says:
403                          GetFileAttributes for a network share, the function fails, and GetLastError
404                          returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share.
405*/
406
407DWORD GetFileAttrib(CFSTR path)
408{
409  #ifndef _UNICODE
410  if (!g_IsNT)
411    return ::GetFileAttributes(fs2fas(path));
412  else
413  #endif
414  {
415    IF_USE_MAIN_PATH
416    {
417      DWORD dw = ::GetFileAttributesW(fs2us(path));
418      if (dw != INVALID_FILE_ATTRIBUTES)
419        return dw;
420    }
421    #ifdef Z7_LONG_PATH
422    if (USE_SUPER_PATH)
423    {
424      UString superPath;
425      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
426        return ::GetFileAttributesW(superPath);
427    }
428    #endif
429    return INVALID_FILE_ATTRIBUTES;
430  }
431}
432
433/* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk
434   so instead of absolute path we have relative path in Name. That is not good in some calls */
435
436/* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */
437
438/* CFileInfo::Find()
439We alow the following paths (as FindFirstFile):
440  C:\folder
441  c:                      - if current dir is NOT ROOT ( c:\folder )
442
443also we support paths that are not supported by FindFirstFile:
444  \
445  \\.\c:
446  c:\                     - Name will be without tail slash ( c: )
447  \\?\c:\                 - Name will be without tail slash ( c: )
448  \\Server\Share
449  \\?\UNC\Server\Share
450
451  c:\folder:stream  - Name = folder:stream
452  c:\:stream        - Name = :stream
453  c::stream         - Name = c::stream
454*/
455
456bool CFileInfo::Find(CFSTR path, bool followLink)
457{
458  #ifdef Z7_DEVICE_FILE
459
460  if (IS_PATH_SEPAR(path[0]) &&
461      IS_PATH_SEPAR(path[1]) &&
462      path[2] == '.' &&
463      path[3] == 0)
464  {
465    // 22.00 : it's virtual directory for devices
466    // IsDevice = true;
467    ClearBase();
468    Name = path + 2;
469    Attrib = FILE_ATTRIBUTE_DIRECTORY;
470    return true;
471  }
472
473  if (IsDevicePath(path))
474  {
475    ClearBase();
476    Name = path + 4;
477    IsDevice = true;
478
479    if (NName::IsDrivePath2(path + 4) && path[6] == 0)
480    {
481      FChar drive[4] = { path[4], ':', '\\', 0 };
482      UInt64 clusterSize, totalSize, freeSize;
483      if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
484      {
485        Size = totalSize;
486        return true;
487      }
488    }
489
490    NIO::CInFile inFile;
491    // ::OutputDebugStringW(path);
492    if (!inFile.Open(path))
493      return false;
494    // ::OutputDebugStringW(L"---");
495    if (inFile.SizeDefined)
496      Size = inFile.Size;
497    return true;
498  }
499  #endif
500
501  #if defined(_WIN32) && !defined(UNDER_CE)
502
503  const int colonPos = FindAltStreamColon(path);
504  if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0)
505  {
506    UString streamName = fs2us(path + (unsigned)colonPos);
507    FString filePath (path);
508    filePath.DeleteFrom((unsigned)colonPos);
509    /* we allow both cases:
510      name:stream
511      name:stream:$DATA
512    */
513    const unsigned kPostfixSize = 6;
514    if (streamName.Len() <= kPostfixSize
515        || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA"))
516      streamName += ":$DATA";
517
518    bool isOk = true;
519
520    if (IsDrivePath2(filePath) &&
521        (colonPos == 2 || (colonPos == 3 && filePath[2] == '\\')))
522    {
523      // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT)
524      ClearBase();
525      Name.Empty();
526      if (colonPos == 2)
527        Name = filePath;
528    }
529    else
530      isOk = Find(filePath, followLink); // check it (followLink)
531
532    if (isOk)
533    {
534      Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);
535      Size = 0;
536      CStreamEnumerator enumerator(filePath);
537      for (;;)
538      {
539        CStreamInfo si;
540        bool found;
541        if (!enumerator.Next(si, found))
542          return false;
543        if (!found)
544        {
545          ::SetLastError(ERROR_FILE_NOT_FOUND);
546          return false;
547        }
548        if (si.Name.IsEqualTo_NoCase(streamName))
549        {
550          // we delete postfix, if alt stream name is not "::$DATA"
551          if (si.Name.Len() > kPostfixSize + 1)
552            si.Name.DeleteFrom(si.Name.Len() - kPostfixSize);
553          Name += us2fs(si.Name);
554          Size = si.Size;
555          IsAltStream = true;
556          return true;
557        }
558      }
559    }
560  }
561
562  #endif
563
564  CFindFile finder;
565
566  #if defined(_WIN32) && !defined(UNDER_CE)
567  {
568    /*
569    DWORD lastError = GetLastError();
570    if (lastError == ERROR_FILE_NOT_FOUND
571        || lastError == ERROR_BAD_NETPATH  // XP64: "\\Server\Share"
572        || lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share"
573        || lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share"
574        || lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share"
575        )
576    */
577
578    unsigned rootSize = 0;
579    if (IsSuperPath(path))
580      rootSize = kSuperPathPrefixSize;
581
582    if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0)
583    {
584      DWORD attrib = GetFileAttrib(path);
585      if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
586      {
587        ClearBase();
588        Attrib = attrib;
589        Name = path + rootSize;
590        Name.DeleteFrom(2);
591        if (!Fill_From_ByHandleFileInfo(path))
592        {
593        }
594        return true;
595      }
596    }
597    else if (IS_PATH_SEPAR(path[0]))
598    {
599      if (path[1] == 0)
600      {
601        DWORD attrib = GetFileAttrib(path);
602        if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
603        {
604          ClearBase();
605          Name.Empty();
606          Attrib = attrib;
607          return true;
608        }
609      }
610      else
611      {
612        const unsigned prefixSize = GetNetworkServerPrefixSize(path);
613        if (prefixSize > 0 && path[prefixSize] != 0)
614        {
615          if (NName::FindSepar(path + prefixSize) < 0)
616          {
617            if (Fill_From_ByHandleFileInfo(path))
618            {
619              Name = path + prefixSize;
620              return true;
621            }
622
623            FString s (path);
624            s.Add_PathSepar();
625            s += '*'; // CHAR_ANY_MASK
626            bool isOK = false;
627            if (finder.FindFirst(s, *this))
628            {
629              if (Name == FTEXT("."))
630              {
631                Name = path + prefixSize;
632                return true;
633              }
634              isOK = true;
635              /* if "\\server\share" maps to root folder "d:\", there is no "." item.
636                 But it's possible that there are another items */
637            }
638            {
639              DWORD attrib = GetFileAttrib(path);
640              if (isOK || (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0))
641              {
642                ClearBase();
643                if (attrib != INVALID_FILE_ATTRIBUTES)
644                  Attrib = attrib;
645                else
646                  SetAsDir();
647                Name = path + prefixSize;
648                return true;
649              }
650            }
651            // ::SetLastError(lastError);
652          }
653        }
654      }
655    }
656  }
657  #endif
658
659  bool res = finder.FindFirst(path, *this);
660  if (!followLink
661      || !res
662      || !HasReparsePoint())
663    return res;
664
665  // return FollowReparse(path, IsDir());
666  return Fill_From_ByHandleFileInfo(path);
667}
668
669bool CFileInfoBase::Fill_From_ByHandleFileInfo(CFSTR path)
670{
671  BY_HANDLE_FILE_INFORMATION info;
672  if (!NIO::CFileBase::GetFileInformation(path, &info))
673    return false;
674  {
675    Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
676    CTime = info.ftCreationTime;
677    ATime = info.ftLastAccessTime;
678    MTime = info.ftLastWriteTime;
679    Attrib = info.dwFileAttributes;
680    return true;
681  }
682}
683
684/*
685bool CFileInfo::FollowReparse(CFSTR path, bool isDir)
686{
687  if (isDir)
688  {
689    FString prefix = path;
690    prefix.Add_PathSepar();
691
692    // "folder/." refers to folder itself. So we can't use that path
693    // we must use enumerator and search "." item
694    CEnumerator enumerator;
695    enumerator.SetDirPrefix(prefix);
696    for (;;)
697    {
698      CFileInfo fi;
699      if (!enumerator.NextAny(fi))
700        break;
701      if (fi.Name.IsEqualTo_Ascii_NoCase("."))
702      {
703        // we can copy preperies;
704        CTime = fi.CTime;
705        ATime = fi.ATime;
706        MTime = fi.MTime;
707        Attrib = fi.Attrib;
708        Size = fi.Size;
709        return true;
710      }
711      break;
712    }
713    // LastError(lastError);
714    return false;
715  }
716
717  {
718    NIO::CInFile inFile;
719    if (inFile.Open(path))
720    {
721      BY_HANDLE_FILE_INFORMATION info;
722      if (inFile.GetFileInformation(&info))
723      {
724        ClearBase();
725        Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
726        CTime = info.ftCreationTime;
727        ATime = info.ftLastAccessTime;
728        MTime = info.ftLastWriteTime;
729        Attrib = info.dwFileAttributes;
730        return true;
731      }
732    }
733    return false;
734  }
735}
736*/
737
738bool DoesFileExist_Raw(CFSTR name)
739{
740  CFileInfo fi;
741  return fi.Find(name) && !fi.IsDir();
742}
743
744bool DoesFileExist_FollowLink(CFSTR name)
745{
746  CFileInfo fi;
747  return fi.Find_FollowLink(name) && !fi.IsDir();
748}
749
750bool DoesDirExist(CFSTR name, bool followLink)
751{
752  CFileInfo fi;
753  return fi.Find(name, followLink) && fi.IsDir();
754}
755
756bool DoesFileOrDirExist(CFSTR name)
757{
758  CFileInfo fi;
759  return fi.Find(name);
760}
761
762
763void CEnumerator::SetDirPrefix(const FString &dirPrefix)
764{
765  _wildcard = dirPrefix;
766  _wildcard += '*';
767}
768
769bool CEnumerator::NextAny(CFileInfo &fi)
770{
771  if (_findFile.IsHandleAllocated())
772    return _findFile.FindNext(fi);
773  else
774    return _findFile.FindFirst(_wildcard, fi);
775}
776
777bool CEnumerator::Next(CFileInfo &fi)
778{
779  for (;;)
780  {
781    if (!NextAny(fi))
782      return false;
783    if (!fi.IsDots())
784      return true;
785  }
786}
787
788bool CEnumerator::Next(CFileInfo &fi, bool &found)
789{
790  /*
791  for (;;)
792  {
793    if (!NextAny(fi))
794      break;
795    if (!fi.IsDots())
796    {
797      found = true;
798      return true;
799    }
800  }
801  */
802
803  if (Next(fi))
804  {
805    found = true;
806    return true;
807  }
808
809  found = false;
810  DWORD lastError = ::GetLastError();
811  if (_findFile.IsHandleAllocated())
812    return (lastError == ERROR_NO_MORE_FILES);
813  // we support the case for empty root folder: FindFirstFile("c:\\*") returns ERROR_FILE_NOT_FOUND
814  if (lastError == ERROR_FILE_NOT_FOUND)
815    return true;
816  if (lastError == ERROR_ACCESS_DENIED)
817  {
818    // here we show inaccessible root system folder as empty folder to eliminate redundant user warnings
819    const char *s = "System Volume Information" STRING_PATH_SEPARATOR "*";
820    const int len = (int)strlen(s);
821    const int delta = (int)_wildcard.Len() - len;
822    if (delta == 0 || (delta > 0 && IS_PATH_SEPAR(_wildcard[(unsigned)delta - 1])))
823      if (StringsAreEqual_Ascii(_wildcard.Ptr((unsigned)delta), s))
824        return true;
825  }
826  return false;
827}
828
829
830////////////////////////////////
831// CFindChangeNotification
832// FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
833
834bool CFindChangeNotification::Close() throw()
835{
836  if (!IsHandleAllocated())
837    return true;
838  if (!::FindCloseChangeNotification(_handle))
839    return false;
840  _handle = INVALID_HANDLE_VALUE;
841  return true;
842}
843
844HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
845{
846  #ifndef _UNICODE
847  if (!g_IsNT)
848    _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
849  else
850  #endif
851  {
852    IF_USE_MAIN_PATH
853    _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
854    #ifdef Z7_LONG_PATH
855    if (!IsHandleAllocated())
856    {
857      UString superPath;
858      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
859        _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);
860    }
861    #endif
862  }
863  return _handle;
864}
865
866#ifndef UNDER_CE
867
868bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
869{
870  driveStrings.Clear();
871  #ifndef _UNICODE
872  if (!g_IsNT)
873  {
874    driveStrings.Clear();
875    UINT32 size = GetLogicalDriveStrings(0, NULL);
876    if (size == 0)
877      return false;
878    CObjArray<char> buf(size);
879    UINT32 newSize = GetLogicalDriveStrings(size, buf);
880    if (newSize == 0 || newSize > size)
881      return false;
882    AString s;
883    UINT32 prev = 0;
884    for (UINT32 i = 0; i < newSize; i++)
885    {
886      if (buf[i] == 0)
887      {
888        s = buf + prev;
889        prev = i + 1;
890        driveStrings.Add(fas2fs(s));
891      }
892    }
893    return prev == newSize;
894  }
895  else
896  #endif
897  {
898    UINT32 size = GetLogicalDriveStringsW(0, NULL);
899    if (size == 0)
900      return false;
901    CObjArray<wchar_t> buf(size);
902    UINT32 newSize = GetLogicalDriveStringsW(size, buf);
903    if (newSize == 0 || newSize > size)
904      return false;
905    UString s;
906    UINT32 prev = 0;
907    for (UINT32 i = 0; i < newSize; i++)
908    {
909      if (buf[i] == 0)
910      {
911        s = buf + prev;
912        prev = i + 1;
913        driveStrings.Add(us2fs(s));
914      }
915    }
916    return prev == newSize;
917  }
918}
919
920#endif // UNDER_CE
921
922
923
924#else // _WIN32
925
926// ---------- POSIX ----------
927
928static int MY__lstat(CFSTR path, struct stat *st, bool followLink)
929{
930  memset(st, 0, sizeof(*st));
931  int res;
932  // #ifdef ENV_HAVE_LSTAT
933  if (/* global_use_lstat && */ !followLink)
934  {
935    // printf("\nlstat\n");
936    res = lstat(path, st);
937  }
938  else
939  // #endif
940  {
941    // printf("\nstat\n");
942    res = stat(path, st);
943  }
944  /*
945  printf("\nres = %d\n", res);
946  printf("\n st_dev = %lld \n", (long long)(st->st_dev));
947  printf("\n st_ino = %lld \n", (long long)(st->st_ino));
948  printf("\n st_mode = %lld \n", (long long)(st->st_mode));
949  printf("\n st_nlink = %lld \n", (long long)(st->st_nlink));
950  printf("\n st_uid = %lld \n", (long long)(st->st_uid));
951  printf("\n st_gid = %lld \n", (long long)(st->st_gid));
952  printf("\n st_size = %lld \n", (long long)(st->st_size));
953  printf("\n st_blksize = %lld \n", (long long)(st->st_blksize));
954  printf("\n st_blocks = %lld \n", (long long)(st->st_blocks));
955  */
956
957  return res;
958}
959
960
961static const char *Get_Name_from_Path(CFSTR path) throw()
962{
963  size_t len = strlen(path);
964  if (len == 0)
965    return path;
966  const char *p = path + len - 1;
967  {
968    if (p == path)
969      return path;
970    p--;
971  }
972  for (;;)
973  {
974    char c = *p;
975    if (IS_PATH_SEPAR(c))
976      return p + 1;
977    if (p == path)
978      return path;
979    p--;
980  }
981}
982
983
984UInt32 Get_WinAttribPosix_From_PosixMode(UInt32 mode)
985{
986  UInt32 attrib = S_ISDIR(mode) ?
987      FILE_ATTRIBUTE_DIRECTORY :
988      FILE_ATTRIBUTE_ARCHIVE;
989  if ((mode & 0222) == 0) // S_IWUSR in p7zip
990    attrib |= FILE_ATTRIBUTE_READONLY;
991  return attrib | FILE_ATTRIBUTE_UNIX_EXTENSION | ((mode & 0xFFFF) << 16);
992}
993
994/*
995UInt32 Get_WinAttrib_From_stat(const struct stat &st)
996{
997  UInt32 attrib = S_ISDIR(st.st_mode) ?
998    FILE_ATTRIBUTE_DIRECTORY :
999    FILE_ATTRIBUTE_ARCHIVE;
1000
1001  if ((st.st_mode & 0222) == 0) // check it !!!
1002    attrib |= FILE_ATTRIBUTE_READONLY;
1003
1004  attrib |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((st.st_mode & 0xFFFF) << 16);
1005  return attrib;
1006}
1007*/
1008
1009void CFileInfo::SetFrom_stat(const struct stat &st)
1010{
1011  // IsDevice = false;
1012
1013  if (S_ISDIR(st.st_mode))
1014  {
1015    Size = 0;
1016  }
1017  else
1018  {
1019    Size = (UInt64)st.st_size; // for a symbolic link, size = size of filename
1020  }
1021
1022  // Attrib = Get_WinAttribPosix_From_PosixMode(st.st_mode);
1023
1024  // NTime::UnixTimeToFileTime(st.st_ctime, CTime);
1025  // NTime::UnixTimeToFileTime(st.st_mtime, MTime);
1026  // NTime::UnixTimeToFileTime(st.st_atime, ATime);
1027  #ifdef __APPLE__
1028  // #ifdef _DARWIN_FEATURE_64_BIT_INODE
1029  /*
1030    here we can use birthtime instead of st_ctimespec.
1031    but we use st_ctimespec for compatibility with previous versions and p7zip.
1032    st_birthtimespec in OSX
1033    st_birthtim : at FreeBSD, NetBSD
1034  */
1035  // timespec_To_FILETIME(st.st_birthtimespec, CTime);
1036  // #else
1037  // timespec_To_FILETIME(st.st_ctimespec, CTime);
1038  // #endif
1039  // timespec_To_FILETIME(st.st_mtimespec, MTime);
1040  // timespec_To_FILETIME(st.st_atimespec, ATime);
1041  CTime = st.st_ctimespec;
1042  MTime = st.st_mtimespec;
1043  ATime = st.st_atimespec;
1044
1045  #else
1046  // timespec_To_FILETIME(st.st_ctim, CTime, &CTime_ns100);
1047  // timespec_To_FILETIME(st.st_mtim, MTime, &MTime_ns100);
1048  // timespec_To_FILETIME(st.st_atim, ATime, &ATime_ns100);
1049  CTime = st.st_ctim;
1050  MTime = st.st_mtim;
1051  ATime = st.st_atim;
1052
1053  #endif
1054
1055  dev = st.st_dev;
1056  ino = st.st_ino;
1057  mode = st.st_mode;
1058  nlink = st.st_nlink;
1059  uid = st.st_uid;
1060  gid = st.st_gid;
1061  rdev = st.st_rdev;
1062
1063  /*
1064  printf("\n sizeof timespec = %d", (int)sizeof(timespec));
1065  printf("\n sizeof st_rdev = %d", (int)sizeof(rdev));
1066  printf("\n sizeof st_ino = %d", (int)sizeof(ino));
1067  printf("\n sizeof mode_t = %d", (int)sizeof(mode_t));
1068  printf("\n sizeof nlink_t = %d", (int)sizeof(nlink_t));
1069  printf("\n sizeof uid_t = %d", (int)sizeof(uid_t));
1070  printf("\n");
1071  */
1072  /*
1073  printf("\n st_rdev = %llx", (long long)rdev);
1074  printf("\n st_dev  = %llx", (long long)dev);
1075  printf("\n dev  : major = %5x minor = %5x", (unsigned)major(dev), (unsigned)minor(dev));
1076  printf("\n st_ino = %lld", (long long)(ino));
1077  printf("\n rdev : major = %5x minor = %5x", (unsigned)major(rdev), (unsigned)minor(rdev));
1078  printf("\n size = %lld \n", (long long)(Size));
1079  printf("\n");
1080  */
1081}
1082
1083/*
1084int Uid_To_Uname(uid_t uid, AString &name)
1085{
1086  name.Empty();
1087  struct passwd *passwd;
1088
1089  if (uid != 0 && uid == cached_no_such_uid)
1090    {
1091      *uname = xstrdup ("");
1092      return;
1093    }
1094
1095  if (!cached_uname || uid != cached_uid)
1096    {
1097      passwd = getpwuid (uid);
1098      if (passwd)
1099  {
1100    cached_uid = uid;
1101    assign_string (&cached_uname, passwd->pw_name);
1102  }
1103      else
1104  {
1105    cached_no_such_uid = uid;
1106    *uname = xstrdup ("");
1107    return;
1108  }
1109    }
1110  *uname = xstrdup (cached_uname);
1111}
1112*/
1113
1114bool CFileInfo::Find_DontFill_Name(CFSTR path, bool followLink)
1115{
1116  struct stat st;
1117  if (MY__lstat(path, &st, followLink) != 0)
1118    return false;
1119  // printf("\nFind_DontFill_Name : name=%s\n", path);
1120  SetFrom_stat(st);
1121  return true;
1122}
1123
1124
1125bool CFileInfo::Find(CFSTR path, bool followLink)
1126{
1127  // printf("\nCEnumerator::Find() name = %s\n", path);
1128  if (!Find_DontFill_Name(path, followLink))
1129    return false;
1130
1131  // printf("\nOK\n");
1132
1133  Name = Get_Name_from_Path(path);
1134  if (!Name.IsEmpty())
1135  {
1136    char c = Name.Back();
1137    if (IS_PATH_SEPAR(c))
1138      Name.DeleteBack();
1139  }
1140  return true;
1141}
1142
1143
1144bool DoesFileExist_Raw(CFSTR name)
1145{
1146  // FIXME for symbolic links.
1147  struct stat st;
1148  if (MY__lstat(name, &st, false) != 0)
1149    return false;
1150  return !S_ISDIR(st.st_mode);
1151}
1152
1153bool DoesFileExist_FollowLink(CFSTR name)
1154{
1155  // FIXME for symbolic links.
1156  struct stat st;
1157  if (MY__lstat(name, &st, true) != 0)
1158    return false;
1159  return !S_ISDIR(st.st_mode);
1160}
1161
1162bool DoesDirExist(CFSTR name, bool followLink)
1163{
1164  struct stat st;
1165  if (MY__lstat(name, &st, followLink) != 0)
1166    return false;
1167  return S_ISDIR(st.st_mode);
1168}
1169
1170bool DoesFileOrDirExist(CFSTR name)
1171{
1172  struct stat st;
1173  if (MY__lstat(name, &st, false) != 0)
1174    return false;
1175  return true;
1176}
1177
1178
1179CEnumerator::~CEnumerator()
1180{
1181  if (_dir)
1182    closedir(_dir);
1183}
1184
1185void CEnumerator::SetDirPrefix(const FString &dirPrefix)
1186{
1187  _wildcard = dirPrefix;
1188}
1189
1190bool CDirEntry::IsDots() const throw()
1191{
1192  /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
1193     we can call fstatat() for that case, but we use only (Name) check here */
1194
1195  #if !defined(_AIX)
1196  if (Type != DT_DIR && Type != DT_UNKNOWN)
1197    return false;
1198  #endif
1199
1200  return Name.Len() != 0
1201      && Name.Len() <= 2
1202      && Name[0] == '.'
1203      && (Name.Len() == 1 || Name[1] == '.');
1204}
1205
1206
1207bool CEnumerator::NextAny(CDirEntry &fi, bool &found)
1208{
1209  found = false;
1210
1211  if (!_dir)
1212  {
1213    const char *w = "./";
1214    if (!_wildcard.IsEmpty())
1215      w = _wildcard.Ptr();
1216    _dir = ::opendir((const char *)w);
1217    if (_dir == NULL)
1218      return false;
1219  }
1220
1221  // To distinguish end of stream from an error, we must set errno to zero before readdir()
1222  errno = 0;
1223
1224  struct dirent *de = readdir(_dir);
1225  if (!de)
1226  {
1227    if (errno == 0)
1228      return true; // it's end of stream, and we report it with (found = false)
1229    // it's real error
1230    return false;
1231  }
1232
1233  fi.iNode = de->d_ino;
1234
1235  #if !defined(_AIX)
1236  fi.Type = de->d_type;
1237  /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
1238     we can set (Type) from fstatat() in that case.
1239     But (Type) is not too important. So we don't set it here with slow fstatat() */
1240  /*
1241  // fi.Type = DT_UNKNOWN; // for debug
1242  if (fi.Type == DT_UNKNOWN)
1243  {
1244    struct stat st;
1245    if (fstatat(dirfd(_dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
1246      if (S_ISDIR(st.st_mode))
1247        fi.Type = DT_DIR;
1248  }
1249  */
1250  #endif
1251
1252  /*
1253  if (de->d_type == DT_DIR)
1254    fi.Attrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(S_IFDIR) << 16);
1255  else if (de->d_type < 16)
1256    fi.Attrib = FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(de->d_type) << (16 + 12));
1257  */
1258  fi.Name = de->d_name;
1259
1260  /*
1261  printf("\nCEnumerator::NextAny; len = %d %s \n", (int)fi.Name.Len(), fi.Name.Ptr());
1262  for (unsigned i = 0; i < fi.Name.Len(); i++)
1263    printf (" %02x", (unsigned)(Byte)de->d_name[i]);
1264  printf("\n");
1265  */
1266
1267  found = true;
1268  return true;
1269}
1270
1271
1272bool CEnumerator::Next(CDirEntry &fi, bool &found)
1273{
1274  // printf("\nCEnumerator::Next()\n");
1275  // PrintName("Next", "");
1276  for (;;)
1277  {
1278    if (!NextAny(fi, found))
1279      return false;
1280    if (!found)
1281      return true;
1282    if (!fi.IsDots())
1283    {
1284      /*
1285      if (!NeedFullStat)
1286        return true;
1287      // we silently skip error file here - it can be wrong link item
1288      if (fi.Find_DontFill_Name(path))
1289        return true;
1290      */
1291      return true;
1292    }
1293  }
1294}
1295
1296/*
1297bool CEnumerator::Next(CDirEntry &fileInfo, bool &found)
1298{
1299  bool found;
1300  if (!Next(fi, found))
1301    return false;
1302  return found;
1303}
1304*/
1305
1306bool CEnumerator::Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const
1307{
1308  // printf("\nCEnumerator::Fill_FileInfo()\n");
1309  struct stat st;
1310  // probably it's OK to use fstatat() even if it changes file position dirfd(_dir)
1311  int res = fstatat(dirfd(_dir), de.Name, &st, followLink ? 0 : AT_SYMLINK_NOFOLLOW);
1312  // if fstatat() is not supported, we can use stat() / lstat()
1313
1314  /*
1315  const FString path = _wildcard + s;
1316  int res = MY__lstat(path, &st, followLink);
1317  */
1318
1319  if (res != 0)
1320    return false;
1321  // printf("\nname=%s\n", de.Name.Ptr());
1322  fileInfo.SetFrom_stat(st);
1323  fileInfo.Name = de.Name;
1324  return true;
1325}
1326
1327#endif // _WIN32
1328
1329}}}
1330