if (!ctx.ignore_header_paths.empty()) {
// Canonicalize path for comparison; Clang uses ./header.h.
- auto canonical_path_str =
+ const std::string& canonical_path_str =
path_str.str().starts_with("./") ? path_str.str().substr(2) : path_str;
-#ifdef _WIN32
- // Handle case-insensitive paths by converting to lowercase.
- canonical_path_str = util::to_lowercase(canonical_path_str);
-#endif
for (const auto& ignore_header_path : ctx.ignore_header_paths) {
if (file_path_matches_dir_prefix_or_file(ignore_header_path,
canonical_path_str)) {
bool
is_ccache_executable(const fs::path& path)
{
- std::string name = path.filename().string();
-#ifdef _WIN32
- name = util::to_lowercase(name);
-#endif
- return name.starts_with("ccache");
+ return util::path_component_starts_with_case_aware(path.filename(), "ccache");
}
bool
auto end = std::mismatch(dir_prefix_or_file.begin(),
dir_prefix_or_file.end(),
file_path.begin(),
- file_path.end())
+ file_path.end(),
+ util::path_components_equal_case_aware)
.first;
return end == dir_prefix_or_file.end() || end->empty();
}
util::logging::init(config.debug(), config.log_file());
ignore_header_paths =
util::split_path_list(config.ignore_headers_in_manifest());
-#ifdef _WIN32
- // Handle case-insensitive paths by converting to lowercase.
- for (auto& path : ignore_header_paths) {
- path = util::to_lowercase(path.string());
- }
-#endif
set_ignore_options(util::split_into_strings(config.ignore_options(), " "));
// Set default umask for all files created by ccache from now on (if
#include "path.hpp"
+#include <ccache/util/assertions.hpp>
#include <ccache/util/direntry.hpp>
#include <ccache/util/filesystem.hpp>
#include <ccache/util/format.hpp>
-#include <ccache/util/string.hpp>
+#include <ccache/util/wincompat.hpp>
#include <algorithm>
+#include <utility>
#ifdef _WIN32
const char k_dev_null_path[] = "nul:";
lexically_relative_case_aware(const fs::path& path, const fs::path& base)
{
#ifdef _WIN32
- // Note: Case-folding might in theory lead to an incorrect path on Windows
- // since not all filesystems are case-insensitive, but this is only done to
- // produce a candidate path that will be verified by the caller later.
- fs::path p = util::to_lowercase(path.string());
- fs::path b = util::to_lowercase(base.string());
- return p.lexically_relative(b);
+ // Note: Case-insensitive comparison might in theory lead to an incorrect path
+ // on Windows since not all filesystems are case-insensitive, but this is only
+ // done to produce a candidate path that will be verified by the caller later.
+ if (!util::path_components_equal_case_aware(path.root_name(),
+ base.root_name())
+ || path.is_absolute() != base.is_absolute()
+ || (!path.has_root_directory() && base.has_root_directory())) {
+ return {};
+ }
+
+ auto [path_it, base_it] =
+ std::mismatch(path.begin(),
+ path.end(),
+ base.begin(),
+ base.end(),
+ util::path_components_equal_case_aware);
+ if (path_it == path.end() && base_it == base.end()) {
+ return ".";
+ }
+
+ int num_parents = 0;
+ for (; base_it != base.end(); ++base_it) {
+ if (*base_it == "..") {
+ --num_parents;
+ } else if (*base_it != "." && !base_it->empty()) {
+ ++num_parents;
+ }
+ }
+ if (num_parents < 0) {
+ return {};
+ }
+
+ fs::path result;
+ for (int i = 0; i < num_parents; ++i) {
+ result /= "..";
+ }
+ for (; path_it != path.end(); ++path_it) {
+ result /= *path_it;
+ }
+ return result;
#else
return path.lexically_relative(base);
#endif
return path;
}
+bool
+path_components_equal_case_aware(const fs::path& component1,
+ const fs::path& component2)
+{
+#ifdef _WIN32
+ const auto& string1 = component1.native();
+ const auto& string2 = component2.native();
+ if (string1.empty() || string2.empty()) {
+ return string1.empty() && string2.empty();
+ }
+ if (!std::in_range<int>(string1.size())
+ || !std::in_range<int>(string2.size())) {
+ return false;
+ }
+ return CompareStringOrdinal(string1.data(),
+ static_cast<int>(string1.size()),
+ string2.data(),
+ static_cast<int>(string2.size()),
+ TRUE)
+ == CSTR_EQUAL;
+#else
+ return component1.native() == component2.native();
+#endif
+}
+
+bool
+path_component_starts_with_case_aware(const fs::path& component,
+ const fs::path& prefix)
+{
+ const auto& string = component.native();
+ const auto& prefix_string = prefix.native();
+ if (prefix_string.size() > string.size()) {
+ return false;
+ }
+#ifdef _WIN32
+ if (prefix_string.empty()) {
+ return true;
+ }
+ if (!std::in_range<int>(prefix_string.size())) {
+ return false;
+ }
+ return CompareStringOrdinal(string.data(),
+ static_cast<int>(prefix_string.size()),
+ prefix_string.data(),
+ static_cast<int>(prefix_string.size()),
+ TRUE)
+ == CSTR_EQUAL;
+#else
+ return string.starts_with(prefix_string);
+#endif
+}
+
bool
path_starts_with(const fs::path& path, const fs::path& prefix)
{
// Note: Not all paths on Windows are case insensitive, but for our purposes
// (checking whether a path is below the base directory) users will expect
// them to be.
- fs::path p1 = util::to_lowercase(util::lexically_normal(path).string());
- fs::path p2 = util::to_lowercase(util::lexically_normal(prefix).string());
+ fs::path p1 = util::lexically_normal(path);
+ fs::path p2 = util::lexically_normal(prefix);
#else
const fs::path& p1 = path;
const fs::path& p2 = prefix;
}
}
- return std::mismatch(p1.begin(), p1.end(), p2.begin(), p2_end).second
+ return std::mismatch(p1.begin(),
+ p1.end(),
+ p2.begin(),
+ p2_end,
+ util::path_components_equal_case_aware)
+ .second
== p2_end;
}
#pragma once
#include <ccache/util/pathstring.hpp>
-#ifdef _WIN32
-# include <ccache/util/string.hpp>
-#endif
#include <filesystem>
#include <string>
return (std::filesystem::path{} / ... / args).lexically_normal();
}
+// Return whether `component1` and `component2` are equal. Comparison is case
+// insensitive on Windows and case sensitive on other platforms.
+bool path_components_equal_case_aware(const std::filesystem::path& component1,
+ const std::filesystem::path& component2);
+
+// Return whether `component` starts with `prefix`. Comparison is case
+// insensitive on Windows and case sensitive on other platforms.
+bool
+path_component_starts_with_case_aware(const std::filesystem::path& component,
+ const std::filesystem::path& prefix);
+
// Return whether `path` starts with `prefix` considering path specifics on
// Windows.
bool path_starts_with(const std::filesystem::path& path,
{
return path == "/dev/null"
#ifdef _WIN32
- || util::to_lowercase(path.string()) == "nul"
+ || path_components_equal_case_aware(path, "nul")
#endif
;
}
#ifdef _WIN32
CHECK(file_path_matches_dir_prefix_or_file("\\aa", "\\aa\\bb"));
CHECK(file_path_matches_dir_prefix_or_file("\\aa\\", "\\aa\\bb"));
+ CHECK(file_path_matches_dir_prefix_or_file(
+ fs::path(L"C:\\\u00c5ngstr\u00f6m"),
+ fs::path(L"c:\\\u00e5NGSTR\u00d6M\\header.h")));
#else
CHECK(!file_path_matches_dir_prefix_or_file("\\aa", "\\aa\\bb"));
CHECK(!file_path_matches_dir_prefix_or_file("\\aa\\", "\\aa\\bb"));
#endif
}
+TEST_CASE("util::path_components_equal_case_aware")
+{
+ CHECK(util::path_components_equal_case_aware("", ""));
+ CHECK(util::path_components_equal_case_aware("foo", "foo"));
+ CHECK(!util::path_components_equal_case_aware("foo", "bar"));
+#ifdef _WIN32
+ CHECK(util::path_components_equal_case_aware("FOO", "foo"));
+ CHECK(util::path_components_equal_case_aware(fs::path(L"\u00c5"),
+ fs::path(L"\u00e5")));
+#else
+ CHECK(!util::path_components_equal_case_aware("FOO", "foo"));
+#endif
+}
+
+TEST_CASE("util::path_component_starts_with_case_aware")
+{
+ CHECK(util::path_component_starts_with_case_aware("foo", ""));
+ CHECK(util::path_component_starts_with_case_aware("foobar", "foo"));
+ CHECK(!util::path_component_starts_with_case_aware("foo", "foobar"));
+ CHECK(!util::path_component_starts_with_case_aware("foobar", "bar"));
+#ifdef _WIN32
+ CHECK(util::path_component_starts_with_case_aware("FOOBAR", "foo"));
+ CHECK(util::path_component_starts_with_case_aware(
+ fs::path(L"\u00c5ngstr\u00f6m"), fs::path(L"\u00e5NG")));
+#else
+ CHECK(!util::path_component_starts_with_case_aware("FOOBAR", "foo"));
+#endif
+}
+
TEST_CASE("util::lexically_normal")
{
CHECK(util::lexically_normal("") == "");
CHECK(make_relative_path(
lower_drive_cwd, lower_drive_cwd, upper_drive_cwd + "/nonexistent")
== "nonexistent");
+
+ const fs::path unicode_dir = fs::path(L"\u00c5ngstr\u00f6m");
+ const fs::path unicode_dir_case_variant = fs::path(L"\u00e5NGSTR\u00d6M");
+ REQUIRE(fs::create_directory(unicode_dir));
+ CHECK(make_relative_path(fs::path(actual_cwd) / unicode_dir,
+ fs::path(actual_cwd) / unicode_dir,
+ fs::path(actual_cwd) / unicode_dir_case_variant
+ / "nonexistent")
+ == "nonexistent");
}
#endif
CHECK(util::path_starts_with("c:/foo/bar", "C:\\FOO"));
CHECK(util::path_starts_with("c:/foo/bar/", "C:\\FOO"));
CHECK(util::path_starts_with("c:/foo/bar", "C:\\FOO\\"));
+ CHECK(util::path_starts_with(fs::path(L"C:\\\u00c5ngstr\u00f6m\\bar"),
+ fs::path(L"c:/\u00e5NGSTR\u00d6M")));
CHECK(!util::path_starts_with("C:\\foo\\bar", "/foo/baz"));
CHECK(!util::path_starts_with("C:\\foo\\bar", "C:/foo/baz"));
CHECK(!util::path_starts_with("C:\\beh\\foo", "/foo"));