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 *Func_FindFirstStreamW)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
43     LPVOID findStreamData, DWORD flags);
44 
45 typedef BOOL (APIENTRY *Func_FindNextStreamW)(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 Z7_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 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 
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 Func_FindFirstStreamW g_FindFirstStreamW;
256 static Func_FindNextStreamW g_FindNextStreamW;
257 
258 static struct CFindStreamLoader
259 {
CFindStreamLoaderNWindows::NFile::NFind::CFindStreamLoader260   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 
IsMainStream() const272 bool CStreamInfo::IsMainStream() const throw()
273 {
274   return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
275 }
276 
GetReducedName() const277 UString 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 /*
287 UString CStreamInfo::GetReducedName2() const
288 {
289   UString s = GetReducedName();
290   if (!s.IsEmpty() && s[0] == ':')
291     s.Delete(0);
292   return s;
293 }
294 */
295 
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)296 static 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 
FindFirst(CFSTR path, CStreamInfo &si)319 bool 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 
FindNext(CStreamInfo &si)354 bool 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 
Next(CStreamInfo &si, bool &found)370 bool 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 /*
390 WinXP-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 
GetFileAttrib(CFSTR path)407 DWORD 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()
439 We alow the following paths (as FindFirstFile):
440   C:\folder
441   c:                      - if current dir is NOT ROOT ( c:\folder )
442 
443 also 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 
Find(CFSTR path, bool followLink)456 bool 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 
Fill_From_ByHandleFileInfo(CFSTR path)669 bool 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 /*
685 bool 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 
DoesFileExist_Raw(CFSTR name)738 bool DoesFileExist_Raw(CFSTR name)
739 {
740   CFileInfo fi;
741   return fi.Find(name) && !fi.IsDir();
742 }
743 
DoesFileExist_FollowLink(CFSTR name)744 bool DoesFileExist_FollowLink(CFSTR name)
745 {
746   CFileInfo fi;
747   return fi.Find_FollowLink(name) && !fi.IsDir();
748 }
749 
DoesDirExist(CFSTR name, bool followLink)750 bool DoesDirExist(CFSTR name, bool followLink)
751 {
752   CFileInfo fi;
753   return fi.Find(name, followLink) && fi.IsDir();
754 }
755 
DoesFileOrDirExist(CFSTR name)756 bool DoesFileOrDirExist(CFSTR name)
757 {
758   CFileInfo fi;
759   return fi.Find(name);
760 }
761 
762 
SetDirPrefix(const FString &dirPrefix)763 void CEnumerator::SetDirPrefix(const FString &dirPrefix)
764 {
765   _wildcard = dirPrefix;
766   _wildcard += '*';
767 }
768 
NextAny(CFileInfo &fi)769 bool CEnumerator::NextAny(CFileInfo &fi)
770 {
771   if (_findFile.IsHandleAllocated())
772     return _findFile.FindNext(fi);
773   else
774     return _findFile.FindFirst(_wildcard, fi);
775 }
776 
Next(CFileInfo &fi)777 bool 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 
Next(CFileInfo &fi, bool &found)788 bool 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 
Close()834 bool 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 
FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)844 HANDLE 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 
MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)868 bool 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 
MY__lstat(CFSTR path, struct stat *st, bool followLink)928 static 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 
Get_Name_from_Path(CFSTR path)961 static 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 
Get_WinAttribPosix_From_PosixMode(UInt32 mode)984 UInt32 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 /*
995 UInt32 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 
SetFrom_stat(const struct stat &st)1009 void 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 /*
1084 int 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 
Find_DontFill_Name(CFSTR path, bool followLink)1114 bool 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 
Find(CFSTR path, bool followLink)1125 bool 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 
DoesFileExist_Raw(CFSTR name)1144 bool 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 
DoesFileExist_FollowLink(CFSTR name)1153 bool 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 
DoesDirExist(CFSTR name, bool followLink)1162 bool 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 
DoesFileOrDirExist(CFSTR name)1170 bool 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 
~CEnumerator()1179 CEnumerator::~CEnumerator()
1180 {
1181   if (_dir)
1182     closedir(_dir);
1183 }
1184 
SetDirPrefix(const FString &dirPrefix)1185 void CEnumerator::SetDirPrefix(const FString &dirPrefix)
1186 {
1187   _wildcard = dirPrefix;
1188 }
1189 
IsDots() const1190 bool 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 
NextAny(CDirEntry &fi, bool &found)1207 bool 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 
Next(CDirEntry &fi, bool &found)1272 bool 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 /*
1297 bool CEnumerator::Next(CDirEntry &fileInfo, bool &found)
1298 {
1299   bool found;
1300   if (!Next(fi, found))
1301     return false;
1302   return found;
1303 }
1304 */
1305 
Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const1306 bool 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