diff --git a/include/bitcoin/system/unicode/utf8_everywhere/paths.hpp b/include/bitcoin/system/unicode/utf8_everywhere/paths.hpp
index b273e20689..8f8e18905c 100644
--- a/include/bitcoin/system/unicode/utf8_everywhere/paths.hpp
+++ b/include/bitcoin/system/unicode/utf8_everywhere/paths.hpp
@@ -37,6 +37,15 @@ BC_API std::filesystem::path to_path(const std::string& value) NOEXCEPT;
BC_API std::filesystem::path default_config_path(
const std::filesystem::path& subdirectory) NOEXCEPT;
+/// Get the module path (not implemented for non-Windows).
+BC_API std::filesystem::path module_path() NOEXCEPT;
+
+/// Fully qualify the path and return it as another.
+/// Not thread safe. If another thread calls SetCurrentDirectory during this
+/// call, the result may be corrupted as static storage is used for directory.
+BC_API std::filesystem::path qualified_path(
+ const std::filesystem::path& path) NOEXCEPT;
+
/// Extend the path and return it as another.
/// std::filesystem::path does not extend long paths, so we provide this.
/// Not thread safe. If another thread calls SetCurrentDirectory during this
diff --git a/src/unicode/utf8_everywhere/paths.cpp b/src/unicode/utf8_everywhere/paths.cpp
index cdcc138e9b..61602065fd 100644
--- a/src/unicode/utf8_everywhere/paths.cpp
+++ b/src/unicode/utf8_everywhere/paths.cpp
@@ -16,13 +16,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#include
+#include
#ifdef HAVE_MSC
#include
#include
#endif
#include
+#include
#include
#include
@@ -82,7 +83,7 @@ std::filesystem::path default_config_path(
#if defined(HAVE_MSC)
-// Helper for to_fully_qualified_path.
+// Helper for qualified_path.
inline auto replace_all(std::wstring text, wchar_t from, wchar_t to) NOEXCEPT
{
for (auto position = text.find(from); position != std::string::npos;
@@ -95,8 +96,7 @@ inline auto replace_all(std::wstring text, wchar_t from, wchar_t to) NOEXCEPT
};
// docs.microsoft.com/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
-static std::wstring to_fully_qualified_path(
- const std::filesystem::path& path) NOEXCEPT
+std::filesystem::path qualified_path(const std::filesystem::path& path) NOEXCEPT
{
// Separator normalization required by use of length extender.
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
@@ -121,7 +121,9 @@ static std::wstring to_fully_qualified_path(
// The returned size does not include the null terminator, and cannot
// exceed the original, but does become smaller, so resize accordingly.
+ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
return { directory.begin(), std::next(directory.begin(), size) };
+ BC_POP_WARNING()
}
std::filesystem::path extended_path(const std::filesystem::path& path) NOEXCEPT
@@ -129,17 +131,59 @@ std::filesystem::path extended_path(const std::filesystem::path& path) NOEXCEPT
// The length extension prefix works only with a fully-qualified path.
// However this includes "considered relative" paths (with ".." segments).
// That is of no consequence here because those will also be converted.
- const auto full = to_fully_qualified_path(path);
+ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+ const auto full = qualified_path(path).wstring();
return { (full.length() > MAX_PATH) ? L"\\\\?\\" + full : full };
+ BC_POP_WARNING()
+}
+
+std::filesystem::path module_path() NOEXCEPT
+{
+ BC_PUSH_WARNING(NO_CASTS_FOR_ARITHMETIC_CONVERSION)
+ constexpr auto max_path = static_cast(MAX_PATH);
+ BC_POP_WARNING()
+
+ for (auto size = max_path; !is_zero(size); size *= 2u)
+ {
+ std::vector buffer(size);
+ const auto length = ::GetModuleFileNameW(NULL, buffer.data(), size);
+ if (is_zero(length))
+ break;
+
+ if (length < size)
+ {
+ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+ return { buffer.begin(), std::next(buffer.begin(), length) };
+ BC_POP_WARNING()
+ }
+ }
+
+ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+ return {};
+ BC_POP_WARNING()
}
#else
+std::filesystem::path qualified_path(const std::filesystem::path& path) NOEXCEPT
+{
+ std::error_code ec{};
+ const auto full = std::filesystem::absolute(path, ec);
+ return ec ? path : full;
+}
+
std::filesystem::path extended_path(const std::filesystem::path& path) NOEXCEPT
{
return path;
}
+std::filesystem::path module_path() NOEXCEPT
+{
+ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+ return {};
+ BC_POP_WARNING()
+}
+
#endif // HAVE_MSC
} // namespace system
diff --git a/test/unicode/utf8_everywhere/environment.cpp b/test/unicode/utf8_everywhere/environment.cpp
index 5af6ba3025..600bc152bf 100644
--- a/test/unicode/utf8_everywhere/environment.cpp
+++ b/test/unicode/utf8_everywhere/environment.cpp
@@ -356,8 +356,6 @@ BOOST_AUTO_TEST_CASE(utf8_environment__allocate_environment_args__null_terminati
free_environment(narrow_args);
}
-// extended_path
-
#endif // HAVE_MSC
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/unicode/utf8_everywhere/paths.cpp b/test/unicode/utf8_everywhere/paths.cpp
index 65cbf5f9e7..d0aedfba5a 100644
--- a/test/unicode/utf8_everywhere/paths.cpp
+++ b/test/unicode/utf8_everywhere/paths.cpp
@@ -122,6 +122,63 @@ BOOST_AUTO_TEST_CASE(paths__to_path__always__expected)
BOOST_REQUIRE(result.u8string() == u8"\\\\?\\C:\\very\\long\\path\\exceeding\\MAX_PATH\\Unicode 文件名.txt");
}
+// The functions are not platform-specific, though the vectors are. Only win32
+// qualifies a path with a drive and extends one that exceeds the maximum.
+constexpr size_t long_path_size = 260;
+
+#if defined(HAVE_MSC)
+static const auto test_absolute = std::filesystem::path{ L"C:\\path\\file.ext" };
+static const auto test_long = L"C:\\" + std::wstring(long_path_size, L'x');
+static const auto test_extended = std::filesystem::path{ L"\\\\?\\" + test_long };
+#else
+static const auto test_absolute = std::filesystem::path{ "/path/file.ext" };
+static const auto test_long = "/" + std::string(long_path_size, 'x');
+static const auto test_extended = std::filesystem::path{ test_long };
+#endif
+
+// module_path
+
+BOOST_AUTO_TEST_CASE(paths__module_path__always__expected)
+{
+ const auto module = module_path();
+
+#if defined(HAVE_MSC)
+ // The module is the test executable, already qualified and existing.
+ BOOST_REQUIRE(!module.empty());
+ BOOST_REQUIRE(module.is_absolute());
+ BOOST_REQUIRE(module.has_filename());
+ BOOST_REQUIRE(std::filesystem::exists(module));
+ BOOST_REQUIRE_EQUAL(qualified_path(module), module);
+#else
+ // Not implemented for non-Windows.
+ BOOST_REQUIRE(module.empty());
+#endif
+}
+
+// qualified_path
+
+BOOST_AUTO_TEST_CASE(paths__qualified_path__absolute__unchanged)
+{
+ BOOST_REQUIRE_EQUAL(qualified_path(test_absolute), test_absolute);
+}
+
+BOOST_AUTO_TEST_CASE(paths__qualified_path__long_absolute__unextended)
+{
+ BOOST_REQUIRE_EQUAL(qualified_path({ test_long }), std::filesystem::path{ test_long });
+}
+
+// extended_path
+
+BOOST_AUTO_TEST_CASE(paths__extended_path__absolute__unchanged)
+{
+ BOOST_REQUIRE_EQUAL(extended_path(test_absolute), test_absolute);
+}
+
+BOOST_AUTO_TEST_CASE(paths__extended_path__long_absolute__expected)
+{
+ BOOST_REQUIRE_EQUAL(extended_path({ test_long }), test_extended);
+}
+
BOOST_AUTO_TEST_SUITE_END()
BC_POP_WARNING()