From 1dfd95f0a0ca1d9e6cbc00e6cbfd1fa20a98f312 Mon Sep 17 00:00:00 2001
From: Jonathan Wakely <jwakely@redhat.com>
Date: Fri, 12 Feb 2021 15:13:02 +0000
Subject: 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.
---
 .../experimental/filesystem/operations/rename.cc   | 180 +++++++++++++++++++++
 1 file changed, 180 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/experimental/filesystem/operations/rename.cc

(limited to 'libstdc++-v3/testsuite/experimental/filesystem')

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 0000000..56039e7
--- /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
+// <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();
+}
-- 
cgit v1.1