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