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