Skip to content

Commit

Permalink
[vcpkg] Fix the case of current_path() before use on Windows. (#13537)
Browse files Browse the repository at this point in the history
* Fix the case of current_path() before use on Windows.

This is a better solution than that I tried in #13144 -- rather than trying to cannoicalize the path completely, which would destroy symlinks etc; this form  calls FindFirstFile on each path element to get the real case from the filesystem.

Fixes #13105.

(Note the wrong case in the CWD in the prompt, but the correct case in all the output:)

PS C:\DeV\vcPKG> .\vcpkg.exe install curl
Computing installation plan...
The following packages will be built and installed:
    curl[core,non-http,schannel,ssl,sspi,winssl]:x86-windows
  * zlib[core]:x86-windows
Additional packages (*) will be modified to complete this operation.
Detecting compiler hash for triplet x86-windows...
Starting package 1/2: zlib:x86-windows
Building package zlib[core]:x86-windows...
Using cached binary package: C:\Users\billy\AppData\Local\vcpkg/archives\c6\c61dd1bcc23348934c55f4ced77f1e4b0f353394.zipBuilding package zlib[core]:x86-windows... done
Installing package zlib[core]:x86-windows...
Installing package zlib[core]:x86-windows... done
Elapsed time for package zlib:x86-windows: 62.26 ms
Starting package 2/2: curl:x86-windows
Building package curl[core,non-http,schannel,ssl,sspi,winssl]:x86-windows...
Could not locate cached archive: C:\Users\billy\AppData\Local\vcpkg/archives\9f\9fa16d473c9539e9ea7ab3922eff46618f3a4c4b.zip
-- Downloading https://github.com/curl/curl/archive/9d954e49bce3706a9a2efb119ecd05767f0f2a9e.tar.gz...
-- Extracting source C:/Dev/vcpkg/downloads/curl-curl-9d954e49bce3706a9a2efb119ecd05767f0f2a9e.tar.gz
-- Applying patch 0002_fix_uwp.patch
-- Applying patch 0004_nghttp2_staticlib.patch
-- Applying patch 0005_remove_imp_suffix.patch
-- Applying patch 0006_fix_tool_depends.patch
-- Applying patch 0007_disable_tool_export_curl_target.patch
-- Applying patch 0009_fix_openssl_config.patch
-- Applying patch 0010_fix_othertests_cmake.patch
-- Applying patch 0011_fix_static_build.patch
-- Using source at C:/Dev/vcpkg/buildtrees/curl/src/767f0f2a9e-91d24adee1.clean
-- Configuring x86-windows
-- Building x86-windows-dbg
-- Building x86-windows-rel
-- Installing: C:/Dev/vcpkg/packages/curl_x86-windows/share/curl/curl-config
-- Installing: C:/Dev/vcpkg/packages/curl_x86-windows/share/curl/vcpkg-cmake-wrapper.cmake
-- Installing: C:/Dev/vcpkg/packages/curl_x86-windows/share/curl/copyright
-- Performing post-build validation
-- Performing post-build validation done
Stored binary cache: C:\Users\billy\AppData\Local\vcpkg/archives\9f\9fa16d473c9539e9ea7ab3922eff46618f3a4c4b.zip
Building package curl[core,non-http,schannel,ssl,sspi,winssl]:x86-windows... done
Installing package curl[core,non-http,schannel,ssl,sspi,winssl]:x86-windows...
Installing package curl[core,non-http,schannel,ssl,sspi,winssl]:x86-windows... done
Elapsed time for package curl:x86-windows: 1.299 min

Total elapsed time: 1.33 min

The package curl:x86-windows provides CMake targets:

    find_package(CURL CONFIG REQUIRED)
    target_link_libraries(main PRIVATE CURL::libcurl)

PS C:\DeV\vcPKG>

* Fix *nix builds.

* PR feedback.

* Update toolsrc/src/vcpkg/base/files.cpp

Co-authored-by: ras0219 <533828+ras0219@users.noreply.github.com>
  • Loading branch information
BillyONeal and ras0219 authored Sep 23, 2020
1 parent 1095c17 commit 5d33bd3
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 18 deletions.
4 changes: 4 additions & 0 deletions toolsrc/include/vcpkg/base/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,8 @@ namespace vcpkg::Files
/// Performs "lhs / rhs" according to the C++17 Filesystem Library Specification.
/// This function exists as a workaround for TS implementations.
fs::path combine(const fs::path& lhs, const fs::path& rhs);

#if defined(_WIN32)
fs::path win32_fix_path_case(const fs::path& source);
#endif // _WIN32
}
56 changes: 56 additions & 0 deletions toolsrc/src/vcpkg-test/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,62 @@ TEST_CASE ("remove all", "[files]")
CHECK_EC_ON_FILE(temp_dir, ec);
}

#if defined(_WIN32)
TEST_CASE ("win32_fix_path_case", "[files]")
{
using vcpkg::Files::win32_fix_path_case;

// This test assumes that the Windows directory is C:\Windows

CHECK(win32_fix_path_case(L"") == L"");

CHECK(win32_fix_path_case(L"C:") == L"C:");
CHECK(win32_fix_path_case(L"c:") == L"C:");
CHECK(win32_fix_path_case(L"C:/") == L"C:\\");
CHECK(win32_fix_path_case(L"C:\\") == L"C:\\");
CHECK(win32_fix_path_case(L"c:\\") == L"C:\\");
CHECK(win32_fix_path_case(L"C:\\WiNdOws") == L"C:\\Windows");
CHECK(win32_fix_path_case(L"c:\\WiNdOws\\") == L"C:\\Windows\\");
CHECK(win32_fix_path_case(L"C://///////WiNdOws") == L"C:\\Windows");
CHECK(win32_fix_path_case(L"c:\\/\\/WiNdOws\\/") == L"C:\\Windows\\");

auto& fs = vcpkg::Files::get_real_filesystem();
auto original_cwd = fs.current_path(VCPKG_LINE_INFO);
fs.current_path(L"C:\\", VCPKG_LINE_INFO);
CHECK(win32_fix_path_case(L"\\") == L"\\");
CHECK(win32_fix_path_case(L"\\/\\WiNdOws") == L"\\Windows");
CHECK(win32_fix_path_case(L"\\WiNdOws") == L"\\Windows");
CHECK(win32_fix_path_case(L"\\WiNdOws") == L"\\Windows");
CHECK(win32_fix_path_case(L"c:WiNdOws") == L"C:Windows");
CHECK(win32_fix_path_case(L"c:WiNdOws/system32") == L"C:Windows\\System32");
fs.current_path(original_cwd, VCPKG_LINE_INFO);

fs.create_directories("SuB/Dir/Ectory", VCPKG_LINE_INFO);
CHECK(win32_fix_path_case(L"sub") == L"SuB");
CHECK(win32_fix_path_case(L"SUB") == L"SuB");
CHECK(win32_fix_path_case(L"sub/") == L"SuB\\");
CHECK(win32_fix_path_case(L"sub/dir") == L"SuB\\Dir");
CHECK(win32_fix_path_case(L"sub/dir/") == L"SuB\\Dir\\");
CHECK(win32_fix_path_case(L"sub/dir/ectory") == L"SuB\\Dir\\Ectory");
CHECK(win32_fix_path_case(L"sub/dir/ectory/") == L"SuB\\Dir\\Ectory\\");
fs.remove_all("SuB", VCPKG_LINE_INFO);

CHECK(win32_fix_path_case(L"//nonexistent_server\\nonexistent_share\\") ==
L"\\\\nonexistent_server\\nonexistent_share\\");
CHECK(win32_fix_path_case(L"\\\\nonexistent_server\\nonexistent_share\\") ==
L"\\\\nonexistent_server\\nonexistent_share\\");
CHECK(win32_fix_path_case(L"\\\\nonexistent_server\\nonexistent_share") ==
L"\\\\nonexistent_server\\nonexistent_share");

CHECK(win32_fix_path_case(L"///three_slashes_not_a_server\\subdir\\") == L"\\three_slashes_not_a_server\\subdir\\");

CHECK(win32_fix_path_case(L"\\??\\c:\\WiNdOws") == L"\\??\\c:\\WiNdOws");
CHECK(win32_fix_path_case(L"\\\\?\\c:\\WiNdOws") == L"\\\\?\\c:\\WiNdOws");
CHECK(win32_fix_path_case(L"\\\\.\\c:\\WiNdOws") == L"\\\\.\\c:\\WiNdOws");
CHECK(win32_fix_path_case(L"c:\\/\\/Nonexistent\\/path/here") == L"C:\\Nonexistent\\path\\here");
}
#endif // _WIN32

#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
TEST_CASE ("remove all -- benchmarks", "[files][!benchmark]")
{
Expand Down
180 changes: 178 additions & 2 deletions toolsrc/src/vcpkg/base/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,93 @@
#include <vcpkg/base/system.process.h>
#include <vcpkg/base/util.h>

#if !defined(_WIN32)
#if defined(_WIN32)
#include <vcpkg/base/system_headers.h>
#else // ^^^ _WIN32 // !_WIN32 vvv
#include <fcntl.h>

#include <sys/file.h>
#include <sys/stat.h>
#endif
#endif // _WIN32

#if defined(__linux__)
#include <sys/sendfile.h>
#elif defined(__APPLE__)
#include <copyfile.h>
#endif // ^^^ defined(__APPLE__)

#include <algorithm>
#include <string>

#if defined(_WIN32)
namespace
{
struct IsSlash
{
bool operator()(const wchar_t c) const noexcept { return c == L'/' || c == L'\\'; }
};

constexpr IsSlash is_slash;

template<size_t N>
bool wide_starts_with(const std::wstring& haystack, const wchar_t (&needle)[N]) noexcept
{
const size_t without_null = N - 1;
return haystack.size() >= without_null && std::equal(needle, needle + without_null, haystack.begin());
}

bool starts_with_drive_letter(std::wstring::const_iterator first, const std::wstring::const_iterator last) noexcept
{
if (last - first < 2)
{
return false;
}

if (!(first[0] >= L'a' && first[0] <= L'z') && !(first[0] >= L'A' && first[0] <= L'Z'))
{
return false;
}

if (first[1] != L':')
{
return false;
}

return true;
}

struct FindFirstOp
{
HANDLE h_find = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW find_data;

unsigned long find_first(const wchar_t* const path) noexcept
{
assert(h_find == INVALID_HANDLE_VALUE);
h_find = FindFirstFileW(path, &find_data);
if (h_find == INVALID_HANDLE_VALUE)
{
return GetLastError();
}

return ERROR_SUCCESS;
}

FindFirstOp() = default;
FindFirstOp(const FindFirstOp&) = delete;
FindFirstOp& operator=(const FindFirstOp&) = delete;

~FindFirstOp()
{
if (h_find != INVALID_HANDLE_VALUE)
{
(void)FindClose(h_find);
}
}
};
} // unnamed namespace
#endif // _WIN32

fs::path fs::u8path(vcpkg::StringView s)
{
#if defined(_WIN32)
Expand Down Expand Up @@ -1197,4 +1271,106 @@ namespace vcpkg::Files
#endif // ^^^ windows
#endif // ^^^ std::experimental::filesystem
}

#ifdef _WIN32
fs::path win32_fix_path_case(const fs::path& source)
{
const std::wstring& native = source.native();
if (native.empty())
{
return fs::path{};
}

if (wide_starts_with(native, L"\\\\?\\") || wide_starts_with(native, L"\\??\\") ||
wide_starts_with(native, L"\\\\.\\"))
{
// no support to attempt to fix paths in the NT, \\GLOBAL??, or device namespaces at this time
return source;
}

const auto last = native.end();
auto first = native.begin();
auto is_wildcard = [](wchar_t c) { return c == L'?' || c == L'*'; };
if (std::any_of(first, last, is_wildcard))
{
Checks::exit_with_message(
VCPKG_LINE_INFO, "Attempt to fix case of a path containing wildcards: %s", fs::u8string(source));
}

std::wstring in_progress;
in_progress.reserve(native.size());
if (last - first >= 3 && is_slash(first[0]) && is_slash(first[1]) && !is_slash(first[2]))
{
// path with UNC prefix \\server\share; this will be rejected by FindFirstFile so we skip over that
in_progress.push_back(L'\\');
in_progress.push_back(L'\\');
first += 2;
auto next_slash = std::find_if(first, last, is_slash);
in_progress.append(first, next_slash);
in_progress.push_back(L'\\');
first = std::find_if_not(next_slash, last, is_slash);
next_slash = std::find_if(first, last, is_slash);
in_progress.append(first, next_slash);
first = std::find_if_not(next_slash, last, is_slash);
if (first != next_slash)
{
in_progress.push_back(L'\\');
}
}
else if (last - first >= 1 && is_slash(first[0]))
{
// root relative path
in_progress.push_back(L'\\');
first = std::find_if_not(first, last, is_slash);
}
else if (starts_with_drive_letter(first, last))
{
// path with drive letter root
auto letter = first[0];
if (letter >= L'a' && letter <= L'z')
{
letter = letter - L'a' + L'A';
}

in_progress.push_back(letter);
in_progress.push_back(L':');
first += 2;
if (first != last && is_slash(*first))
{
// absolute path
in_progress.push_back(L'\\');
first = std::find_if_not(first, last, is_slash);
}
}

assert(!fs::path(first, last).has_root_path());

while (first != last)
{
auto next_slash = std::find_if(first, last, is_slash);
auto original_size = in_progress.size();
in_progress.append(first, next_slash);
FindFirstOp this_find;
unsigned long last_error = this_find.find_first(in_progress.c_str());
if (last_error == ERROR_SUCCESS)
{
in_progress.resize(original_size);
in_progress.append(this_find.find_data.cFileName);
}
else
{
// we might not have access to this intermediate part of the path;
// just guess that the case of that element is correct and move on
}

first = std::find_if_not(next_slash, last, is_slash);
if (first != next_slash)
{
in_progress.push_back(L'\\');
}
}

return fs::path(std::move(in_progress));
}
#endif // _WIN32
}
24 changes: 8 additions & 16 deletions toolsrc/src/vcpkg/vcpkgpaths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,13 @@ namespace
Files::Filesystem& filesystem, const fs::path& root, std::string* option, StringLiteral name, LineInfo li)
{
auto result = process_output_directory_impl(filesystem, root, option, name, li);
#if defined(_WIN32)
result = vcpkg::Files::win32_fix_path_case(result);
#endif // _WIN32
Debug::print("Using ", name, "-root: ", fs::u8string(result), '\n');
return result;
}

void uppercase_win32_drive_letter(fs::path& path)
{
#if defined(_WIN32)
const auto& nativePath = path.native();
if (nativePath.size() > 2 && (nativePath[0] >= L'a' && nativePath[0] <= L'z') && nativePath[1] == L':')
{
auto uppercaseFirstLetter = std::move(path).native();
uppercaseFirstLetter[0] = nativePath[0] - L'a' + L'A';
path = uppercaseFirstLetter;
}
#endif
(void)path;
}

} // unnamed namespace

namespace vcpkg
Expand Down Expand Up @@ -225,6 +214,10 @@ namespace vcpkg
: m_pimpl(std::make_unique<details::VcpkgPathsImpl>(filesystem, args.compiler_tracking_enabled()))
{
original_cwd = filesystem.current_path(VCPKG_LINE_INFO);
#if defined(_WIN32)
original_cwd = vcpkg::Files::win32_fix_path_case(original_cwd);
#endif // _WIN32

if (args.vcpkg_root_dir)
{
root = filesystem.canonical(VCPKG_LINE_INFO, fs::u8path(*args.vcpkg_root_dir));
Expand All @@ -238,7 +231,7 @@ namespace vcpkg
filesystem.canonical(VCPKG_LINE_INFO, System::get_exe_path_of_current_process()), ".vcpkg-root");
}
}
uppercase_win32_drive_letter(root);

Checks::check_exit(VCPKG_LINE_INFO, !root.empty(), "Error: Could not detect vcpkg-root.");
Debug::print("Using vcpkg-root: ", fs::u8string(root), '\n');

Expand All @@ -252,7 +245,6 @@ namespace vcpkg
{
manifest_root_dir = filesystem.find_file_recursively_up(original_cwd, fs::u8path("vcpkg.json"));
}
uppercase_win32_drive_letter(manifest_root_dir);

if (!manifest_root_dir.empty() && manifest_mode_on)
{
Expand Down

0 comments on commit 5d33bd3

Please sign in to comment.