diff --git a/README.md b/README.md index 5ada1c2..369f26c 100644 --- a/README.md +++ b/README.md @@ -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), diff --git a/include/ghc/filesystem.hpp b/include/ghc/filesystem.hpp index 7eea0ce..c10f810 100644 --- a/include/ghc/filesystem.hpp +++ b/include/ghc/filesystem.hpp @@ -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 } } diff --git a/test/filesystem_test.cpp b/test/filesystem_test.cpp index 649f1d7..eb3ad37 100644 --- a/test/filesystem_test.cpp +++ b/test/filesystem_test.cpp @@ -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));