From: Jonathan Wakely Date: Fri, 12 Feb 2021 15:13:02 +0000 (+0000) Subject: libstdc++: Fix filesystem::rename on Windows [PR 98985] X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=1dfd95f0a0ca1d9e6cbc00e6cbfd1fa20a98f312;p=gcc.git libstdc++: Fix filesystem::rename on Windows [PR 98985] The _wrename function won't overwrite an existing file, so use MoveFileEx instead. That allows renaming directories over files, which POSIX doesn't allow, so check for that case explicitly and report an error. Also document the deviation from the expected behaviour, and add a test for filesystem::rename which was previously missing. The Filesystem TS experimental::filesystem::rename doesn't have that extra code to handle directories correctly, so the relevant parts of the new test are not run on Windows. libstdc++-v3/ChangeLog: * doc/xml/manual/status_cxx2014.xml: Document implementation specific properties of std::experimental::filesystem::rename. * doc/xml/manual/status_cxx2017.xml: Document implementation specific properties of std::filesystem::rename. * doc/html/*: Regenerate. * src/c++17/fs_ops.cc (fs::rename): Implement correct behaviour for directories on Windows. * src/filesystem/ops-common.h (__gnu_posix::rename): Use MoveFileExW on Windows. * testsuite/27_io/filesystem/operations/rename.cc: New test. * testsuite/experimental/filesystem/operations/rename.cc: New test. --- diff --git a/libstdc++-v3/doc/html/index.html b/libstdc++-v3/doc/html/index.html index 587d4ee819a..772009a816b 100644 --- a/libstdc++-v3/doc/html/index.html +++ b/libstdc++-v3/doc/html/index.html @@ -23,7 +23,7 @@


Table of Contents

The GNU C++ Library Manual
I. Introduction -
1. Status
Implementation Status
C++ 1998/2003
Implementation Status
Implementation Specific Behavior
C++ 2011
Implementation Specific Behavior
C++ 2014
C++ 2017
Implementation Specific Behavior
Parallelism 2 TS
C++ 2020
C++ TR1
Implementation Specific Behavior
C++ TR 24733
C++ IS 29124
Implementation Specific Behavior
License
The Code: GPL
The Documentation: GPL, FDL
Bugs
Implementation Bugs
Standard Bugs
2. Setup
Prerequisites
Configure
Make
3. Using
Command Options
Headers
Header Files
Mixing Headers
The C Headers and namespace std
Precompiled Headers
Macros
Dual ABI
Troubleshooting
Namespaces
Available Namespaces
namespace std
Using Namespace Composition
Linking
Almost Nothing
Finding Dynamic or Shared Libraries
Experimental Library Extensions
Concurrency
Prerequisites
Thread Safety
Atomics
IO
Structure
Defaults
Future
Alternatives
Containers
Exceptions
Exception Safety
Exception Neutrality
Doing without
Compatibility
With C
With POSIX thread cancellation
Debugging Support
Using g++
Debug Versions of Library Binary Files
Memory Leak Hunting
Non-memory leaks in Pool and MT allocators
Data Race Hunting
Using gdb
Tracking uncaught exceptions
Debug Mode
Compile Time Checking
II. +
1. Status
Implementation Status
C++ 1998/2003
Implementation Status
Implementation Specific Behavior
C++ 2011
Implementation Specific Behavior
C++ 2014
Implementation Specific Behavior
Filesystem TS
C++ 2017
Implementation Specific Behavior
Parallelism 2 TS
C++ 2020
C++ TR1
Implementation Specific Behavior
C++ TR 24733
C++ IS 29124
Implementation Specific Behavior
License
The Code: GPL
The Documentation: GPL, FDL
Bugs
Implementation Bugs
Standard Bugs
2. Setup
Prerequisites
Configure
Make
3. Using
Command Options
Headers
Header Files
Mixing Headers
The C Headers and namespace std
Precompiled Headers
Macros
Dual ABI
Troubleshooting
Namespaces
Available Namespaces
namespace std
Using Namespace Composition
Linking
Almost Nothing
Finding Dynamic or Shared Libraries
Experimental Library Extensions
Concurrency
Prerequisites
Thread Safety
Atomics
IO
Structure
Defaults
Future
Alternatives
Containers
Exceptions
Exception Safety
Exception Neutrality
Doing without
Compatibility
With C
With POSIX thread cancellation
Debugging Support
Using g++
Debug Versions of Library Binary Files
Memory Leak Hunting
Non-memory leaks in Pool and MT allocators
Data Race Hunting
Using gdb
Tracking uncaught exceptions
Debug Mode
Compile Time Checking
II. Standard Contents
4. Support diff --git a/libstdc++-v3/doc/html/manual/index.html b/libstdc++-v3/doc/html/manual/index.html index d66de295f11..24a4ba97baf 100644 --- a/libstdc++-v3/doc/html/manual/index.html +++ b/libstdc++-v3/doc/html/manual/index.html @@ -4,7 +4,7 @@


Table of Contents

I. Introduction -
1. Status
Implementation Status
C++ 1998/2003
Implementation Status
Implementation Specific Behavior
C++ 2011
Implementation Specific Behavior
C++ 2014
C++ 2017
Implementation Specific Behavior
Parallelism 2 TS
C++ 2020
C++ TR1
Implementation Specific Behavior
C++ TR 24733
C++ IS 29124
Implementation Specific Behavior
License
The Code: GPL
The Documentation: GPL, FDL
Bugs
Implementation Bugs
Standard Bugs
2. Setup
Prerequisites
Configure
Make
3. Using
Command Options
Headers
Header Files
Mixing Headers
The C Headers and namespace std
Precompiled Headers
Macros
Dual ABI
Troubleshooting
Namespaces
Available Namespaces
namespace std
Using Namespace Composition
Linking
Almost Nothing
Finding Dynamic or Shared Libraries
Experimental Library Extensions
Concurrency
Prerequisites
Thread Safety
Atomics
IO
Structure
Defaults
Future
Alternatives
Containers
Exceptions
Exception Safety
Exception Neutrality
Doing without
Compatibility
With C
With POSIX thread cancellation
Debugging Support
Using g++
Debug Versions of Library Binary Files
Memory Leak Hunting
Non-memory leaks in Pool and MT allocators
Data Race Hunting
Using gdb
Tracking uncaught exceptions
Debug Mode
Compile Time Checking
II. +
1. Status
Implementation Status
C++ 1998/2003
Implementation Status
Implementation Specific Behavior
C++ 2011
Implementation Specific Behavior
C++ 2014
Implementation Specific Behavior
Filesystem TS
C++ 2017
Implementation Specific Behavior
Parallelism 2 TS
C++ 2020
C++ TR1
Implementation Specific Behavior
C++ TR 24733
C++ IS 29124
Implementation Specific Behavior
License
The Code: GPL
The Documentation: GPL, FDL
Bugs
Implementation Bugs
Standard Bugs
2. Setup
Prerequisites
Configure
Make
3. Using
Command Options
Headers
Header Files
Mixing Headers
The C Headers and namespace std
Precompiled Headers
Macros
Dual ABI
Troubleshooting
Namespaces
Available Namespaces
namespace std
Using Namespace Composition
Linking
Almost Nothing
Finding Dynamic or Shared Libraries
Experimental Library Extensions
Concurrency
Prerequisites
Thread Safety
Atomics
IO
Structure
Defaults
Future
Alternatives
Containers
Exceptions
Exception Safety
Exception Neutrality
Doing without
Compatibility
With C
With POSIX thread cancellation
Debugging Support
Using g++
Debug Versions of Library Binary Files
Memory Leak Hunting
Non-memory leaks in Pool and MT allocators
Data Race Hunting
Using gdb
Tracking uncaught exceptions
Debug Mode
Compile Time Checking
II. Standard Contents
4. Support diff --git a/libstdc++-v3/doc/html/manual/intro.html b/libstdc++-v3/doc/html/manual/intro.html index 0a99c8fc71a..3604e0a1d45 100644 --- a/libstdc++-v3/doc/html/manual/intro.html +++ b/libstdc++-v3/doc/html/manual/intro.html @@ -5,4 +5,4 @@ Prev The GNU C++ Library Manual Next

Part I.  Introduction -

\ No newline at end of file +

Table of Contents

1. Status
Implementation Status
C++ 1998/2003
Implementation Status
Implementation Specific Behavior
C++ 2011
Implementation Specific Behavior
C++ 2014
Implementation Specific Behavior
Filesystem TS
C++ 2017
Implementation Specific Behavior
Parallelism 2 TS
C++ 2020
C++ TR1
Implementation Specific Behavior
C++ TR 24733
C++ IS 29124
Implementation Specific Behavior
License
The Code: GPL
The Documentation: GPL, FDL
Bugs
Implementation Bugs
Standard Bugs
2. Setup
Prerequisites
Configure
Make
3. Using
Command Options
Headers
Header Files
Mixing Headers
The C Headers and namespace std
Precompiled Headers
Macros
Dual ABI
Troubleshooting
Namespaces
Available Namespaces
namespace std
Using Namespace Composition
Linking
Almost Nothing
Finding Dynamic or Shared Libraries
Experimental Library Extensions
Concurrency
Prerequisites
Thread Safety
Atomics
IO
Structure
Defaults
Future
Alternatives
Containers
Exceptions
Exception Safety
Exception Neutrality
Doing without
Compatibility
With C
With POSIX thread cancellation
Debugging Support
Using g++
Debug Versions of Library Binary Files
Memory Leak Hunting
Non-memory leaks in Pool and MT allocators
Data Race Hunting
Using gdb
Tracking uncaught exceptions
Debug Mode
Compile Time Checking
\ No newline at end of file diff --git a/libstdc++-v3/doc/html/manual/status.html b/libstdc++-v3/doc/html/manual/status.html index daa74a85c6f..46c6ee73998 100644 --- a/libstdc++-v3/doc/html/manual/status.html +++ b/libstdc++-v3/doc/html/manual/status.html @@ -2,7 +2,7 @@ Chapter 1. Status

Chapter 1. Status

Implementation Status

C++ 1998/2003

Implementation Status

+ Next


Chapter 1. Status

Implementation Status

C++ 1998/2003

Implementation Status

This status table is based on the table of contents of ISO/IEC 14882:2003.

This section describes the C++ support in mainline GCC, not in any @@ -536,7 +536,24 @@ not in any particular release. Link with -lstdc++fs -


C++ 2017

+


Implementation Specific Behavior

Filesystem TS

+ 2.1 POSIX conformance [fs.conform.9945] + The behavior of the filesystem library implementation will depend on + the target operating system. Some features will not be supported + on some targets. Symbolic links and file permissions + are not supported on Windows. +

+ 15.30 Rename [fs.op.rename] + On Windows, experimental::filesystem::rename + is implemented by calling MoveFileExW and so + does not meet the requirements of POSIX rename + when one or both of the paths resolves to an existing directory. + Specifically, it is possible to rename a directory so it replaces + a non-directory (POSIX requires an error in that case), + and it is not possible to rename a directory to replace another + directory (POSIX requires that to work if the directory being + replaced is empty). +

C++ 2017

In this implementation the -std=gnu++17 or -std=c++17 flag must be used to enable language and library @@ -1076,7 +1093,8 @@ since C++14 and the implementation is complete. 30.10.2.1 [fs.conform.9945] The behavior of the filesystem library implementation will depend on the target operating system. Some features will not be supported - on some targets. + on some targets. Symbolic links and file permissions + are not supported on Windows.

30.10.5 [fs.filesystem.syn] The clock used for file times is an unspecified type @@ -1099,7 +1117,16 @@ since C++14 and the implementation is complete.

30.10.15.4 [fs.op.file_size] If !is_regular_file(p), an error is reported. -

Parallelism 2 TS

+

+ 30.10.15.32 [fs.op.rename] + On Windows, filesystem::rename + is implemented by calling MoveFileExW and so + does not meet the requirements of POSIX rename + when one or both of the paths resolves to an existing directory. + Specifically, it is not possible to rename a directory to replace another + directory (POSIX requires that to work if the directory being + replaced is empty). +

Parallelism 2 TS

9.3 [parallel.simd.abi] max_fixed_size<T> is 32, except when targetting AVX512BW and sizeof(T) is 1. diff --git a/libstdc++-v3/doc/xml/manual/status_cxx2014.xml b/libstdc++-v3/doc/xml/manual/status_cxx2014.xml index 2cf5f629efb..5dc287707d8 100644 --- a/libstdc++-v3/doc/xml/manual/status_cxx2014.xml +++ b/libstdc++-v3/doc/xml/manual/status_cxx2014.xml @@ -1717,9 +1717,33 @@ not in any particular release. - +

Implementation Specific Behavior + +
Filesystem TS + + 2.1 POSIX conformance [fs.conform.9945] + The behavior of the filesystem library implementation will depend on + the target operating system. Some features will not be supported + on some targets. Symbolic links and file permissions + are not supported on Windows. + + + 15.30 Rename [fs.op.rename] + On Windows, experimental::filesystem::rename + is implemented by calling MoveFileExW and so + does not meet the requirements of POSIX rename + when one or both of the paths resolves to an existing directory. + Specifically, it is possible to rename a directory so it replaces + a non-directory (POSIX requires an error in that case), + and it is not possible to rename a directory to replace another + directory (POSIX requires that to work if the directory being + replaced is empty). + +
+
+ diff --git a/libstdc++-v3/doc/xml/manual/status_cxx2017.xml b/libstdc++-v3/doc/xml/manual/status_cxx2017.xml index b1c12bd3799..7f64c47bdfe 100644 --- a/libstdc++-v3/doc/xml/manual/status_cxx2017.xml +++ b/libstdc++-v3/doc/xml/manual/status_cxx2017.xml @@ -2992,7 +2992,8 @@ since C++14 and the implementation is complete. 30.10.2.1 [fs.conform.9945] The behavior of the filesystem library implementation will depend on the target operating system. Some features will not be supported - on some targets. + on some targets. Symbolic links and file permissions + are not supported on Windows. @@ -3025,6 +3026,18 @@ since C++14 and the implementation is complete. If !is_regular_file(p), an error is reported. + + 30.10.15.32 [fs.op.rename] + On Windows, filesystem::rename + is implemented by calling MoveFileExW and so + does not meet the requirements of POSIX rename + when one or both of the paths resolves to an existing directory. + Specifically, it is not possible to rename a directory to replace another + directory (POSIX requires that to work if the directory being + replaced is empty). + + +
Parallelism 2 TS diff --git a/libstdc++-v3/src/c++17/fs_ops.cc b/libstdc++-v3/src/c++17/fs_ops.cc index 3817655471c..3e1671e611e 100644 --- a/libstdc++-v3/src/c++17/fs_ops.cc +++ b/libstdc++-v3/src/c++17/fs_ops.cc @@ -1394,6 +1394,36 @@ fs::rename(const path& from, const path& to) void fs::rename(const path& from, const path& to, error_code& ec) noexcept { +#if _GLIBCXX_FILESYSTEM_IS_WINDOWS + const auto to_status = fs::status(to, ec); + if (to_status.type() == file_type::not_found) + ec.clear(); + else if (ec) + return; + + if (fs::exists(to_status)) + { + const auto from_status = fs::status(from, ec); + if (ec) + return; + + if (fs::is_directory(to_status)) + { + if (!fs::is_directory(from_status)) + { + // Cannot rename a non-directory over an existing directory. + ec = std::make_error_code(std::errc::is_a_directory); + return; + } + } + else if (fs::is_directory(from_status)) + { + // Cannot rename a directory over an existing non-directory. + ec = std::make_error_code(std::errc::not_a_directory); + return; + } + } +#endif if (posix::rename(from.c_str(), to.c_str())) ec.assign(errno, std::generic_category()); else diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h index 118256a0d22..529d4e09016 100644 --- a/libstdc++-v3/src/filesystem/ops-common.h +++ b/libstdc++-v3/src/filesystem/ops-common.h @@ -104,7 +104,16 @@ namespace __gnu_posix #endif inline int rename(const wchar_t* oldname, const wchar_t* newname) - { return _wrename(oldname, newname); } + { + if (MoveFileExW(oldname, newname, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + return 0; + if (GetLastError() == ERROR_ACCESS_DENIED) + errno = EACCES; + else + errno = EIO; + return -1; + } inline int truncate(const wchar_t* path, _off64_t length) { diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/rename.cc b/libstdc++-v3/testsuite/27_io/filesystem/operations/rename.cc new file mode 100644 index 00000000000..c873811ad93 --- /dev/null +++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/rename.cc @@ -0,0 +1,181 @@ +// Copyright (C) 2021 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } +// { dg-require-filesystem-ts "" } + +#include +#include +#include + +namespace fs = std::filesystem; + +void +test01() +{ + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + auto p1 = __gnu_test::nonexistent_path(); + auto p2 = __gnu_test::nonexistent_path(); + + fs::rename(p1, p2, ec); + VERIFY( ec ); + + ec.clear(); + fs::rename(p1, "", ec); + VERIFY( ec ); + + ec.clear(); + fs::rename("", p1, ec); + VERIFY( ec ); + + ec = bad_ec; + std::ofstream{p1}; // create file + fs::rename(p1, p1, ec); // no-op + VERIFY( !ec ); + VERIFY( is_regular_file(p1) ); + + ec.clear(); + rename(p2, p1, ec); + VERIFY( ec ); + VERIFY( ec.value() == ENOENT ); + VERIFY( is_regular_file(p1) ); + + ec = bad_ec; + fs::rename(p1, p2, ec); + VERIFY( !ec ); + VERIFY( !exists(p1) ); + VERIFY( is_regular_file(p2) ); + + ec = bad_ec; + std::ofstream{p1}; // create file + fs::rename(p1, p2, ec); + VERIFY( !ec ); + VERIFY( !exists(p1) ); + VERIFY( is_regular_file(p2) ); + + fs::remove(p2, ec); +} + +void +test_symlinks() +{ +#if defined(__MINGW32__) || defined(__MINGW64__) + // No symlink support +#else + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + const auto dir = __gnu_test::nonexistent_path(); + fs::create_directory(dir); + + create_symlink(dir/"nonesuch", dir/"link"); // dangling symlink + ec = bad_ec; + fs::rename(dir/"link", dir/"newlink", ec); + VERIFY( !ec ); + VERIFY( !exists(symlink_status(dir/"link")) ); + VERIFY( is_symlink(dir/"newlink") ); + + __gnu_test::scoped_file f(dir/"file"); + create_symlink(dir/"file", dir/"link"); + ec = bad_ec; + fs::rename(dir/"link", dir/"newerlink", ec); + VERIFY( !ec ); + VERIFY( !exists(symlink_status(dir/"link")) ); + VERIFY( is_symlink(dir/"newerlink") ); + VERIFY( is_regular_file(dir/"file") ); + + fs::remove_all(dir, ec); + f.path.clear(); +#endif +} + +void +test_directories() +{ + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + const auto dir = __gnu_test::nonexistent_path(); + fs::create_directory(dir); + __gnu_test::scoped_file f(dir/"file"); + fs::create_directory(dir/"subdir"); + + // Rename directory. + ec = bad_ec; + fs::rename(dir/"subdir", dir/"subdir2", ec); + VERIFY( !ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( !exists(dir/"subdir") ); + + // Cannot rename a directory to a sub-directory of itself. + fs::rename(dir/"subdir2", dir/"subdir2/subsubdir", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( !exists(dir/"subdir2"/"subsubdir") ); + + // Cannot rename a file to the name of an existing directory. + ec.clear(); + fs::rename(dir/"file", dir/"subdir2", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"file") ); + + // Cannot rename a directory to the name of an existing non-directory + ec.clear(); + fs::rename(dir/"subdir2", dir/"file", ec); + VERIFY( ec ); + VERIFY( is_regular_file(dir/"file") ); + VERIFY( is_directory(dir/"subdir2") ); + + // Cannot rename directory to the name of a non-empty directory. + ec.clear(); + __gnu_test::scoped_file f2(dir/"subdir2/file"); + fs::create_directory(dir/"subdir"); + fs::rename(dir/"subdir", dir/"subdir2", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir") ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"subdir2/file") ); + +#if defined(__MINGW32__) || defined(__MINGW64__) + // Cannot rename a directory to an existing directory +#else + // Can rename a non-empty directory to the name of an empty directory. + ec = bad_ec; + fs::rename(dir/"subdir2", dir/"subdir", ec); + VERIFY( !ec ); + VERIFY( is_directory(dir/"subdir") ); + VERIFY( !exists(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"subdir/file") ); +#endif + + f2.path.clear(); + f.path.clear(); + + fs::remove_all(dir, ec); +} + +int +main() +{ + test01(); + test_symlinks(); + test_directories(); +} diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/rename.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/rename.cc new file mode 100644 index 00000000000..56039e7f37d --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/rename.cc @@ -0,0 +1,180 @@ +// Copyright (C) 2021 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-DUSE_FILESYSTEM_TS -lstdc++fs" } +// { dg-do run { target c++11 } } +// { dg-require-filesystem-ts "" } + +#include +#include +#include + +namespace fs = std::experimental::filesystem; + +void +test01() +{ + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + auto p1 = __gnu_test::nonexistent_path(); + auto p2 = __gnu_test::nonexistent_path(); + + fs::rename(p1, p2, ec); + VERIFY( ec ); + + ec.clear(); + fs::rename(p1, "", ec); + VERIFY( ec ); + + ec.clear(); + fs::rename("", p1, ec); + VERIFY( ec ); + + ec = bad_ec; + std::ofstream{p1}; // create file + fs::rename(p1, p1, ec); // no-op + VERIFY( !ec ); + VERIFY( is_regular_file(p1) ); + + ec.clear(); + rename(p2, p1, ec); + VERIFY( ec ); + VERIFY( is_regular_file(p1) ); + + ec = bad_ec; + fs::rename(p1, p2, ec); + VERIFY( !ec ); + VERIFY( !exists(p1) ); + VERIFY( is_regular_file(p2) ); + + ec = bad_ec; + std::ofstream{p1}; // create file + fs::rename(p1, p2, ec); + VERIFY( !ec ); + VERIFY( !exists(p1) ); + VERIFY( is_regular_file(p2) ); + + fs::remove(p2, ec); +} + +void +test_symlinks() +{ +#if defined(__MINGW32__) || defined(__MINGW64__) + // No symlink support +#else + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + const auto dir = __gnu_test::nonexistent_path(); + fs::create_directory(dir); + + create_symlink(dir/"nonesuch", dir/"link"); // dangling symlink + ec = bad_ec; + fs::rename(dir/"link", dir/"newlink", ec); + VERIFY( !ec ); + VERIFY( !exists(symlink_status(dir/"link")) ); + VERIFY( is_symlink(dir/"newlink") ); + + __gnu_test::scoped_file f(dir/"file"); + create_symlink(dir/"file", dir/"link"); + ec = bad_ec; + fs::rename(dir/"link", dir/"newerlink", ec); + VERIFY( !ec ); + VERIFY( !exists(symlink_status(dir/"link")) ); + VERIFY( is_symlink(dir/"newerlink") ); + VERIFY( is_regular_file(dir/"file") ); + + fs::remove_all(dir, ec); + f.path.clear(); +#endif +} + +void +test_directories() +{ + std::error_code ec; + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); + + const auto dir = __gnu_test::nonexistent_path(); + fs::create_directory(dir); + __gnu_test::scoped_file f(dir/"file"); + fs::create_directory(dir/"subdir"); + + // Rename directory. + ec = bad_ec; + fs::rename(dir/"subdir", dir/"subdir2", ec); + VERIFY( !ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( !exists(dir/"subdir") ); + + // Cannot rename a directory to a sub-directory of itself. + fs::rename(dir/"subdir2", dir/"subdir2/subsubdir", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( !exists(dir/"subdir2"/"subsubdir") ); + + // Cannot rename a file to the name of an existing directory. + ec.clear(); + fs::rename(dir/"file", dir/"subdir2", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"file") ); + +#if defined(__MINGW32__) || defined(__MINGW64__) + // XXX broken on Windows, see PR 98985 +#else + // Cannot rename a directory to the name of an existing non-directory + ec.clear(); + fs::rename(dir/"subdir2", dir/"file", ec); + VERIFY( ec ); + VERIFY( is_regular_file(dir/"file") ); + VERIFY( is_directory(dir/"subdir2") ); + + // Cannot rename directory to the name of a non-empty directory. + ec.clear(); + __gnu_test::scoped_file f2(dir/"subdir2/file"); + fs::create_directory(dir/"subdir"); + fs::rename(dir/"subdir", dir/"subdir2", ec); + VERIFY( ec ); + VERIFY( is_directory(dir/"subdir") ); + VERIFY( is_directory(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"subdir2/file") ); + + // Can rename a non-empty directory to the name of an empty directory. + ec = bad_ec; + fs::rename(dir/"subdir2", dir/"subdir", ec); + VERIFY( !ec ); + VERIFY( is_directory(dir/"subdir") ); + VERIFY( !exists(dir/"subdir2") ); + VERIFY( is_regular_file(dir/"subdir/file") ); + f2.path.clear(); + + f.path.clear(); +#endif + + fs::remove_all(dir, ec); +} + +int +main() +{ + test01(); + test_symlinks(); + test_directories(); +}