1 // Windows/FileDir.cpp 2 3 #include "StdAfx.h" 4 5 #ifndef _UNICODE 6 #include "../Common/StringConvert.h" 7 #endif 8 9 #include "FileDir.h" 10 #include "FileFind.h" 11 #include "FileName.h" 12 13 #ifndef _UNICODE 14 extern bool g_IsNT; 15 #endif 16 17 using namespace NWindows; 18 using namespace NFile; 19 using namespace NName; 20 21 namespace NWindows { 22 namespace NFile { 23 namespace NDir { 24 25 #ifndef UNDER_CE 26 27 bool GetWindowsDir(FString &path) 28 { 29 UINT needLength; 30 #ifndef _UNICODE 31 if (!g_IsNT) 32 { 33 TCHAR s[MAX_PATH + 2]; 34 s[0] = 0; 35 needLength = ::GetWindowsDirectory(s, MAX_PATH + 1); 36 path = fas2fs(s); 37 } 38 else 39 #endif 40 { 41 WCHAR s[MAX_PATH + 2]; 42 s[0] = 0; 43 needLength = ::GetWindowsDirectoryW(s, MAX_PATH + 1); 44 path = us2fs(s); 45 } 46 return (needLength > 0 && needLength <= MAX_PATH); 47 } 48 49 bool GetSystemDir(FString &path) 50 { 51 UINT needLength; 52 #ifndef _UNICODE 53 if (!g_IsNT) 54 { 55 TCHAR s[MAX_PATH + 2]; 56 s[0] = 0; 57 needLength = ::GetSystemDirectory(s, MAX_PATH + 1); 58 path = fas2fs(s); 59 } 60 else 61 #endif 62 { 63 WCHAR s[MAX_PATH + 2]; 64 s[0] = 0; 65 needLength = ::GetSystemDirectoryW(s, MAX_PATH + 1); 66 path = us2fs(s); 67 } 68 return (needLength > 0 && needLength <= MAX_PATH); 69 } 70 #endif 71 72 bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) 73 { 74 #ifndef _UNICODE 75 if (!g_IsNT) 76 { 77 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 78 return false; 79 } 80 #endif 81 82 HANDLE hDir = INVALID_HANDLE_VALUE; 83 IF_USE_MAIN_PATH 84 hDir = ::CreateFileW(fs2us(path), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 85 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); 86 #ifdef WIN_LONG_PATH 87 if (hDir == INVALID_HANDLE_VALUE && USE_SUPER_PATH) 88 { 89 UString superPath; 90 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 91 hDir = ::CreateFileW(superPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 92 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); 93 } 94 #endif 95 96 bool res = false; 97 if (hDir != INVALID_HANDLE_VALUE) 98 { 99 res = BOOLToBool(::SetFileTime(hDir, cTime, aTime, mTime)); 100 ::CloseHandle(hDir); 101 } 102 return res; 103 } 104 105 bool SetFileAttrib(CFSTR path, DWORD attrib) 106 { 107 #ifndef _UNICODE 108 if (!g_IsNT) 109 { 110 if (::SetFileAttributes(fs2fas(path), attrib)) 111 return true; 112 } 113 else 114 #endif 115 { 116 IF_USE_MAIN_PATH 117 if (::SetFileAttributesW(fs2us(path), attrib)) 118 return true; 119 #ifdef WIN_LONG_PATH 120 if (USE_SUPER_PATH) 121 { 122 UString superPath; 123 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 124 return BOOLToBool(::SetFileAttributesW(superPath, attrib)); 125 } 126 #endif 127 } 128 return false; 129 } 130 131 132 bool SetFileAttrib_PosixHighDetect(CFSTR path, DWORD attrib) 133 { 134 if ((attrib & 0xF0000000) != 0) 135 attrib &= 0x3FFF; 136 return SetFileAttrib(path, attrib); 137 } 138 139 140 bool RemoveDir(CFSTR path) 141 { 142 #ifndef _UNICODE 143 if (!g_IsNT) 144 { 145 if (::RemoveDirectory(fs2fas(path))) 146 return true; 147 } 148 else 149 #endif 150 { 151 IF_USE_MAIN_PATH 152 if (::RemoveDirectoryW(fs2us(path))) 153 return true; 154 #ifdef WIN_LONG_PATH 155 if (USE_SUPER_PATH) 156 { 157 UString superPath; 158 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 159 return BOOLToBool(::RemoveDirectoryW(superPath)); 160 } 161 #endif 162 } 163 return false; 164 } 165 166 bool MyMoveFile(CFSTR oldFile, CFSTR newFile) 167 { 168 #ifndef _UNICODE 169 if (!g_IsNT) 170 { 171 if (::MoveFile(fs2fas(oldFile), fs2fas(newFile))) 172 return true; 173 } 174 else 175 #endif 176 { 177 IF_USE_MAIN_PATH_2(oldFile, newFile) 178 if (::MoveFileW(fs2us(oldFile), fs2us(newFile))) 179 return true; 180 #ifdef WIN_LONG_PATH 181 if (USE_SUPER_PATH_2) 182 { 183 UString d1, d2; 184 if (GetSuperPaths(oldFile, newFile, d1, d2, USE_MAIN_PATH_2)) 185 return BOOLToBool(::MoveFileW(d1, d2)); 186 } 187 #endif 188 } 189 return false; 190 } 191 192 #ifndef UNDER_CE 193 194 EXTERN_C_BEGIN 195 typedef BOOL (WINAPI *Func_CreateHardLinkW)( 196 LPCWSTR lpFileName, 197 LPCWSTR lpExistingFileName, 198 LPSECURITY_ATTRIBUTES lpSecurityAttributes 199 ); 200 EXTERN_C_END 201 202 bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName) 203 { 204 #ifndef _UNICODE 205 if (!g_IsNT) 206 { 207 SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 208 return false; 209 /* 210 if (::CreateHardLink(fs2fas(newFileName), fs2fas(existFileName), NULL)) 211 return true; 212 */ 213 } 214 else 215 #endif 216 { 217 Func_CreateHardLinkW my_CreateHardLinkW = (Func_CreateHardLinkW) 218 ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW"); 219 if (!my_CreateHardLinkW) 220 return false; 221 IF_USE_MAIN_PATH_2(newFileName, existFileName) 222 if (my_CreateHardLinkW(fs2us(newFileName), fs2us(existFileName), NULL)) 223 return true; 224 #ifdef WIN_LONG_PATH 225 if (USE_SUPER_PATH_2) 226 { 227 UString d1, d2; 228 if (GetSuperPaths(newFileName, existFileName, d1, d2, USE_MAIN_PATH_2)) 229 return BOOLToBool(my_CreateHardLinkW(d1, d2, NULL)); 230 } 231 #endif 232 } 233 return false; 234 } 235 236 #endif 237 238 /* 239 WinXP-64 CreateDir(): 240 "" - ERROR_PATH_NOT_FOUND 241 \ - ERROR_ACCESS_DENIED 242 C:\ - ERROR_ACCESS_DENIED, if there is such drive, 243 244 D:\folder - ERROR_PATH_NOT_FOUND, if there is no such drive, 245 C:\nonExistent\folder - ERROR_PATH_NOT_FOUND 246 247 C:\existFolder - ERROR_ALREADY_EXISTS 248 C:\existFolder\ - ERROR_ALREADY_EXISTS 249 250 C:\folder - OK 251 C:\folder\ - OK 252 253 \\Server\nonExistent - ERROR_BAD_NETPATH 254 \\Server\Share_Readonly - ERROR_ACCESS_DENIED 255 \\Server\Share - ERROR_ALREADY_EXISTS 256 257 \\Server\Share_NTFS_drive - ERROR_ACCESS_DENIED 258 \\Server\Share_FAT_drive - ERROR_ALREADY_EXISTS 259 */ 260 261 bool CreateDir(CFSTR path) 262 { 263 #ifndef _UNICODE 264 if (!g_IsNT) 265 { 266 if (::CreateDirectory(fs2fas(path), NULL)) 267 return true; 268 } 269 else 270 #endif 271 { 272 IF_USE_MAIN_PATH 273 if (::CreateDirectoryW(fs2us(path), NULL)) 274 return true; 275 #ifdef WIN_LONG_PATH 276 if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) 277 { 278 UString superPath; 279 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 280 return BOOLToBool(::CreateDirectoryW(superPath, NULL)); 281 } 282 #endif 283 } 284 return false; 285 } 286 287 /* 288 CreateDir2 returns true, if directory can contain files after the call (two cases): 289 1) the directory already exists 290 2) the directory was created 291 path must be WITHOUT trailing path separator. 292 293 We need CreateDir2, since fileInfo.Find() for reserved names like "com8" 294 returns FILE instead of DIRECTORY. And we need to use SuperPath */ 295 296 static bool CreateDir2(CFSTR path) 297 { 298 #ifndef _UNICODE 299 if (!g_IsNT) 300 { 301 if (::CreateDirectory(fs2fas(path), NULL)) 302 return true; 303 } 304 else 305 #endif 306 { 307 IF_USE_MAIN_PATH 308 if (::CreateDirectoryW(fs2us(path), NULL)) 309 return true; 310 #ifdef WIN_LONG_PATH 311 if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) 312 { 313 UString superPath; 314 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 315 { 316 if (::CreateDirectoryW(superPath, NULL)) 317 return true; 318 if (::GetLastError() != ERROR_ALREADY_EXISTS) 319 return false; 320 NFind::CFileInfo fi; 321 if (!fi.Find(us2fs(superPath))) 322 return false; 323 return fi.IsDir(); 324 } 325 } 326 #endif 327 } 328 if (::GetLastError() != ERROR_ALREADY_EXISTS) 329 return false; 330 NFind::CFileInfo fi; 331 if (!fi.Find(path)) 332 return false; 333 return fi.IsDir(); 334 } 335 336 bool CreateComplexDir(CFSTR _path) 337 { 338 #ifdef _WIN32 339 340 { 341 DWORD attrib = NFind::GetFileAttrib(_path); 342 if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) 343 return true; 344 } 345 346 #ifndef UNDER_CE 347 348 if (IsDriveRootPath_SuperAllowed(_path)) 349 return false; 350 351 unsigned prefixSize = GetRootPrefixSize(_path); 352 353 #endif 354 355 #endif 356 357 FString path (_path); 358 359 int pos = path.ReverseFind_PathSepar(); 360 if (pos >= 0 && (unsigned)pos == path.Len() - 1) 361 { 362 if (path.Len() == 1) 363 return true; 364 path.DeleteBack(); 365 } 366 367 const FString path2 (path); 368 pos = path.Len(); 369 370 for (;;) 371 { 372 if (CreateDir2(path)) 373 break; 374 if (::GetLastError() == ERROR_ALREADY_EXISTS) 375 return false; 376 pos = path.ReverseFind_PathSepar(); 377 if (pos < 0 || pos == 0) 378 return false; 379 380 #if defined(_WIN32) && !defined(UNDER_CE) 381 if (pos == 1 && IS_PATH_SEPAR(path[0])) 382 return false; 383 if (prefixSize >= (unsigned)pos + 1) 384 return false; 385 #endif 386 387 path.DeleteFrom(pos); 388 } 389 390 while (pos < (int)path2.Len()) 391 { 392 int pos2 = NName::FindSepar(path2.Ptr(pos + 1)); 393 if (pos2 < 0) 394 pos = path2.Len(); 395 else 396 pos += 1 + pos2; 397 path.SetFrom(path2, pos); 398 if (!CreateDir(path)) 399 return false; 400 } 401 402 return true; 403 } 404 405 bool DeleteFileAlways(CFSTR path) 406 { 407 /* If alt stream, we also need to clear READ-ONLY attribute of main file before delete. 408 SetFileAttrib("name:stream", ) changes attributes of main file. */ 409 { 410 DWORD attrib = NFind::GetFileAttrib(path); 411 if (attrib != INVALID_FILE_ATTRIBUTES 412 && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0 413 && (attrib & FILE_ATTRIBUTE_READONLY) != 0) 414 { 415 if (!SetFileAttrib(path, attrib & ~FILE_ATTRIBUTE_READONLY)) 416 return false; 417 } 418 } 419 420 #ifndef _UNICODE 421 if (!g_IsNT) 422 { 423 if (::DeleteFile(fs2fas(path))) 424 return true; 425 } 426 else 427 #endif 428 { 429 /* DeleteFile("name::$DATA") deletes all alt streams (same as delete DeleteFile("name")). 430 Maybe it's better to open "name::$DATA" and clear data for unnamed stream? */ 431 IF_USE_MAIN_PATH 432 if (::DeleteFileW(fs2us(path))) 433 return true; 434 #ifdef WIN_LONG_PATH 435 if (USE_SUPER_PATH) 436 { 437 UString superPath; 438 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 439 return BOOLToBool(::DeleteFileW(superPath)); 440 } 441 #endif 442 } 443 return false; 444 } 445 446 bool RemoveDirWithSubItems(const FString &path) 447 { 448 bool needRemoveSubItems = true; 449 { 450 NFind::CFileInfo fi; 451 if (!fi.Find(path)) 452 return false; 453 if (!fi.IsDir()) 454 { 455 ::SetLastError(ERROR_DIRECTORY); 456 return false; 457 } 458 if (fi.HasReparsePoint()) 459 needRemoveSubItems = false; 460 } 461 462 if (needRemoveSubItems) 463 { 464 FString s (path); 465 s.Add_PathSepar(); 466 const unsigned prefixSize = s.Len(); 467 NFind::CEnumerator enumerator; 468 enumerator.SetDirPrefix(s); 469 NFind::CFileInfo fi; 470 while (enumerator.Next(fi)) 471 { 472 s.DeleteFrom(prefixSize); 473 s += fi.Name; 474 if (fi.IsDir()) 475 { 476 if (!RemoveDirWithSubItems(s)) 477 return false; 478 } 479 else if (!DeleteFileAlways(s)) 480 return false; 481 } 482 } 483 484 if (!SetFileAttrib(path, 0)) 485 return false; 486 return RemoveDir(path); 487 } 488 489 #ifdef UNDER_CE 490 491 bool MyGetFullPathName(CFSTR path, FString &resFullPath) 492 { 493 resFullPath = path; 494 return true; 495 } 496 497 #else 498 499 bool MyGetFullPathName(CFSTR path, FString &resFullPath) 500 { 501 return GetFullPath(path, resFullPath); 502 } 503 504 bool SetCurrentDir(CFSTR path) 505 { 506 // SetCurrentDirectory doesn't support \\?\ prefix 507 #ifndef _UNICODE 508 if (!g_IsNT) 509 { 510 return BOOLToBool(::SetCurrentDirectory(fs2fas(path))); 511 } 512 else 513 #endif 514 { 515 return BOOLToBool(::SetCurrentDirectoryW(fs2us(path))); 516 } 517 } 518 519 bool GetCurrentDir(FString &path) 520 { 521 path.Empty(); 522 DWORD needLength; 523 #ifndef _UNICODE 524 if (!g_IsNT) 525 { 526 TCHAR s[MAX_PATH + 2]; 527 s[0] = 0; 528 needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); 529 path = fas2fs(s); 530 } 531 else 532 #endif 533 { 534 WCHAR s[MAX_PATH + 2]; 535 s[0] = 0; 536 needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); 537 path = us2fs(s); 538 } 539 return (needLength > 0 && needLength <= MAX_PATH); 540 } 541 542 #endif 543 544 bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName) 545 { 546 bool res = MyGetFullPathName(path, resDirPrefix); 547 if (!res) 548 resDirPrefix = path; 549 int pos = resDirPrefix.ReverseFind_PathSepar(); 550 resFileName = resDirPrefix.Ptr(pos + 1); 551 resDirPrefix.DeleteFrom(pos + 1); 552 return res; 553 } 554 555 bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix) 556 { 557 FString resFileName; 558 return GetFullPathAndSplit(path, resDirPrefix, resFileName); 559 } 560 561 bool MyGetTempPath(FString &path) 562 { 563 path.Empty(); 564 DWORD needLength; 565 #ifndef _UNICODE 566 if (!g_IsNT) 567 { 568 TCHAR s[MAX_PATH + 2]; 569 s[0] = 0; 570 needLength = ::GetTempPath(MAX_PATH + 1, s); 571 path = fas2fs(s); 572 } 573 else 574 #endif 575 { 576 WCHAR s[MAX_PATH + 2]; 577 s[0] = 0; 578 needLength = ::GetTempPathW(MAX_PATH + 1, s);; 579 path = us2fs(s); 580 } 581 return (needLength > 0 && needLength <= MAX_PATH); 582 } 583 584 static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COutFile *outFile) 585 { 586 UInt32 d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); 587 for (unsigned i = 0; i < 100; i++) 588 { 589 path = prefix; 590 if (addRandom) 591 { 592 char s[16]; 593 UInt32 val = d; 594 unsigned k; 595 for (k = 0; k < 8; k++) 596 { 597 unsigned t = val & 0xF; 598 val >>= 4; 599 s[k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); 600 } 601 s[k] = '\0'; 602 if (outFile) 603 path += '.'; 604 path += s; 605 UInt32 step = GetTickCount() + 2; 606 if (step == 0) 607 step = 1; 608 d += step; 609 } 610 addRandom = true; 611 if (outFile) 612 path += ".tmp"; 613 if (NFind::DoesFileOrDirExist(path)) 614 { 615 SetLastError(ERROR_ALREADY_EXISTS); 616 continue; 617 } 618 if (outFile) 619 { 620 if (outFile->Create(path, false)) 621 return true; 622 } 623 else 624 { 625 if (CreateDir(path)) 626 return true; 627 } 628 DWORD error = GetLastError(); 629 if (error != ERROR_FILE_EXISTS && 630 error != ERROR_ALREADY_EXISTS) 631 break; 632 } 633 path.Empty(); 634 return false; 635 } 636 637 bool CTempFile::Create(CFSTR prefix, NIO::COutFile *outFile) 638 { 639 if (!Remove()) 640 return false; 641 if (!CreateTempFile(prefix, false, _path, outFile)) 642 return false; 643 _mustBeDeleted = true; 644 return true; 645 } 646 647 bool CTempFile::CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFile) 648 { 649 if (!Remove()) 650 return false; 651 FString tempPath; 652 if (!MyGetTempPath(tempPath)) 653 return false; 654 if (!CreateTempFile(tempPath + namePrefix, true, _path, outFile)) 655 return false; 656 _mustBeDeleted = true; 657 return true; 658 } 659 660 bool CTempFile::Remove() 661 { 662 if (!_mustBeDeleted) 663 return true; 664 _mustBeDeleted = !DeleteFileAlways(_path); 665 return !_mustBeDeleted; 666 } 667 668 bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore) 669 { 670 // DWORD attrib = 0; 671 if (deleteDestBefore) 672 { 673 if (NFind::DoesFileExist(name)) 674 { 675 // attrib = NFind::GetFileAttrib(name); 676 if (!DeleteFileAlways(name)) 677 return false; 678 } 679 } 680 DisableDeleting(); 681 return MyMoveFile(_path, name); 682 683 /* 684 if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_READONLY)) 685 { 686 DWORD attrib2 = NFind::GetFileAttrib(name); 687 if (attrib2 != INVALID_FILE_ATTRIBUTES) 688 SetFileAttrib(name, attrib2 | FILE_ATTRIBUTE_READONLY); 689 } 690 */ 691 } 692 693 bool CTempDir::Create(CFSTR prefix) 694 { 695 if (!Remove()) 696 return false; 697 FString tempPath; 698 if (!MyGetTempPath(tempPath)) 699 return false; 700 if (!CreateTempFile(tempPath + prefix, true, _path, NULL)) 701 return false; 702 _mustBeDeleted = true; 703 return true; 704 } 705 706 bool CTempDir::Remove() 707 { 708 if (!_mustBeDeleted) 709 return true; 710 _mustBeDeleted = !RemoveDirWithSubItems(_path); 711 return !_mustBeDeleted; 712 } 713 714 }}} 715