1 // Windows/FileFind.cpp 2 3 #include "StdAfx.h" 4 5 #include "FileFind.h" 6 #include "FileIO.h" 7 #include "FileName.h" 8 #ifndef _UNICODE 9 #include "../Common/StringConvert.h" 10 #endif 11 12 #ifndef _UNICODE 13 extern bool g_IsNT; 14 #endif 15 16 using namespace NWindows; 17 using namespace NFile; 18 using namespace NName; 19 20 #if defined(_WIN32) && !defined(UNDER_CE) 21 22 EXTERN_C_BEGIN 23 24 typedef enum 25 { 26 My_FindStreamInfoStandard, 27 My_FindStreamInfoMaxInfoLevel 28 } MY_STREAM_INFO_LEVELS; 29 30 typedef struct 31 { 32 LARGE_INTEGER StreamSize; 33 WCHAR cStreamName[MAX_PATH + 36]; 34 } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA; 35 36 typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, 37 LPVOID findStreamData, DWORD flags); 38 39 typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData); 40 41 EXTERN_C_END 42 43 #endif 44 45 namespace NWindows { 46 namespace NFile { 47 48 #ifdef SUPPORT_DEVICE_FILE 49 namespace NSystem 50 { 51 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); 52 } 53 #endif 54 55 namespace NFind { 56 57 bool CFileInfo::IsDots() const throw() 58 { 59 if (!IsDir() || Name.IsEmpty()) 60 return false; 61 if (Name[0] != FTEXT('.')) 62 return false; 63 return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.')); 64 } 65 66 #define WIN_FD_TO_MY_FI(fi, fd) \ 67 fi.Attrib = fd.dwFileAttributes; \ 68 fi.CTime = fd.ftCreationTime; \ 69 fi.ATime = fd.ftLastAccessTime; \ 70 fi.MTime = fd.ftLastWriteTime; \ 71 fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \ 72 fi.IsAltStream = false; \ 73 fi.IsDevice = false; 74 75 /* 76 #ifdef UNDER_CE 77 fi.ObjectID = fd.dwOID; 78 #else 79 fi.ReparseTag = fd.dwReserved0; 80 #endif 81 */ 82 83 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi) 84 { 85 WIN_FD_TO_MY_FI(fi, fd); 86 fi.Name = us2fs(fd.cFileName); 87 #if defined(_WIN32) && !defined(UNDER_CE) 88 // fi.ShortName = us2fs(fd.cAlternateFileName); 89 #endif 90 } 91 92 #ifndef _UNICODE 93 94 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) 95 { 96 WIN_FD_TO_MY_FI(fi, fd); 97 fi.Name = fas2fs(fd.cFileName); 98 #if defined(_WIN32) && !defined(UNDER_CE) 99 // fi.ShortName = fas2fs(fd.cAlternateFileName); 100 #endif 101 } 102 #endif 103 104 //////////////////////////////// 105 // CFindFile 106 107 bool CFindFileBase::Close() throw() 108 { 109 if (_handle == INVALID_HANDLE_VALUE) 110 return true; 111 if (!::FindClose(_handle)) 112 return false; 113 _handle = INVALID_HANDLE_VALUE; 114 return true; 115 } 116 117 bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi) 118 { 119 if (!Close()) 120 return false; 121 #ifndef _UNICODE 122 if (!g_IsNT) 123 { 124 WIN32_FIND_DATAA fd; 125 _handle = ::FindFirstFileA(fs2fas(path), &fd); 126 if (_handle == INVALID_HANDLE_VALUE) 127 return false; 128 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 129 } 130 else 131 #endif 132 { 133 WIN32_FIND_DATAW fd; 134 135 IF_USE_MAIN_PATH 136 _handle = ::FindFirstFileW(fs2us(path), &fd); 137 #ifdef WIN_LONG_PATH 138 if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH) 139 { 140 UString longPath; 141 if (GetSuperPath(path, longPath, USE_MAIN_PATH)) 142 _handle = ::FindFirstFileW(longPath, &fd); 143 } 144 #endif 145 if (_handle == INVALID_HANDLE_VALUE) 146 return false; 147 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 148 } 149 return true; 150 } 151 152 bool CFindFile::FindNext(CFileInfo &fi) 153 { 154 #ifndef _UNICODE 155 if (!g_IsNT) 156 { 157 WIN32_FIND_DATAA fd; 158 if (!::FindNextFileA(_handle, &fd)) 159 return false; 160 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 161 } 162 else 163 #endif 164 { 165 WIN32_FIND_DATAW fd; 166 if (!::FindNextFileW(_handle, &fd)) 167 return false; 168 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 169 } 170 return true; 171 } 172 173 #if defined(_WIN32) && !defined(UNDER_CE) 174 175 //////////////////////////////// 176 // AltStreams 177 178 static FindFirstStreamW_Ptr g_FindFirstStreamW; 179 static FindNextStreamW_Ptr g_FindNextStreamW; 180 181 struct CFindStreamLoader 182 { 183 CFindStreamLoader() 184 { 185 g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW"); 186 g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW"); 187 } 188 } g_FindStreamLoader; 189 190 bool CStreamInfo::IsMainStream() const throw() 191 { 192 return Name == L"::$DATA"; 193 }; 194 195 UString CStreamInfo::GetReducedName() const 196 { 197 UString s = Name; 198 if (s.Len() >= 6) 199 if (wcscmp(s.RightPtr(6), L":$DATA") == 0) 200 s.DeleteFrom(s.Len() - 6); 201 return s; 202 } 203 204 static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si) 205 { 206 si.Size = sd.StreamSize.QuadPart; 207 si.Name = sd.cStreamName; 208 } 209 210 bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si) 211 { 212 if (!Close()) 213 return false; 214 if (!g_FindFirstStreamW) 215 { 216 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 217 return false; 218 } 219 { 220 MY_WIN32_FIND_STREAM_DATA sd; 221 IF_USE_MAIN_PATH 222 _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0); 223 if (_handle == INVALID_HANDLE_VALUE) 224 { 225 if (::GetLastError() == ERROR_HANDLE_EOF) 226 return false; 227 // long name can be tricky for path like ".\dirName". 228 #ifdef WIN_LONG_PATH 229 if (USE_SUPER_PATH) 230 { 231 UString longPath; 232 if (GetSuperPath(path, longPath, USE_MAIN_PATH)) 233 _handle = g_FindFirstStreamW(longPath, My_FindStreamInfoStandard, &sd, 0); 234 } 235 #endif 236 } 237 if (_handle == INVALID_HANDLE_VALUE) 238 return false; 239 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); 240 } 241 return true; 242 } 243 244 bool CFindStream::FindNext(CStreamInfo &si) 245 { 246 if (!g_FindNextStreamW) 247 { 248 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 249 return false; 250 } 251 { 252 MY_WIN32_FIND_STREAM_DATA sd; 253 if (!g_FindNextStreamW(_handle, &sd)) 254 return false; 255 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); 256 } 257 return true; 258 } 259 260 bool CStreamEnumerator::Next(CStreamInfo &si, bool &found) 261 { 262 bool res; 263 if (_find.IsHandleAllocated()) 264 res = _find.FindNext(si); 265 else 266 res = _find.FindFirst(_filePath, si); 267 if (res) 268 { 269 found = true; 270 return true; 271 } 272 found = false; 273 return (::GetLastError() == ERROR_HANDLE_EOF); 274 } 275 276 #endif 277 278 279 #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; 280 281 void CFileInfoBase::Clear() throw() 282 { 283 Size = 0; 284 MY_CLEAR_FILETIME(CTime); 285 MY_CLEAR_FILETIME(ATime); 286 MY_CLEAR_FILETIME(MTime); 287 Attrib = 0; 288 IsAltStream = false; 289 IsDevice = false; 290 } 291 292 #if defined(_WIN32) && !defined(UNDER_CE) 293 294 static int FindAltStreamColon(CFSTR path) 295 { 296 for (int i = 0;; i++) 297 { 298 FChar c = path[i]; 299 if (c == 0) 300 return -1; 301 if (c == ':') 302 { 303 if (path[i + 1] == '\\') 304 if (i == 1 || (i > 1 && path[i - 2] == '\\')) 305 { 306 wchar_t c0 = path[i - 1]; 307 if (c0 >= 'a' && c0 <= 'z' || 308 c0 >= 'A' && c0 <= 'Z') 309 continue; 310 } 311 return i; 312 } 313 } 314 } 315 316 #endif 317 318 bool CFileInfo::Find(CFSTR path) 319 { 320 #ifdef SUPPORT_DEVICE_FILE 321 if (IsDevicePath(path)) 322 { 323 Clear(); 324 Name = path + 4; 325 326 IsDevice = true; 327 if (/* path[0] == '\\' && path[1] == '\\' && path[2] == '.' && path[3] == '\\' && */ 328 path[5] == ':' && path[6] == 0) 329 { 330 FChar drive[4] = { path[4], ':', '\\', 0 }; 331 UInt64 clusterSize, totalSize, freeSize; 332 if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize)) 333 { 334 Size = totalSize; 335 return true; 336 } 337 } 338 339 NIO::CInFile inFile; 340 // ::OutputDebugStringW(path); 341 if (!inFile.Open(path)) 342 return false; 343 // ::OutputDebugStringW(L"---"); 344 if (inFile.SizeDefined) 345 Size = inFile.Size; 346 return true; 347 } 348 #endif 349 350 #if defined(_WIN32) && !defined(UNDER_CE) 351 352 int colonPos = FindAltStreamColon(path); 353 if (colonPos >= 0) 354 { 355 UString streamName = fs2us(path + (unsigned)colonPos); 356 FString filePath = path; 357 filePath.DeleteFrom(colonPos); 358 streamName += L":$DATA"; // change it!!!! 359 if (Find(filePath)) 360 { 361 // if (IsDir()) 362 Attrib &= ~FILE_ATTRIBUTE_DIRECTORY; 363 Size = 0; 364 CStreamEnumerator enumerator(filePath); 365 for (;;) 366 { 367 CStreamInfo si; 368 bool found; 369 if (!enumerator.Next(si, found)) 370 return false; 371 if (!found) 372 { 373 ::SetLastError(ERROR_FILE_NOT_FOUND); 374 return false; 375 } 376 if (si.Name.IsEqualToNoCase(streamName)) 377 { 378 Name += us2fs(si.Name); 379 Name.DeleteFrom(Name.Len() - 6); 380 Size = si.Size; 381 IsAltStream = true; 382 return true; 383 } 384 } 385 } 386 } 387 388 #endif 389 390 CFindFile finder; 391 if (finder.FindFirst(path, *this)) 392 return true; 393 #ifdef _WIN32 394 { 395 DWORD lastError = GetLastError(); 396 if (lastError == ERROR_BAD_NETPATH || 397 lastError == ERROR_FILE_NOT_FOUND || 398 lastError == ERROR_INVALID_NAME // for "\\SERVER\shared" paths that are translated to "\\?\UNC\SERVER\shared" 399 ) 400 { 401 unsigned len = MyStringLen(path); 402 if (len > 2 && path[0] == '\\' && path[1] == '\\') 403 { 404 int startPos = 2; 405 if (len > kSuperUncPathPrefixSize && IsSuperUncPath(path)) 406 startPos = kSuperUncPathPrefixSize; 407 int pos = FindCharPosInString(path + startPos, FTEXT('\\')); 408 if (pos >= 0) 409 { 410 pos += startPos + 1; 411 len -= pos; 412 int pos2 = FindCharPosInString(path + pos, FTEXT('\\')); 413 if (pos2 < 0 || pos2 == (int)len - 1) 414 { 415 FString s = path; 416 if (pos2 < 0) 417 { 418 pos2 = len; 419 s += FTEXT('\\'); 420 } 421 s += FCHAR_ANY_MASK; 422 if (finder.FindFirst(s, *this)) 423 if (Name == FTEXT(".")) 424 { 425 Name.SetFrom(s.Ptr(pos), pos2); 426 return true; 427 } 428 ::SetLastError(lastError); 429 } 430 } 431 } 432 } 433 } 434 #endif 435 return false; 436 } 437 438 bool DoesFileExist(CFSTR name) 439 { 440 CFileInfo fi; 441 return fi.Find(name) && !fi.IsDir(); 442 } 443 444 bool DoesDirExist(CFSTR name) 445 { 446 CFileInfo fi; 447 return fi.Find(name) && fi.IsDir(); 448 } 449 bool DoesFileOrDirExist(CFSTR name) 450 { 451 CFileInfo fi; 452 return fi.Find(name); 453 } 454 455 bool CEnumerator::NextAny(CFileInfo &fi) 456 { 457 if (_findFile.IsHandleAllocated()) 458 return _findFile.FindNext(fi); 459 else 460 return _findFile.FindFirst(_wildcard, fi); 461 } 462 463 bool CEnumerator::Next(CFileInfo &fi) 464 { 465 for (;;) 466 { 467 if (!NextAny(fi)) 468 return false; 469 if (!fi.IsDots()) 470 return true; 471 } 472 } 473 474 bool CEnumerator::Next(CFileInfo &fi, bool &found) 475 { 476 if (Next(fi)) 477 { 478 found = true; 479 return true; 480 } 481 found = false; 482 return (::GetLastError() == ERROR_NO_MORE_FILES); 483 } 484 485 //////////////////////////////// 486 // CFindChangeNotification 487 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it. 488 489 bool CFindChangeNotification::Close() throw() 490 { 491 if (!IsHandleAllocated()) 492 return true; 493 if (!::FindCloseChangeNotification(_handle)) 494 return false; 495 _handle = INVALID_HANDLE_VALUE; 496 return true; 497 } 498 499 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter) 500 { 501 #ifndef _UNICODE 502 if (!g_IsNT) 503 _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter); 504 else 505 #endif 506 { 507 IF_USE_MAIN_PATH 508 _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter); 509 #ifdef WIN_LONG_PATH 510 if (!IsHandleAllocated()) 511 { 512 UString longPath; 513 if (GetSuperPath(path, longPath, USE_MAIN_PATH)) 514 _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter); 515 } 516 #endif 517 } 518 return _handle; 519 } 520 521 #ifndef UNDER_CE 522 523 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings) 524 { 525 driveStrings.Clear(); 526 #ifndef _UNICODE 527 if (!g_IsNT) 528 { 529 driveStrings.Clear(); 530 UINT32 size = GetLogicalDriveStrings(0, NULL); 531 if (size == 0) 532 return false; 533 AString buf; 534 UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size)); 535 if (newSize == 0 || newSize > size) 536 return false; 537 AString s; 538 for (UINT32 i = 0; i < newSize; i++) 539 { 540 char c = buf[i]; 541 if (c == '\0') 542 { 543 driveStrings.Add(fas2fs(s)); 544 s.Empty(); 545 } 546 else 547 s += c; 548 } 549 return s.IsEmpty(); 550 } 551 else 552 #endif 553 { 554 UINT32 size = GetLogicalDriveStringsW(0, NULL); 555 if (size == 0) 556 return false; 557 UString buf; 558 UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size)); 559 if (newSize == 0 || newSize > size) 560 return false; 561 UString s; 562 for (UINT32 i = 0; i < newSize; i++) 563 { 564 WCHAR c = buf[i]; 565 if (c == L'\0') 566 { 567 driveStrings.Add(us2fs(s)); 568 s.Empty(); 569 } 570 else 571 s += c; 572 } 573 return s.IsEmpty(); 574 } 575 } 576 577 #endif 578 579 }}} 580