@@ -1717,9 +1717,33 @@ not in any particular release.
</entry>
</row>
-
</tbody>
</tgroup>
</table>
+<section xml:id="iso.2014.specific" xreflabel="Implementation Specific"><info><title>Implementation Specific Behavior</title></info>
+
+ <section xml:id="iso.2014.filesystemts" xreflabel="Implementation Specific Behavior of the Filesystem TS"><info><title>Filesystem TS</title></info>
+ <para>
+ <emphasis>2.1 POSIX conformance [fs.conform.9945]</emphasis>
+ 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.
+ </para>
+ <para>
+ <emphasis>15.30 Rename [fs.op.rename]</emphasis>
+ On Windows, <code>experimental::filesystem::rename</code>
+ is implemented by calling <code>MoveFileExW</code> and so
+ does not meet the requirements of POSIX <code>rename</code>
+ 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).
+ </para>
+ </section>
+</section>
+
</section>
@@ -2992,7 +2992,8 @@ since C++14 and the implementation is complete.
<emphasis>30.10.2.1 [fs.conform.9945]</emphasis>
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.
</para>
<para>
@@ -3025,6 +3026,18 @@ since C++14 and the implementation is complete.
If <code>!is_regular_file(p)</code>, an error is reported.
</para>
+ <para>
+ <emphasis>30.10.15.32 [fs.op.rename]</emphasis>
+ On Windows, <code>filesystem::rename</code>
+ is implemented by calling <code>MoveFileExW</code> and so
+ does not meet the requirements of POSIX <code>rename</code>
+ 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).
+ </para>
+
+
<section xml:id="iso.2017.par2ts" xreflabel="Implementation Specific Behavior of the Parallelism 2 TS"><info><title>Parallelism 2 TS</title></info>
<para>
@@ -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
@@ -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)
{
new file mode 100644
@@ -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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++17" }
+// { dg-do run { target c++17 } }
+// { dg-require-filesystem-ts "" }
+
+#include <filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+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();
+}
new file mode 100644
@@ -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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-DUSE_FILESYSTEM_TS -lstdc++fs" }
+// { dg-do run { target c++11 } }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+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();
+}