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 19extern bool g_IsNT; 20#endif 21 22using namespace NWindows; 23using namespace NFile; 24using namespace NName; 25 26#if defined(_WIN32) && !defined(UNDER_CE) 27 28EXTERN_C_BEGIN 29 30typedef enum 31{ 32 My_FindStreamInfoStandard, 33 My_FindStreamInfoMaxInfoLevel 34} MY_STREAM_INFO_LEVELS; 35 36typedef struct 37{ 38 LARGE_INTEGER StreamSize; 39 WCHAR cStreamName[MAX_PATH + 36]; 40} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA; 41 42typedef HANDLE (WINAPI *Func_FindFirstStreamW)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, 43 LPVOID findStreamData, DWORD flags); 44 45typedef BOOL (APIENTRY *Func_FindNextStreamW)(HANDLE findStream, LPVOID findStreamData); 46 47EXTERN_C_END 48 49#endif // defined(_WIN32) && !defined(UNDER_CE) 50 51 52namespace NWindows { 53namespace NFile { 54 55 56#ifdef _WIN32 57#ifdef Z7_DEVICE_FILE 58namespace NSystem 59{ 60bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); 61} 62#endif 63#endif 64 65namespace 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 75void 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 98bool 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 129static 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 139static 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 152bool 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/* 163WinXP-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 194bool 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 229bool 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 255static Func_FindFirstStreamW g_FindFirstStreamW; 256static Func_FindNextStreamW g_FindNextStreamW; 257 258static struct CFindStreamLoader 259{ 260 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 272bool CStreamInfo::IsMainStream() const throw() 273{ 274 return StringsAreEqualNoCase_Ascii(Name, "::$DATA"); 275} 276 277UString 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/* 287UString CStreamInfo::GetReducedName2() const 288{ 289 UString s = GetReducedName(); 290 if (!s.IsEmpty() && s[0] == ':') 291 s.Delete(0); 292 return s; 293} 294*/ 295 296static 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 319bool 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 354bool 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 370bool 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/* 390WinXP-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 407DWORD 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() 439We alow the following paths (as FindFirstFile): 440 C:\folder 441 c: - if current dir is NOT ROOT ( c:\folder ) 442 443also 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 456bool 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 669bool 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/* 685bool 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 738bool DoesFileExist_Raw(CFSTR name) 739{ 740 CFileInfo fi; 741 return fi.Find(name) && !fi.IsDir(); 742} 743 744bool DoesFileExist_FollowLink(CFSTR name) 745{ 746 CFileInfo fi; 747 return fi.Find_FollowLink(name) && !fi.IsDir(); 748} 749 750bool DoesDirExist(CFSTR name, bool followLink) 751{ 752 CFileInfo fi; 753 return fi.Find(name, followLink) && fi.IsDir(); 754} 755 756bool DoesFileOrDirExist(CFSTR name) 757{ 758 CFileInfo fi; 759 return fi.Find(name); 760} 761 762 763void CEnumerator::SetDirPrefix(const FString &dirPrefix) 764{ 765 _wildcard = dirPrefix; 766 _wildcard += '*'; 767} 768 769bool CEnumerator::NextAny(CFileInfo &fi) 770{ 771 if (_findFile.IsHandleAllocated()) 772 return _findFile.FindNext(fi); 773 else 774 return _findFile.FindFirst(_wildcard, fi); 775} 776 777bool 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 788bool 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 834bool 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 844HANDLE 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 868bool 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 928static 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 961static 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 984UInt32 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/* 995UInt32 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 1009void 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/* 1084int 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 1114bool 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 1125bool 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 1144bool 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 1153bool 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 1162bool 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 1170bool 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 1179CEnumerator::~CEnumerator() 1180{ 1181 if (_dir) 1182 closedir(_dir); 1183} 1184 1185void CEnumerator::SetDirPrefix(const FString &dirPrefix) 1186{ 1187 _wildcard = dirPrefix; 1188} 1189 1190bool 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 1207bool 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 1272bool 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/* 1297bool CEnumerator::Next(CDirEntry &fileInfo, bool &found) 1298{ 1299 bool found; 1300 if (!Next(fi, found)) 1301 return false; 1302 return found; 1303} 1304*/ 1305 1306bool 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