Skip to content
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
9 changes: 9 additions & 0 deletions include/bitcoin/system/unicode/utf8_everywhere/paths.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 49 additions & 5 deletions src/unicode/utf8_everywhere/paths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bitcoin/system/unicode/utf8_everywhere/environment.hpp>
#include <bitcoin/system/unicode/utf8_everywhere/paths.hpp>

#ifdef HAVE_MSC
#include <shlobj.h>
#include <windows.h>
#endif
#include <filesystem>
#include <system_error>
#include <bitcoin/system/define.hpp>
#include <bitcoin/system/unicode/conversion.hpp>

Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -121,25 +121,69 @@ 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
{
// 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<DWORD>(MAX_PATH);
BC_POP_WARNING()

for (auto size = max_path; !is_zero(size); size *= 2u)
{
std::vector<wchar_t> 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
Expand Down
2 changes: 0 additions & 2 deletions test/unicode/utf8_everywhere/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
57 changes: 57 additions & 0 deletions test/unicode/utf8_everywhere/paths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading