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