|
32 | 32 | #include "llvm/ADT/ArrayRef.h" |
33 | 33 | #include "llvm/Support/Chrono.h" |
34 | 34 | #include "llvm/Support/Windows/WindowsSupport.h" |
| 35 | +#include "llvm/Support/WindowsError.h" |
| 36 | +#include <fileapi.h> |
35 | 37 | #include <windows.h> |
36 | 38 | #include <winerror.h> |
37 | 39 | #endif |
@@ -2485,6 +2487,163 @@ TEST_F(FileSystemTest, widenPath) { |
2485 | 2487 | #endif |
2486 | 2488 |
|
2487 | 2489 | #ifdef _WIN32 |
| 2490 | +/// Checks whether short 8.3 form names are enabled in the given UTF-8 path. |
| 2491 | +static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) { |
| 2492 | + // Create a directory under Path8 with a name long enough that Windows will |
| 2493 | + // provide a short 8.3 form name, if short 8.3 form names are enabled. |
| 2494 | + SmallString<MAX_PATH> Dir(Path8); |
| 2495 | + path::append(Dir, "verylongdir"); |
| 2496 | + if (std::error_code EC = fs::create_directories(Dir)) |
| 2497 | + return llvm::errorCodeToError(EC); |
| 2498 | + scope_exit Close([&] { fs::remove_directories(Dir); }); |
| 2499 | + |
| 2500 | + SmallVector<wchar_t, MAX_PATH> Path16; |
| 2501 | + if (std::error_code EC = sys::windows::widenPath(Dir, Path16)) |
| 2502 | + return llvm::errorCodeToError(EC); |
| 2503 | + |
| 2504 | + WIN32_FIND_DATAW Data; |
| 2505 | + HANDLE H = ::FindFirstFileW(Path16.data(), &Data); |
| 2506 | + if (H == INVALID_HANDLE_VALUE) |
| 2507 | + return llvm::errorCodeToError(llvm::mapWindowsError(::GetLastError())); |
| 2508 | + ::FindClose(H); |
| 2509 | + |
| 2510 | + return (Data.cAlternateFileName[0] != L'\0'); |
| 2511 | +} |
| 2512 | + |
| 2513 | +/// Returns the short 8.3 form path for the given UTF-8 path, or an empty string |
| 2514 | +/// on failure. Uses Win32 GetShortPathNameW. |
| 2515 | +static std::string getShortPathName(llvm::StringRef Path8) { |
| 2516 | + // Convert UTF-8 to UTF-16. |
| 2517 | + SmallVector<wchar_t, MAX_PATH> Path16; |
| 2518 | + if (std::error_code EC = sys::windows::widenPath(Path8, Path16)) |
| 2519 | + return {}; |
| 2520 | + |
| 2521 | + // Get the required buffer size for the short 8.3 form path (includes null |
| 2522 | + // terminator). |
| 2523 | + DWORD Required = ::GetShortPathNameW(Path16.data(), nullptr, 0); |
| 2524 | + if (Required == 0) |
| 2525 | + return {}; |
| 2526 | + |
| 2527 | + SmallVector<wchar_t, MAX_PATH> ShortPath; |
| 2528 | + ShortPath.resize_for_overwrite(Required); |
| 2529 | + |
| 2530 | + DWORD Written = |
| 2531 | + ::GetShortPathNameW(Path16.data(), ShortPath.data(), Required); |
| 2532 | + if (Written == 0 || Written >= Required) |
| 2533 | + return {}; |
| 2534 | + |
| 2535 | + ShortPath.truncate(Written); |
| 2536 | + |
| 2537 | + SmallString<MAX_PATH> Utf8Result; |
| 2538 | + if (std::error_code EC = sys::windows::UTF16ToUTF8( |
| 2539 | + ShortPath.data(), ShortPath.size(), Utf8Result)) |
| 2540 | + return {}; |
| 2541 | + |
| 2542 | + return std::string(Utf8Result); |
| 2543 | +} |
| 2544 | + |
| 2545 | +/// Returns true if the two paths refer to the same file or directory by |
| 2546 | +/// comparing their UniqueIDs. |
| 2547 | +static bool sameEntity(llvm::StringRef P1, llvm::StringRef P2) { |
| 2548 | + fs::UniqueID ID1, ID2; |
| 2549 | + return !fs::getUniqueID(P1, ID1) && !fs::getUniqueID(P2, ID2) && ID1 == ID2; |
| 2550 | +} |
| 2551 | + |
| 2552 | +/// Removes the Windows long path path prefix (\\?\ or \\?\UNC\) from the given |
| 2553 | +/// UTF-8 path, if present. |
| 2554 | +static std::string stripPrefix(llvm::StringRef P) { |
| 2555 | + if (P.starts_with(R"(\\?\UNC\)")) |
| 2556 | + return "\\" + P.drop_front(7).str(); |
| 2557 | + if (P.starts_with(R"(\\?\)")) |
| 2558 | + return P.drop_front(4).str(); |
| 2559 | + return P.str(); |
| 2560 | +} |
| 2561 | + |
| 2562 | +TEST_F(FileSystemTest, makeLongFormPath) { |
| 2563 | + auto Enabled = areShortNamesEnabled(TestDirectory.str()); |
| 2564 | + ASSERT_TRUE(static_cast<bool>(Enabled)) |
| 2565 | + << llvm::toString(Enabled.takeError()); |
| 2566 | + if (!*Enabled) |
| 2567 | + GTEST_SKIP() << "Short 8.3 form names not enabled in: " << TestDirectory; |
| 2568 | + |
| 2569 | + // Setup: A test directory longer than 8 characters for which a distinct |
| 2570 | + // short 8.3 form name will be created on Windows. Typically, 123456~1. |
| 2571 | + constexpr const char *OneDir = "\\123456789"; // >8 chars |
| 2572 | + |
| 2573 | + // Setup: Create a path where even if all components were reduced to short 8.3 |
| 2574 | + // form names, the total length would exceed MAX_PATH. |
| 2575 | + SmallString<MAX_PATH * 2> Deep(TestDirectory); |
| 2576 | + const size_t NLevels = (MAX_PATH / 8) + 1; |
| 2577 | + for (size_t I = 0; I < NLevels; ++I) |
| 2578 | + Deep.append(OneDir); |
| 2579 | + |
| 2580 | + ASSERT_NO_ERROR(fs::create_directories(Deep)); |
| 2581 | + |
| 2582 | + // Setup: Create prefixed and non-prefixed short 8.3 form paths from the deep |
| 2583 | + // test path we just created. |
| 2584 | + std::string DeepShortWithPrefix = getShortPathName(Deep); |
| 2585 | + ASSERT_TRUE(StringRef(DeepShortWithPrefix).starts_with(R"(\\?\)")) |
| 2586 | + << "Expected prefixed short 8.3 form path, got: " << DeepShortWithPrefix; |
| 2587 | + std::string DeepShort = stripPrefix(DeepShortWithPrefix); |
| 2588 | + |
| 2589 | + // Setup: Create a short 8.3 form path for the first-level directory. |
| 2590 | + SmallString<MAX_PATH> FirstLevel(TestDirectory); |
| 2591 | + FirstLevel.append(OneDir); |
| 2592 | + std::string Short = getShortPathName(FirstLevel); |
| 2593 | + ASSERT_FALSE(Short.empty()) |
| 2594 | + << "Expected short 8.3 form path for test directory."; |
| 2595 | + |
| 2596 | + // Setup: Create a short 8.3 form path with . and .. components for the |
| 2597 | + // first-level directory. |
| 2598 | + llvm::SmallString<MAX_PATH> WithDots(FirstLevel); |
| 2599 | + llvm::sys::path::append(WithDots, ".", "..", OneDir); |
| 2600 | + std::string DotAndDotDot = getShortPathName(WithDots); |
| 2601 | + ASSERT_FALSE(DotAndDotDot.empty()) |
| 2602 | + << "Expected short 8.3 form path for test directory."; |
| 2603 | + auto ContainsDotAndDotDot = [](llvm::StringRef S) { |
| 2604 | + return S.contains("\\.\\") && S.contains("\\..\\"); |
| 2605 | + }; |
| 2606 | + ASSERT_TRUE(ContainsDotAndDotDot(DotAndDotDot)) |
| 2607 | + << "Expected '.' and '..' components in: " << DotAndDotDot; |
| 2608 | + |
| 2609 | + // Case 1: Non-existent short 8.3 form path. |
| 2610 | + SmallString<MAX_PATH> NoExist("NotEre~1"); |
| 2611 | + ASSERT_FALSE(fs::exists(NoExist)); |
| 2612 | + SmallString<MAX_PATH> NoExistResult; |
| 2613 | + EXPECT_TRUE(windows::makeLongFormPath(NoExist, NoExistResult)); |
| 2614 | + EXPECT_TRUE(NoExistResult.empty()); |
| 2615 | + |
| 2616 | + // Case 2: Valid short 8.3 form path. |
| 2617 | + SmallString<MAX_PATH> ShortResult; |
| 2618 | + ASSERT_FALSE(windows::makeLongFormPath(Short, ShortResult)); |
| 2619 | + EXPECT_TRUE(sameEntity(Short, ShortResult)); |
| 2620 | + |
| 2621 | + // Case 3: Valid . and .. short 8.3 form path. |
| 2622 | + SmallString<MAX_PATH> DotAndDotDotResult; |
| 2623 | + ASSERT_FALSE(windows::makeLongFormPath(DotAndDotDot, DotAndDotDotResult)); |
| 2624 | + EXPECT_TRUE(sameEntity(DotAndDotDot, DotAndDotDotResult)); |
| 2625 | + // Assert that '.' and '..' remain as path components. |
| 2626 | + ASSERT_TRUE(ContainsDotAndDotDot(DotAndDotDotResult)); |
| 2627 | + |
| 2628 | + // Case 4: Deep short 8.3 form path without \\?\ prefix. |
| 2629 | + SmallString<MAX_PATH> DeepResult; |
| 2630 | + ASSERT_FALSE(windows::makeLongFormPath(DeepShort, DeepResult)); |
| 2631 | + EXPECT_TRUE(sameEntity(DeepShort, DeepResult)); |
| 2632 | + EXPECT_FALSE(StringRef(DeepResult).starts_with(R"(\\?\)")) |
| 2633 | + << "Expected unprefixed result, got: " << DeepResult; |
| 2634 | + |
| 2635 | + // Case 5: Deep short 8.3 form path with \\?\ prefix. |
| 2636 | + SmallString<MAX_PATH> DeepPrefixedResult; |
| 2637 | + ASSERT_FALSE( |
| 2638 | + windows::makeLongFormPath(DeepShortWithPrefix, DeepPrefixedResult)); |
| 2639 | + EXPECT_TRUE(sameEntity(DeepShortWithPrefix, DeepPrefixedResult)); |
| 2640 | + EXPECT_TRUE(StringRef(DeepPrefixedResult).starts_with(R"(\\?\)")) |
| 2641 | + << "Expected prefixed result, got: " << DeepPrefixedResult; |
| 2642 | + |
| 2643 | + // Cleanup. |
| 2644 | + ASSERT_NO_ERROR(fs::remove_directories(TestDirectory.str())); |
| 2645 | +} |
| 2646 | + |
2488 | 2647 | // Windows refuses lock request if file region is already locked by the same |
2489 | 2648 | // process. POSIX system in this case updates the existing lock. |
2490 | 2649 | TEST_F(FileSystemTest, FileLocker) { |
|
0 commit comments