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