From 9baf48a6830f778015c6ff9f6fa5cee103b1c040 Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Wed, 10 Aug 2022 13:43:39 +0300 Subject: [PATCH] Added a new Filesystem patch to fix weakly_canonical on Windows. --- feed/history/boost_1_80_0.qbk | 2 + ...stem-fix-weakly-canonical-long-paths.patch | 171 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 patches/1_80_0/0002-filesystem-fix-weakly-canonical-long-paths.patch diff --git a/feed/history/boost_1_80_0.qbk b/feed/history/boost_1_80_0.qbk index bed5f91f..ce019f28 100644 --- a/feed/history/boost_1_80_0.qbk +++ b/feed/history/boost_1_80_0.qbk @@ -26,6 +26,8 @@ in the release. * Boost.Filesystem directory iterators may fail to construct for a network share on Windows prior to 10, see [github_pr filesystem 246] and [github filesystem 245]. [@/patches/1_80_0/0001-filesystem-win-fix-dir-it-net-share.patch Patch]. +* In Boost.Filesystem on Windows, `weakly_canonical` fails to process paths that start with the "\\\\?\\" prefix, see [github filesystem 247]. + [@/patches/1_80_0/0002-filesystem-fix-weakly-canonical-long-paths.patch Patch]. [endsect] diff --git a/patches/1_80_0/0002-filesystem-fix-weakly-canonical-long-paths.patch b/patches/1_80_0/0002-filesystem-fix-weakly-canonical-long-paths.patch new file mode 100644 index 00000000..9312059a --- /dev/null +++ b/patches/1_80_0/0002-filesystem-fix-weakly-canonical-long-paths.patch @@ -0,0 +1,171 @@ +commit 476ca7b6c1d37a5d796f8525813a9a64c9e54ffc +Author: Andrey Semashev +Date: Wed Aug 10 04:57:21 2022 +0300 + + Fix weakly_canonical on Windows with long paths prefix. + + During its operation, weakly_canonical would call status() on the path + consisting only from the root name of the input path. This would fail + with ERROR_INVALID_FUNCTION if the root name starts with the "\\?\" prefix, + as the root name path is not absolute. + + To fix this, we don't check the status of the root name path (which is + not the correct check anyways as it tests the current directory on the + corresponding drive for existence, which is not what we want). Additionally, + avoid calling status() on the paths containing dot and dot-dot elements + during the weakly_canonical execution for the same reason - the "\\?\" + prefix disables most of the path processing in Windows APIs, including + dot and dot-dot elements resolution. + + Fixes https://github.com/boostorg/filesystem/issues/247. + +diff --git a/libs/filesystem/src/operations.cpp b/libs/filesystem/src/operations.cpp +index dd636e9..ca2fff3 100644 +--- a/libs/filesystem/src/operations.cpp ++++ b/libs/filesystem/src/operations.cpp +@@ -4434,7 +4434,7 @@ path weakly_canonical(path const& p, path const& base, system::error_code* ec) + path head(p); + for (; !head.empty(); --itr) + { +- file_status head_status = detail::status_impl(head, &local_ec); ++ file_status head_status(detail::status_impl(head, &local_ec)); + if (BOOST_UNLIKELY(head_status.type() == fs::status_error)) + { + if (!ec) +@@ -4450,32 +4450,83 @@ path weakly_canonical(path const& p, path const& base, system::error_code* ec) + head.remove_filename(); + } + ++ if (head.empty()) ++ return p.lexically_normal(); ++ ++ path const& dot_p = dot_path(); ++ path const& dot_dot_p = dot_dot_path(); ++ + #else + +- // On Windows, filesystem APIs such as GetFileAttributesW perform lexical path normalization internally. +- // As a result, a path like "c:\a\.." can be reported as present even if "c:\a" is not. This would break +- // canonical, as symlink_status that it calls internally would report an error that the file at the intermediate +- // path does not exist. To avoid this, scan the initial path in the forward direction. +- // Also, operate on paths with preferred separators. This can be important on Windows since GetFileAttributesW, +- // which is called in status() may return "file not found" for paths to network shares and mounted cloud +- // storages that have forward slashes as separators. ++ // On Windows, filesystem APIs such as GetFileAttributesW and CreateFileW perform lexical path normalization ++ // internally. As a result, a path like "c:\a\.." can be reported as present even if "c:\a" is not. This would ++ // break canonical, as symlink_status that it calls internally would report an error that the file at the ++ // intermediate path does not exist. To avoid this, scan the initial path in the forward direction. ++ // Also, operate on paths with preferred separators. This can be important on Windows since GetFileAttributesW ++ // or CreateFileW, which is called in status() may return "file not found" for paths to network shares and ++ // mounted cloud storages that have forward slashes as separators. ++ // Also, avoid querying status of the root name such as \\?\c: as CreateFileW returns ERROR_INVALID_FUNCTION for ++ // such path. Querying the status of a root name such as c: is also not right as this path refers to the current ++ // directory on drive C:, which is not what we want to test for existence anyway. + path::iterator itr(p.begin()); + path head; +- for (; itr != p_end; ++itr) ++ if (p.has_root_name()) + { +- path const& p_elem = *itr; +- if (p_elem.size() == 1u && detail::is_directory_separator(p_elem.native()[0])) ++ BOOST_ASSERT(itr != p_end); ++ head = *itr; ++ ++itr; ++ } ++ ++ if (p.has_root_directory()) ++ { ++ BOOST_ASSERT(itr != p_end); ++ // Convert generic separator returned by the iterator for the root directory to ++ // the preferred separator. ++ head += path::preferred_separator; ++ ++itr; ++ } ++ ++ if (!head.empty()) ++ { ++ file_status head_status(detail::status_impl(head, &local_ec)); ++ if (BOOST_UNLIKELY(head_status.type() == fs::status_error)) + { +- // Convert generic separator returned by the iterator for the root directory to +- // the preferred separator. +- head += path::preferred_separator; ++ if (!ec) ++ BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::weakly_canonical", head, local_ec)); ++ ++ *ec = local_ec; ++ return path(); + } +- else ++ ++ if (head_status.type() == fs::file_not_found) ++ { ++ // If the root path does not exist then no path element exists ++ return p.lexically_normal(); ++ } ++ } ++ ++ path const& dot_p = dot_path(); ++ path const& dot_dot_p = dot_dot_path(); ++ for (; itr != p_end; ++itr) ++ { ++ path const& p_elem = *itr; ++ ++ // Avoid querying status of paths containing dot and dot-dot elements, as this will break ++ // if the root name starts with "\\?\". ++ if (p_elem == dot_p) ++ continue; ++ ++ if (p_elem == dot_dot_p) + { +- head /= p_elem; ++ if (head.has_relative_path()) ++ head.remove_filename(); ++ ++ continue; + } + +- file_status head_status = detail::status_impl(head, &local_ec); ++ head /= p_elem; ++ ++ file_status head_status(detail::status_impl(head, &local_ec)); + if (BOOST_UNLIKELY(head_status.type() == fs::status_error)) + { + if (!ec) +@@ -4492,33 +4543,22 @@ path weakly_canonical(path const& p, path const& base, system::error_code* ec) + } + } + ++ if (head.empty()) ++ return p.lexically_normal(); ++ + #endif + +- path const& dot_p = dot_path(); +- path const& dot_dot_p = dot_dot_path(); + path tail; + bool tail_has_dots = false; + for (; itr != p_end; ++itr) + { + path const& tail_elem = *itr; +-#if defined(BOOST_WINDOWS_API) +- if (tail_elem.size() == 1u && detail::is_directory_separator(tail_elem.native()[0])) +- { +- // Convert generic separator returned by the iterator for the root directory to +- // the preferred separator. +- tail += path::preferred_separator; +- continue; +- } +-#endif + tail /= tail_elem; + // for a later optimization, track if any dot or dot-dot elements are present + if (!tail_has_dots && (tail_elem == dot_p || tail_elem == dot_dot_p)) + tail_has_dots = true; + } + +- if (head.empty()) +- return p.lexically_normal(); +- + head = detail::canonical(head, base, &local_ec); + if (BOOST_UNLIKELY(!!local_ec)) + {