Resolve copy_symlink() handling of relative and dangling targets on Windows, refactor create_symlink for clarity, refs #211

This commit is contained in:
Steffen Schümann 2026-07-19 10:12:55 +02:00
parent 29b5acd3d6
commit dded74dfb5
3 changed files with 49 additions and 12 deletions

View File

@ -673,6 +673,8 @@ to the expected behavior.
UTF-16 no longer drops subsequent valid code units.
* Fix for [#210](https://github.com/gulrak/filesystem/issues/210),
`equivalent()` now compares only filesystem identity.
* Fix for [#211](https://github.com/gulrak/filesystem/issues/211), Windows
`copy_symlink()` now resolves relative targets from the source link.
* Fix for [#178](https://github.com/gulrak/filesystem/issues/178), recursive
iteration no longer resolves symlinks unless requested.
* Fix for [#185](https://github.com/gulrak/filesystem/issues/185),

View File

@ -1938,14 +1938,8 @@ GHC_INLINE std::string systemErrorText(ErrorNumber code = 0)
using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD);
using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec)
GHC_INLINE void create_symlink_unchecked(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec)
{
std::error_code tec;
auto fs = status(target_name, tec);
if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) {
ec = detail::make_error_code(detail::portable_error::not_supported);
return;
}
#if defined(__GNUC__) && __GNUC__ >= 8 || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
@ -1973,6 +1967,17 @@ GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink,
}
}
GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec)
{
std::error_code tec;
auto fs = status(target_name, tec);
if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) {
ec = detail::make_error_code(detail::portable_error::not_supported);
return;
}
create_symlink_unchecked(target_name, new_symlink, to_directory, ec);
}
GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec)
{
#if defined(__GNUC__) && __GNUC__ >= 8 || defined(__clang__)
@ -4085,12 +4090,15 @@ GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symli
ec.clear();
auto to = read_symlink(existing_symlink, ec);
if (!ec) {
if (exists(to, ec) && is_directory(to, ec)) {
create_directory_symlink(to, new_symlink, ec);
}
else {
create_symlink(to, new_symlink, ec);
#ifdef GHC_OS_WINDOWS
auto resolved_to = to.is_absolute() ? to : existing_symlink.parent_path() / to;
auto to_directory = exists(resolved_to, ec) && is_directory(resolved_to, ec);
if (!ec) {
detail::create_symlink_unchecked(to, new_symlink, to_directory, ec);
}
#else
create_symlink(to, new_symlink, ec);
#endif
}
}

View File

@ -1918,6 +1918,33 @@ TEST_CASE("fs.op.copy_symlink - copy_symlink", "[filesystem][operations][fs.op.c
CHECK_NOTHROW(fs::copy_symlink("sdir", "sdirc2", ec));
CHECK(fs::exists("sdirc2"));
CHECK(!ec);
fs::create_directories("links/dir-target");
generateFile("links/file-target");
fs::create_directory_symlink("dir-target", "links/relative-dir-link");
fs::create_symlink("file-target", "links/relative-file-link");
fs::create_directory("file-target");
CHECK_NOTHROW(fs::copy_symlink("links/relative-dir-link", "links/relative-dir-copy"));
CHECK(fs::is_directory("links/relative-dir-copy"));
CHECK(fs::read_symlink("links/relative-dir-copy") == fs::path("dir-target"));
ec = std::make_error_code(std::errc::invalid_argument);
CHECK_NOTHROW(fs::copy_symlink("links/relative-file-link", "links/relative-file-copy", ec));
CHECK(!ec);
CHECK(fs::is_regular_file("links/relative-file-copy"));
CHECK(fs::read_symlink("links/relative-file-copy") == fs::path("file-target"));
fs::create_symlink("missing-target", "links/dangling-link");
CHECK_NOTHROW(fs::copy_symlink("links/dangling-link", "links/dangling-copy", ec));
CHECK(!ec);
CHECK(fs::is_symlink("links/dangling-copy"));
CHECK(fs::read_symlink("links/dangling-copy") == fs::path("missing-target"));
fs::create_symlink(fs::absolute("foo"), "links/absolute-link");
CHECK_NOTHROW(fs::copy_symlink("links/absolute-link", "links/absolute-copy"));
CHECK(fs::is_regular_file("links/absolute-copy"));
CHECK(fs::read_symlink("links/absolute-copy") == fs::absolute("foo"));
}
CHECK_THROWS_AS(fs::copy_symlink("bar", "barc"), fs::filesystem_error);
CHECK_NOTHROW(fs::copy_symlink("bar", "barc", ec));