Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vcpkg] Fix the case of current_path() before use on Windows. (#13537) #333

Merged
merged 1 commit into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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