On Fri, Jun 26, 2026 at 6:59 PM 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. > The code looks OK for me. I do not know Windows interface good enough to validate every call. One small, optional change listed below. It caused me some back and forth on why we have this difference. > > 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) > The above is inconsistent with how windows_create_symlink is handled. The former is defined if _GLIBCXX_FILESYSTEM_IS_WINDOWS and then check SYMBOLIC_LINK_FLAG_DIRECTORY, this is only called when both are defined. I think it would be better to make it consistent. As windows_read_symlink_handle is surrounded by additional code, I would change create function to match, but that only needpick. > +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/."); > + 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 > +#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 > >
