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
>> > >
>>
>>

Reply via email to