From 0a5efe84894e92668a1d34742ccbe358c2ce0971 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 10 Mar 2025 14:07:46 -0400 Subject: [PATCH] cmSystemTools: Fix GetRealPath implementation on Windows In commit 823e1df54c (cmSystemTools: Implement GetRealPath on Windows, 2024-11-04, v4.0.0-rc1~521^2~1) we implemented the POSIX behavior that resolves symlinks followed by '..' components. However, Windows just removes them lexically. Also, we were not handling all junction types. Instead, use `GetFinalPathNameByHandleW` via `uv_fs_realpath`. Note that we previously attempted this in commit 640709e7db (cmSystemTools: Implement GetRealPath on Windows, 2017-10-02, v3.11.0-rc1~445^2~1) but reverted it in commit 83630d4918 (cmSystemTools: Revert GetRealPath implementation on Windows, 2018-05-29, v3.11.3~3^2) due to resolving `subst` drives. This time, add code to re-`subst`itute the drive in the resolved path. Fixes: #26750 Issue: #17206 --- Source/cmSystemTools.cxx | 45 ++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index a0150e6a64..c2c5cd77cf 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1340,19 +1340,42 @@ std::string cmSystemTools::GetRealPath(std::string const& path, std::string* errorMessage) { #ifdef _WIN32 - std::string resolved_path; - using namespace cm::PathResolver; - // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath - static Resolver const resolver(RealOS); - cmsys::Status status = resolver.Resolve(path, resolved_path); - if (!status) { - if (errorMessage) { - *errorMessage = status.GetString(); - resolved_path.clear(); - } else { - resolved_path = path; + std::string resolved_path = + cmSystemTools::GetRealPathResolvingWindowsSubst(path, errorMessage); + + // If the original path used a subst drive and the real path starts + // with the substitution, restore the subst drive prefix. This may + // incorrectly restore a subst drive if the underlying drive was + // encountered via an absolute symlink, but this is an acceptable + // limitation to otherwise preserve susbt drives. + if (resolved_path.size() >= 2 && resolved_path[1] == ':' && + path.size() >= 2 && path[1] == ':' && + toupper(resolved_path[0]) != toupper(path[0])) { + // FIXME: Add thread_local or mutex if we use threads. + static std::map substMap; + char const drive = static_cast(toupper(path[0])); + std::string maybe_subst = cmStrCat(drive, ":/"); + auto smi = substMap.find(drive); + if (smi == substMap.end()) { + smi = substMap + .emplace( + drive, + cmSystemTools::GetRealPathResolvingWindowsSubst(maybe_subst)) + .first; + } + std::string const& resolved_subst = smi->second; + std::string::size_type const ns = resolved_subst.size(); + if (ns > 0) { + std::string::size_type const np = resolved_path.size(); + if (ns == np && resolved_path == resolved_subst) { + resolved_path = maybe_subst; + } else if (ns > 0 && ns < np && resolved_path[ns] == '/' && + resolved_path.compare(0, ns, resolved_subst) == 0) { + resolved_path.replace(0, ns + 1, maybe_subst); + } } } + return resolved_path; #else return cmsys::SystemTools::GetRealPath(path, errorMessage);