On Sat, Jun 27, 2026 at 1:14 PM Adam Wood <[email protected]> wrote:
> > > On Sat, Jun 27, 2026 at 4:56 AM Jonathan Wakely <[email protected]> > wrote: > >> On Fri, 26 Jun 2026 at 19:15, Jonathan Wakely <[email protected]> wrote: >> > >> > On Fri, 26 Jun 2026 at 17:59, Jonathan Wakely <[email protected]> >> wrote: >> > > >> > > From: Adam Wood <[email protected]> >> > > >> > > Tested on x86_64-w64-mingw32 on Windows 11. >> > > >> > > The the new symlink functions are only defined/enabled on versions of >> > > Windows with symlink support. >> > > >> > > Because path resolution may be different between Windows and POSIX >> when a >> > > dotdot follows a symlink, I also made some edits to tests involving >> that >> > > specific case. >> > > >> > > Finally, I dealt with the issue that creating symlinks with the >> > > unprivileged flag returns an error in earlier versions of Windows by >> > > just trying again if ERROR_INVALID_PARAMETER occurs without the >> > > unprivileged flag. This is how MSVC STL does it as well. >> > > >> > > The patch does not support junctions or mount points. >> > > >> > > libstdc++-v3/Changelog: >> > > >> > > * src/c++17/fs_ops.cc: Include <winioctl.h> for >> > > FSCTL_GET_REPARSE_POINT. Include <ntdef.h> for >> > > REPARSE_DATA_BUFFER. >> > > (windows_create_symlink): New helper function for >> > > fs::create_symlink and fs::create_directory_symlink. >> > > (fs::create_directory_symlink): Call windows_create_symlink on >> > > Windows. >> > > (fs::create_symlink): Call windows_create_symlink on Windows. >> > > (auto_win_file_handle::auto_win_file_handle): Add >> follow_symlink >> > > parameter to control whether the handle should open the >> symlink >> > > or the target, with a default value of true. >> > > (windows_read_symlink_handle): New helper function >> > > for fs::read_symlink. >> > > (fs::read_symlink): Call windows_read_symlink_handle on >> Windows. >> > > (fs::remove): Call RemoveDirectoryW only for directories, and >> > > DeleteFileW for regular files, but attempt both for symlinks. >> > > (fs::remove_all): Return immediately if path is empty. >> > > Check if path points to a symlink, and if so, remove the >> > > symlink using fs::remove. >> > > * src/filesystem/ops-common.h [_GLIBCXX_FILESYSTEM_IS_WINDOWS] >> > > (S_IFLNK, S_ISLNK): Define. >> > > (__detail::__open_for_stat): New helper function for stat and >> > > lstat. >> > > (__detail::FileType): New enum type. >> > > (__detail::__check_handle_type): New helper function for stat >> > > and lstat. >> > > (__detail::__is_handle_symlink): New helper function for >> > > fs::read_symlink. >> > > (__detail::__stat_windows): New helper function for stat and >> > > lstat. >> > > (__gnu_posix::stat, __gnu_posix::lstat): Use __stat_windows to >> > > properly follow or not follow symlinks, and check if file is a >> > > symlink. >> > > * testsuite/27_io/filesystem/operations/canonical.cc (test03): >> > > Use fs::create_directory_symlink instead of >> fs::create_symlink. >> > > Check if NO_SYMLINKS or _GLIBCXX_FILESYSTEM_IS_WINDOWS instead >> > > of just checking NO_SYMLINKS when defining baz. >> > > * testsuite/27_io/filesystem/operations/copy.cc (test02): >> > > Create a symlink to temporary file instead of ".". Use >> > > fs::exists(symlink_status()) instead of fs::exists for >> symlinks. >> > > * testsuite/27_io/filesystem/operations/weakly_canonical.cc >> > > (test01): Use fs::create_directory_symlink instead of >> > > fs::create_symlink. Wrap statements that test a dotdot after >> a >> > > symlink in an ifndef _GLIBCXX_FILESYSTEM_IS_WINDOWS. >> > > * testsuite/util/testsuite_fs.h: Do not define NO_SYMLINKS on >> > > Windows. >> > > >> > > Co-authored-by: Jonathan Wakely <[email protected]> >> > > --- >> > > >> > > Tested x86_64-linux. >> > > >> > > Tested x86_64-w64-mingw32 running on Wine 11.0 and Windows 11. >> > > >> > > v5: Fix stat and lstat emulation for mingw-w64 13.0.0 which does not >> set >> > > S_IFDIR for directories. Adjust weakly_canonical.cc test for different >> > > behaviour on Windows. >> > > >> > > libstdc++-v3/src/c++17/fs_ops.cc | 210 >> +++++++++++++++++- >> > > libstdc++-v3/src/filesystem/ops-common.h | 106 ++++++++- >> > > .../27_io/filesystem/operations/canonical.cc | 4 +- >> > > .../27_io/filesystem/operations/copy.cc | 7 +- >> > > .../filesystem/operations/weakly_canonical.cc | 17 +- >> > > libstdc++-v3/testsuite/util/testsuite_fs.h | 2 +- >> > > 6 files changed, 328 insertions(+), 18 deletions(-) >> > > >> > > diff --git a/libstdc++-v3/src/c++17/fs_ops.cc >> b/libstdc++-v3/src/c++17/fs_ops.cc >> > > index 454962c75219..387869751c58 100644 >> > > --- a/libstdc++-v3/src/c++17/fs_ops.cc >> > > +++ b/libstdc++-v3/src/c++17/fs_ops.cc >> > > @@ -56,6 +56,8 @@ >> > > #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > # define WIN32_LEAN_AND_MEAN >> > > # include <windows.h> >> > > +# include <winioctl.h> // FSCTL_GET_REPARSE_POINT >> > > +# include <ntdef.h> // REPARSE_DATA_BUFFER >> > > #endif >> > > >> > > #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem { >> > > @@ -644,6 +646,52 @@ fs::create_directory(const path& p, const path& >> attributes, >> > > #endif >> > > } >> > > >> > > +#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > +namespace >> > > +{ >> > > + void >> > > + windows_create_symlink(const fs::path& to, const fs::path& >> new_symlink, >> > > + const fs::file_type target_type, >> > > + std::error_code& ec) noexcept >> > > + { >> > > +#ifdef SYMBOLIC_LINK_FLAG_DIRECTORY // Implies CreateSymbolicLinkW >> support. >> > > + DWORD symlink_type = target_type == fs::file_type::directory >> > > + ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; >> > > + // Windows can't handle relative symlinks with non-preferred >> slashes. >> > > + // Creating the symlink will succeed, but the symlink won't >> resolve >> > > + // correctly in later operations. >> > > + const fs::path* preferred_to = &to; >> > > + fs::path to2; >> > > + if (to.native().find(L'/') != std::string::npos) >> > > + { >> > > + __try >> > > + { >> > > + to2 = to; >> > > + to2.make_preferred(); >> > > + preferred_to = &to2; >> > > + } >> > > + __catch (const std::bad_alloc&) >> > > + { >> > > + ec = std::make_error_code(std::errc::not_enough_memory); >> > > + return; >> > > + } >> > > + } >> > > + if (CreateSymbolicLinkW(new_symlink.c_str(), >> preferred_to->c_str(), >> > > + symlink_type >> > > + | >> SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) >> > > + ec.clear(); >> > > + else if (GetLastError() == ERROR_INVALID_PARAMETER >> > > + && CreateSymbolicLinkW(new_symlink.c_str(), >> preferred_to->c_str(), >> > > + symlink_type)) >> > > + ec.clear(); >> > > + else >> > > + ec = std::__last_system_error(); >> > > +#else >> > > + ec = std::make_error_code(std::errc::function_not_supported); >> > > +#endif >> > > + } >> > > +} >> > > +#endif >> > > >> > > void >> > > fs::create_directory_symlink(const path& to, const path& new_symlink) >> > > @@ -660,7 +708,7 @@ fs::create_directory_symlink(const path& to, >> const path& new_symlink, >> > > error_code& ec) noexcept >> > > { >> > > #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > - ec = std::make_error_code(std::errc::function_not_supported); >> > > + windows_create_symlink(to, new_symlink, file_type::directory, ec); >> > > #else >> > > create_symlink(to, new_symlink, ec); >> > > #endif >> > > @@ -715,6 +763,8 @@ fs::create_symlink(const path& to, const path& >> new_symlink, >> > > ec.assign(errno, std::generic_category()); >> > > else >> > > ec.clear(); >> > > +#elif _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > + windows_create_symlink(to, new_symlink, file_type::regular, ec); >> > > #else >> > > ec = std::make_error_code(std::errc::function_not_supported); >> > > #endif >> > > @@ -829,10 +879,14 @@ namespace >> > > struct auto_win_file_handle >> > > { >> > > explicit >> > > - auto_win_file_handle(const wchar_t* p, std::error_code& ec) >> noexcept >> > > + auto_win_file_handle(const wchar_t* p, std::error_code& ec, >> > > + const bool follow_symlink = true) noexcept >> > > : handle(CreateFileW(p, 0, >> > > FILE_SHARE_DELETE | FILE_SHARE_READ | >> FILE_SHARE_WRITE, >> > > - 0, OPEN_EXISTING, >> FILE_FLAG_BACKUP_SEMANTICS, 0)), >> > > + 0, OPEN_EXISTING, >> > > + (FILE_FLAG_BACKUP_SEMANTICS >> > > + | (follow_symlink ? 0 : >> FILE_FLAG_OPEN_REPARSE_POINT)), >> > > + 0)), >> > > ec(ec) >> > > { >> > > if (handle == INVALID_HANDLE_VALUE) >> > > @@ -1193,6 +1247,74 @@ fs::proximate(const path& p, const path& base, >> error_code& ec) >> > > return result; >> > > } >> > > >> > > +#if defined(_GLIBCXX_FILESYSTEM_IS_WINDOWS) \ >> > > + && defined(SYMBOLIC_LINK_FLAG_DIRECTORY) >> > > +namespace >> > > +{ >> > > + void >> > > + windows_read_symlink_handle(auto_win_file_handle& link_handle, >> > > + std::error_code& ec, >> > > + fs::path& result) >> > > + { >> > > + PREPARSE_DATA_BUFFER reparse_buffer = nullptr; >> > > + std::unique_ptr<char[]> big_buffer; >> > > + >> > > + // Allocate enough memory on the stack to get the reparse data >> > > + // plus a 260 character path. Should be sufficient in most >> cases. >> > > + // Allocate an extra wchar_t to ensure we can manually null >> terminate. >> > > + static constexpr size_t small_buffer_size = >> sizeof(REPARSE_DATA_BUFFER) >> > > + + 260 * >> sizeof(wchar_t); >> > > + char small_buffer[small_buffer_size + sizeof(wchar_t)]; >> > > + reparse_buffer = >> reinterpret_cast<PREPARSE_DATA_BUFFER>(&small_buffer[0]); >> > > + long unsigned int bytes_returned, big_buffer_size; >> > > + >> > > + // Attempt to get the reparse data with the buffer on the stack >> > > + // before allocating the exact amount needed on the heap. >> > > + bool got_reparse_data = DeviceIoControl(link_handle.handle, >> > > + FSCTL_GET_REPARSE_POINT, >> > > + nullptr, 0, >> > > + &small_buffer, >> small_buffer_size, >> > > + &bytes_returned, nullptr); >> > > + >> > > + int last_error = GetLastError(); >> > > + if (!got_reparse_data && last_error == ERROR_MORE_DATA) >> > > + { >> > > + big_buffer_size = bytes_returned; >> > > + big_buffer.reset(new char[big_buffer_size + sizeof(wchar_t)]); >> > > + got_reparse_data = DeviceIoControl(link_handle.handle, >> > > + FSCTL_GET_REPARSE_POINT, >> > > + nullptr, 0, >> > > + big_buffer.get(), >> big_buffer_size, >> > > + &bytes_returned, nullptr); >> > > + if (!got_reparse_data) >> > > + { >> > > + ec = std::__last_system_error(); >> > > + return; >> > > + } >> > > + >> > > + reparse_buffer >> > > + = reinterpret_cast<PREPARSE_DATA_BUFFER>(big_buffer.get()); >> > > + >> > > + } >> > > + else >> > > + { >> > > + if (!got_reparse_data) >> > > + { >> > > + ec = std::__last_system_error(); >> > > + return; >> > > + } >> > > + } >> > > + >> > > + ec.clear(); >> > > + auto& symlink_buffer = reparse_buffer->SymbolicLinkReparseBuffer; >> > > + wchar_t* target_name = &symlink_buffer.PathBuffer[0]; >> > > + target_name += symlink_buffer.PrintNameOffset / sizeof(wchar_t); >> > > + target_name[symlink_buffer.PrintNameLength / sizeof(wchar_t)] = >> L'\0'; >> > > + result = target_name; >> > > + } >> > > +}; >> > > +#endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > + >> > > fs::path >> > > fs::read_symlink(const path& p) >> > > { >> > > @@ -1248,6 +1370,26 @@ fs::path fs::read_symlink(const path& p, >> error_code& ec) >> > > bufsz *= 2; >> > > } >> > > while (true); >> > > +#elif defined(_GLIBCXX_FILESYSTEM_IS_WINDOWS) \ >> > > + && defined(SYMBOLIC_LINK_FLAG_DIRECTORY) >> > > + auto_win_file_handle link_handle(p.c_str(), ec, false); >> > > + if (!link_handle) >> > > + return result; >> > > + >> > > + int is_symlink = __detail::__is_handle_symlink(link_handle.handle); >> > > + if (is_symlink == -1) >> > > + { >> > > + ec = __last_system_error(); >> > > + return result; >> > > + } >> > > + >> > > + if (!is_symlink) >> > > + { >> > > + ec.assign(EINVAL, std::generic_category()); >> > > + return result; >> > > + } >> > > + >> > > + windows_read_symlink_handle(link_handle, ec, result); >> > > #else >> > > ec = std::make_error_code(std::errc::function_not_supported); >> > > #endif >> > > @@ -1291,8 +1433,14 @@ fs::remove(const path& p, error_code& ec) >> noexcept >> > > auto st = symlink_status(p, ec); >> > > if (exists(st)) >> > > { >> > > - if ((is_directory(p, ec) && RemoveDirectoryW(p.c_str())) >> > > - || DeleteFileW(p.c_str())) >> > > + if ((is_directory(st) || is_symlink(st)) >> > > + && RemoveDirectoryW(p.c_str())) >> > > + { >> > > + ec.clear(); >> > > + return true; >> > > + } >> > > + else if ((is_regular_file(st) || is_symlink(st)) >> > > + && DeleteFileW(p.c_str())) >> > > { >> > > ec.clear(); >> > > return true; >> > > @@ -1320,6 +1468,30 @@ std::uintmax_t >> > > fs::remove_all(const path& p) >> > > { >> > > error_code ec; >> > > +#if _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > + if (p.empty()) >> > > + return 0; >> > > + // The current opendir implementation on Windows always follows an >> initial >> > > + // symlink. Therefore, if remove_all is called on a symlink, >> > > + // the target is removed. Call remove if we have a symlink. >> > > + auto p_status = symlink_status(p, ec); >> > > + if (!exists(p_status)) >> > > + { >> > > + int err = ec.default_error_condition().value(); >> > > + bool not_found = !ec || is_not_found_errno(err); >> > > + if (!not_found) >> > > + _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all", >> > > + p, ec)); >> > > + return 0; >> > > + } >> > > + if (is_symlink(p_status)) >> > > + { >> > > + if (!remove(p, ec)) >> > > + _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all", >> > > + p, ec)); >> > > + return 1; >> > > + } >> > > +#endif >> > > uintmax_t count = 0; >> > > recursive_directory_iterator dir(p, directory_options{64|128}, ec); >> > > switch (ec.value()) // N.B. assumes ec.category() == >> std::generic_category() >> > > @@ -1363,6 +1535,34 @@ fs::remove_all(const path& p) >> > > std::uintmax_t >> > > fs::remove_all(const path& p, error_code& ec) >> > > { >> > > +#if _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > + if (p.empty()) >> > > + { >> > > + ec.clear(); >> > > + return 0; >> > > + } >> > > + // The current opendir implementation on Windows always follows an >> initial >> > > + // symlink. Therefore, if remove_all is called on a symlink, >> > > + // the target is removed. Call remove if we have a symlink. >> > > + auto p_status = symlink_status(p, ec); >> > > + if (!exists(p_status)) >> > > + { >> > > + int err = ec.default_error_condition().value(); >> > > + bool not_found = !ec || is_not_found_errno(err); >> > > + if (not_found) >> > > + { >> > > + ec.clear(); >> > > + return 0; >> > > + } >> > > + return -1; >> > > + } >> > > + if (is_symlink(p_status)) >> > > + { >> > > + if (remove(p, ec)) >> > > + return 1; >> > > + return ec ? -1 : 0; >> > > + } >> > > +#endif >> > > uintmax_t count = 0; >> > > recursive_directory_iterator dir(p, directory_options{64|128}, ec); >> > > switch (ec.value()) // N.B. assumes ec.category() == >> std::generic_category() >> > > diff --git a/libstdc++-v3/src/filesystem/ops-common.h >> b/libstdc++-v3/src/filesystem/ops-common.h >> > > index 304d5896d026..cdb4a1ca0ca8 100644 >> > > --- a/libstdc++-v3/src/filesystem/ops-common.h >> > > +++ b/libstdc++-v3/src/filesystem/ops-common.h >> > > @@ -105,6 +105,101 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > > >> > > namespace filesystem >> > > { >> > > +#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > +namespace __detail >> > > +{ >> > > +#define S_IFLNK 0xC000 >> > > +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) >> > > + >> > > + using stat_type = struct ::__stat64; >> > > + >> > > + inline HANDLE __open_for_stat(const wchar_t* path, bool >> following_symlinks) >> > > + { >> > > + constexpr auto share_flags >> > > + = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; >> > > + auto file_flags = FILE_FLAG_BACKUP_SEMANTICS; >> > > + if (!following_symlinks) >> > > + file_flags |= FILE_FLAG_OPEN_REPARSE_POINT; >> > > + HANDLE handle >> > > + = CreateFileW(path, 0, share_flags, 0, OPEN_EXISTING, >> file_flags, 0); >> > > + >> > > + if (handle == INVALID_HANDLE_VALUE) >> > > + { >> > > + // CreateFileW does not set errno. >> > > + errno = >> std::__last_system_error().default_error_condition().value(); >> > > + } >> > > + >> > > + return handle; >> > > + } >> > > + >> > > + // _fstat64 in mingw-w64 does not know about symlinks and before >> v14.0.0 >> > > + // it does not know about directories. >> > > + // We use GetFileInformationByHandleEx to check whether the HANDLE >> refers >> > > + // to a symlink or directory, then fix the result of _fstat64 >> accordingly. >> > > + enum class FileType { Err = -1, Dir = S_IFDIR, Link = S_IFLNK, >> Other = 0 }; >> > > + >> > > + inline FileType __check_handle_type(HANDLE handle, bool >> following_symlinks) >> > > + { >> > > +#ifdef SYMBOLIC_LINK_FLAG_DIRECTORY >> > > + FILE_ATTRIBUTE_TAG_INFO type_info; >> > > + if (!GetFileInformationByHandleEx(handle, FileAttributeTagInfo, >> > > + &type_info, sizeof(type_info))) >> > > + { >> > > + errno = >> std::__last_system_error().default_error_condition().value(); >> > > + return FileType::Err; >> > > + } >> > > + // A directory symlink has both DIRECTORY and REPARSE_POINT set, >> > > + // so to detect a symlink we need to check for REPARSE_POINT >> first. >> > > + if (!following_symlinks) >> > > + if (type_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT >> > > + && type_info.ReparseTag == IO_REPARSE_TAG_SYMLINK) >> > > + return FileType::Link; >> > > + if (type_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) >> > > + return FileType::Dir; >> > > +#endif >> > > + return FileType::Other; >> > > + } >> > > + >> > > + // -1 error, 0 not a symlink, 1 a symlink >> > > + inline int __is_handle_symlink(HANDLE handle) >> > > + { >> > > + FileType type = __check_handle_type(handle, false); >> > > + if (type == FileType::Err) >> > > + return -1; >> > > + return type == FileType::Link; >> > > + } >> > > + >> > > + inline int __stat_windows(const wchar_t* path, stat_type* buffer, >> > > + bool following_symlinks) >> > > + { >> > > + HANDLE handle = __open_for_stat(path, following_symlinks); >> > > + if (handle == INVALID_HANDLE_VALUE) >> > > + return -1; >> > > + // Manually check for directory or symlink, because _fstat does >> not. >> > > + FileType type = __check_handle_type(handle, following_symlinks); >> > > + if (type == FileType::Err) >> > > + { >> > > + CloseHandle(handle); >> > > + return -1; >> > > + } >> > > + int fd = ::_open_osfhandle((intptr_t)handle, _O_RDONLY); >> > > + if (fd == -1) >> > > + { >> > > + CloseHandle(handle); >> > > + return -1; >> > > + } >> > > + int stat_result = ::_fstat64(fd, buffer); >> > > + if (stat_result != -1 && type != FileType::Other) >> > > + { >> > > + // Clear the previous file type. >> > > + buffer->st_mode &= ~S_IFMT; >> > > + buffer->st_mode |= (::mode_t)type; >> > > + } >> > > + ::_close(fd); >> > > + return stat_result; >> > > + } >> > > +} >> > > +#endif >> > > namespace __gnu_posix >> > > { >> > > #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > @@ -121,13 +216,14 @@ namespace __gnu_posix >> > > using stat_type = struct ::__stat64; >> > > >> > > inline int stat(const wchar_t* path, stat_type* buffer) >> > > - { return ::_wstat64(path, buffer); } >> > > + { return __detail::__stat_windows(path, buffer, true); } >> > > >> > > inline int lstat(const wchar_t* path, stat_type* buffer) >> > > - { >> > > - // FIXME: symlinks not currently supported >> > > - return stat(path, buffer); >> > > - } >> > > +#ifdef SYMBOLIC_LINK_FLAG_DIRECTORY >> > > + { return __detail::__stat_windows(path, buffer, false); } >> > > +#else >> > > + { return stat(path, buffer); } >> > > +#endif >> > > >> > > using ::mode_t; >> > > >> > > diff --git >> a/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc >> b/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc >> > > index 884b6da438a3..74d6fd16e21f 100644 >> > > --- a/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc >> > > +++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/canonical.cc >> > > @@ -113,14 +113,14 @@ test03() >> > > fs::path foo = dir/"foo", bar = dir/"bar"; >> > > fs::create_directory(foo); >> > > fs::create_directory(bar); >> > > -#ifdef NO_SYMLINKS >> > > +#if defined(NO_SYMLINKS) || defined(_GLIBCXX_FILESYSTEM_IS_WINDOWS) >> > > #if defined(__MINGW32__) || defined(__MINGW64__) >> > > const fs::path baz = dir/"foo\\\\..\\bar///"; >> > > #else >> > > const fs::path baz = dir/"foo//../bar///"; >> > > #endif >> > > #else >> > > - fs::create_symlink("../bar", foo/"baz"); >> > > + fs::create_directory_symlink("../bar", foo/"baz"); >> > > const fs::path baz = dir/"foo//./baz///"; >> > > #endif >> > > >> > > diff --git >> a/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc >> b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc >> > > index 1ca4a44da59e..03960fa90f24 100644 >> > > --- a/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc >> > > +++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy.cc >> > > @@ -68,13 +68,14 @@ test02() >> > > { >> > > #ifndef NO_SYMLINKS >> > > const std::error_code bad_ec = >> make_error_code(std::errc::invalid_argument); >> > > + __gnu_test::scoped_file tmp_file; >> > > auto from = __gnu_test::nonexistent_path(); >> > > std::error_code ec; >> > > >> > > ec = bad_ec; >> > > - fs::create_symlink(".", from, ec); >> > > + fs::create_symlink(tmp_file.path, from, ec); >> > > VERIFY( !ec ); >> > > - VERIFY( fs::exists(from) ); >> > > + VERIFY( fs::exists(symlink_status(from)) ); >> > > >> > > auto to = __gnu_test::nonexistent_path(); >> > > ec = bad_ec; >> > > @@ -97,7 +98,7 @@ test02() >> > > ec = bad_ec; >> > > fs::copy(from, to, fs::copy_options::copy_symlinks, ec); >> > > VERIFY( !ec ); >> > > - VERIFY( fs::exists(to) ); >> > > + VERIFY( fs::exists(symlink_status(to)) ); >> > > VERIFY( is_symlink(to) ); >> > > >> > > ec.clear(); >> > > diff --git >> a/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc >> b/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc >> > > index 6c187c73b79f..9cfdf6231a8b 100644 >> > > --- >> a/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc >> > > +++ >> b/libstdc++-v3/testsuite/27_io/filesystem/operations/weakly_canonical.cc >> > > @@ -40,19 +40,32 @@ test01() >> > > fs::path p; >> > > >> > > #ifndef NO_SYMLINKS >> > > - fs::create_symlink("../bar", foo/"bar"); >> > > + fs::create_directory_symlink(fs::path("..")/"bar", foo/"bar"); >> > > + >> > > + // This fails when under under Wine: >> > > + p = fs::canonical(dir/"foo//./bar/."); >> > >> > This seems to be a Wine bug, reported as >> > https://bugs.winehq.org/show_bug.cgi?id=59922 >> >> Not a bug - see the explanation (and my reply) at the URL above. >> > > I configured and made a mingw cross-compiler and ran the 27_io/filesystem > tests with Wine. This test passed. In fact, there were no regressions > compared to master. > I am using Wine 11.11 and mingw-w64-headers 14.0.0-1. > >> >> >> > >> > >> > > + VERIFY( p == dirc/"bar" ); >> > > >> > > p = fs::weakly_canonical(dir/"foo//./bar///../biz/."); >> > > +#ifndef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > VERIFY( p == dirc/"biz/" ); >> > > +#else >> > > + VERIFY( p == dirc/"foo\\biz\\" ); // XXX is this right? check MSVC >> > I wrote a test program on Windows and compiled with MSVC. This is how it works on actual Windows. // Program to test how MSVC STL's std::filesystem works when symlinks follow dotdots #include <iostream> #include <filesystem> namespace fs = std::filesystem; int main() { try { fs::path dir = fs::path("test_symlink_behaviour"); const fs::path dirc = fs::canonical(dir); fs::remove_all(dir); fs::path foo = dir/"foo", bar = dir/"bar"; fs::create_directory(dir); fs::create_directory(foo); fs::create_directory(bar); fs::create_directory(bar/"baz"); fs::path p; fs::create_directory_symlink(fs::path("..")/"bar", foo/"bar"); p = fs::weakly_canonical(dir/"foo//./bar///../biz/."); const fs::path expected_resolution = dirc/"foo\\biz\\"; std::cout << "Without ec, dir/foo//./bar///../biz/. == dir/foo\\biz\\ is " << std::boolalpha << (p == expected_resolution) << ".\n"; std::error_code ec; p = fs::weakly_canonical(dir/"foo//./bar///../biz/.", ec); std::cout << "With ec, dir/foo//./bar///../biz/. == dir/foo\\biz\\ is " << std::boolalpha << (p == expected_resolution) << ".\n"; } catch (const std::filesystem::filesystem_error & err) { std::cout << "std::filesystem exception: " << err.what() << "\n"; } return 0; } // End program Compiled with cl.exe /std:c++17 /WX /W4 /EHs .\test_windows_symlink_behaviour.cpp > > > +#endif >> > > + >> > > p = fs::weakly_canonical(dir/"foo/.//bar/././baz/."); >> > > - VERIFY( p == dirc/"bar/baz" ); >> > > + VERIFY( p == dirc/"bar"/"baz" ); >> > > p = >> fs::weakly_canonical(fs::current_path()/dir/"bar//../foo/bar/baz"); >> > > VERIFY( p == dirc/"bar/baz" ); >> > > >> > > ec = bad_ec; >> > > p = fs::weakly_canonical(dir/"foo//./bar///../biz/.", ec); >> > > VERIFY( !ec ); >> > > +#ifndef _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > VERIFY( p == dirc/"biz/" ); >> > > +#else >> > > + VERIFY( p == dirc/"foo\\biz\\" ); // XXX is this right? check MSVC >> > > +#endif >> > > ec = bad_ec; >> > > p = fs::weakly_canonical(dir/"foo/.//bar/././baz/.", ec); >> > > VERIFY( !ec ); >> > > diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h >> b/libstdc++-v3/testsuite/util/testsuite_fs.h >> > > index fa099b0986bc..f1cf28ce4dd9 100644 >> > > --- a/libstdc++-v3/testsuite/util/testsuite_fs.h >> > > +++ b/libstdc++-v3/testsuite/util/testsuite_fs.h >> > > @@ -42,7 +42,7 @@ namespace test_fs = std::experimental::filesystem; >> > > #include <stdlib.h> // mkstemp >> > > #endif >> > > >> > > -#ifndef _GLIBCXX_HAVE_SYMLINK >> > > +#if !defined _GLIBCXX_HAVE_SYMLINK && !defined >> _GLIBCXX_FILESYSTEM_IS_WINDOWS >> > > #define NO_SYMLINKS >> > > #endif >> > > >> > > -- >> > > 2.54.0 >> > > >> >>
