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