Fix error handling in copy_file and equivalent
authorJonathan Wakely <jwakely@redhat.com>
Tue, 25 Oct 2016 15:32:37 +0000 (16:32 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 25 Oct 2016 15:32:37 +0000 (16:32 +0100)
* src/filesystem/ops.cc (do_copy_file): Report an error if source or
destination is not a regular file (LWG 2712).
(equivalent): Fix error handling and result when only one file exists.
* testsuite/experimental/filesystem/operations/copy.cc: Remove files
created by tests. Test copying directories.
* testsuite/experimental/filesystem/operations/copy_file.cc: Remove
files created by tests.
* testsuite/experimental/filesystem/operations/equivalent.cc: New.
* testsuite/experimental/filesystem/operations/is_empty.cc: New.
* testsuite/experimental/filesystem/operations/read_symlink.cc: Remove
file created by test.
* testsuite/experimental/filesystem/operations/remove_all.cc: New.
* testsuite/util/testsuite_fs.h (~scoped_file): Only try to remove
file if path is non-empty, to support removal by other means.

From-SVN: r241521

libstdc++-v3/ChangeLog
libstdc++-v3/src/filesystem/ops.cc
libstdc++-v3/testsuite/experimental/filesystem/operations/copy.cc
libstdc++-v3/testsuite/experimental/filesystem/operations/copy_file.cc
libstdc++-v3/testsuite/experimental/filesystem/operations/equivalent.cc [new file with mode: 0644]
libstdc++-v3/testsuite/experimental/filesystem/operations/is_empty.cc [new file with mode: 0644]
libstdc++-v3/testsuite/experimental/filesystem/operations/read_symlink.cc
libstdc++-v3/testsuite/experimental/filesystem/operations/remove_all.cc [new file with mode: 0644]
libstdc++-v3/testsuite/util/testsuite_fs.h

index e551d392da1bb15a2393c304ae6b21ac688523c8..5086417d4635bddd477a46f8faf0eb202f3b50c1 100644 (file)
@@ -1,3 +1,20 @@
+2016-10-25  Jonathan Wakely  <jwakely@redhat.com>
+
+       * src/filesystem/ops.cc (do_copy_file): Report an error if source or
+       destination is not a regular file (LWG 2712).
+       (equivalent): Fix error handling and result when only one file exists.
+       * testsuite/experimental/filesystem/operations/copy.cc: Remove files
+       created by tests. Test copying directories.
+       * testsuite/experimental/filesystem/operations/copy_file.cc: Remove
+       files created by tests.
+       * testsuite/experimental/filesystem/operations/equivalent.cc: New.
+       * testsuite/experimental/filesystem/operations/is_empty.cc: New.
+       * testsuite/experimental/filesystem/operations/read_symlink.cc: Remove
+       file created by test.
+       * testsuite/experimental/filesystem/operations/remove_all.cc: New.
+       * testsuite/util/testsuite_fs.h (~scoped_file): Only try to remove
+       file if path is non-empty, to support removal by other means.
+
 2016-10-24  Jonathan Wakely  <jwakely@redhat.com>
 
        * src/filesystem/ops.cc (is_empty): Fix error handling.
index d9a12df5fa20848fc03b804556ea04002b56598c..fcf747e6a01afb2159213e60fcb33a65414d94b1 100644 (file)
@@ -350,6 +350,8 @@ namespace
          from_st = &st2;
       }
     f = make_file_status(*from_st);
+    // _GLIBCXX_RESOLVE_LIB_DEFECTS
+    // 2712. copy_file() has a number of unspecified error conditions
     if (!is_regular_file(f))
       {
        ec = std::make_error_code(std::errc::not_supported);
@@ -360,8 +362,13 @@ namespace
 
     if (exists(t))
       {
-       if (!is_other(t) && !is_other(f)
-           && to_st->st_dev == from_st->st_dev
+       if (!is_regular_file(t))
+         {
+           ec = std::make_error_code(std::errc::not_supported);
+           return false;
+         }
+
+       if (to_st->st_dev == from_st->st_dev
            && to_st->st_ino == from_st->st_ino)
          {
            ec = std::make_error_code(std::errc::file_exists);
@@ -912,7 +919,7 @@ fs::equivalent(const path& p1, const path& p2)
 {
   error_code ec;
   auto result = equivalent(p1, p2, ec);
-  if (ec.value())
+  if (ec)
     _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot check file equivalence",
          p1, p2, ec));
   return result;
@@ -922,25 +929,42 @@ bool
 fs::equivalent(const path& p1, const path& p2, error_code& ec) noexcept
 {
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
+  int err = 0;
+  file_status s1, s2;
   stat_type st1, st2;
-  if (::stat(p1.c_str(), &st1) == 0 && ::stat(p2.c_str(), &st2) == 0)
+  if (::stat(p1.c_str(), &st1) == 0)
+    s1 = make_file_status(st1);
+  else if (is_not_found_errno(errno))
+    s1.type(file_type::not_found);
+  else
+    err = errno;
+
+  if (::stat(p2.c_str(), &st2) == 0)
+    s2 = make_file_status(st2);
+  else if (is_not_found_errno(errno))
+    s2.type(file_type::not_found);
+  else
+    err = errno;
+
+  if (exists(s1) && exists(s2))
     {
-      file_status s1 = make_file_status(st1);
-      file_status s2 = make_file_status(st2);
       if (is_other(s1) && is_other(s2))
        {
          ec = std::make_error_code(std::errc::not_supported);
          return false;
        }
       ec.clear();
+      if (is_other(s1) || is_other(s2))
+       return false;
       return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
     }
-  else if (is_not_found_errno(errno))
-    {
-      ec = std::make_error_code(std::errc::no_such_file_or_directory);
-      return false;
-    }
-  ec.assign(errno, std::generic_category());
+  else if (!exists(s1) && !exists(s2))
+    ec = std::make_error_code(std::errc::no_such_file_or_directory);
+  else if (err)
+    ec.assign(err, std::generic_category());
+  else
+    ec.clear();
+  return false;
 #else
   ec = std::make_error_code(std::errc::not_supported);
 #endif
index 2cfb1c1fea4e0981b5685d637eade03072bf3b13..7173788c4511f5b9f74bfe89c8ce1a987a60834d 100644 (file)
@@ -128,6 +128,9 @@ test03()
   fs::copy(from, to);
   VERIFY( fs::exists(to) );
   VERIFY( fs::file_size(to) == fs::file_size(from) );
+
+  remove(from);
+  remove(to);
 }
 
 // Test is_directory(f) case.
@@ -138,6 +141,37 @@ test04()
   auto to = __gnu_test::nonexistent_path();
   std::error_code ec;
 
+  create_directories(from/"a/b/c");
+
+  {
+    __gnu_test::scoped_file f(to);
+    copy(from, to, ec);
+    VERIFY( ec );
+  }
+
+  __gnu_test::scoped_file f1(from/"a/f1");
+  std::ofstream{f1.path} << "file one";
+  __gnu_test::scoped_file f2(from/"a/b/f2");
+  std::ofstream{f2.path} << "file two";
+
+  copy(from, to, ec);
+  VERIFY( !ec );
+  VERIFY( exists(to) && is_empty(to) );
+  remove(to);
+
+  copy(from, to, fs::copy_options::recursive, ec);
+  VERIFY( !ec );
+  VERIFY( exists(to) && !is_empty(to) );
+  VERIFY( is_regular_file(to/"a/f1") && !is_empty(to/"a/f1") );
+  VERIFY( file_size(from/"a/f1") == file_size(to/"a/f1") );
+  VERIFY( is_regular_file(to/"a/b/f2") && !is_empty(to/"a/b/f2") );
+  VERIFY( file_size(from/"a/b/f2") == file_size(to/"a/b/f2") );
+  VERIFY( is_directory(to/"a/b/c") && is_empty(to/"a/b/c") );
+
+  f1.path.clear();
+  f2.path.clear();
+  remove_all(from, ec);
+  remove_all(to, ec);
 }
 
 // Test no-op cases.
index 9cb09d3ebaffd6cacd5996a7630b12c7aa510a70..1e487cd0b0cc80be826f4a6ab9978a7b53c9d020 100644 (file)
@@ -73,6 +73,9 @@ test01()
   VERIFY( !ec );
   VERIFY( exists(to) );
   VERIFY( file_size(to) == file_size(from) );
+
+  remove(from);
+  remove(to);
 }
 
 int
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/equivalent.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/equivalent.cc
new file mode 100644 (file)
index 0000000..77ed054
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (C) 2016 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 "-lstdc++fs" }
+// { dg-do run { target c++11 } }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_fs.h>
+#include <testsuite_hooks.h>
+
+namespace fs = std::experimental::filesystem;
+
+
+void
+test01()
+{
+  auto p1 = __gnu_test::nonexistent_path();
+  auto p2 = __gnu_test::nonexistent_path();
+  std::error_code ec;
+  bool result;
+
+  result = equivalent(p1, p2, ec);
+  VERIFY( ec );
+  VERIFY( !result );
+  const auto bad_ec = ec;
+
+  __gnu_test::scoped_file f1(p1);
+  result = equivalent(p1, p2, ec);
+  VERIFY( !ec );
+  VERIFY( !result );
+
+  __gnu_test::scoped_file f2(p2);
+  ec = bad_ec;
+  result = equivalent(p1, p2, ec);
+  VERIFY( !ec );
+  VERIFY( !result );
+
+  auto p3 = __gnu_test::nonexistent_path();
+  create_hard_link(p1, p3, ec);
+  if (ec)
+    return;  // hard links not supported
+  __gnu_test::scoped_file f3(p3, __gnu_test::scoped_file::adopt_file);
+
+  ec = bad_ec;
+  result = equivalent(p1, p3, ec);
+  VERIFY( !ec );
+  VERIFY( result );
+
+  ec = bad_ec;
+  result = equivalent(p2, p3, ec);
+  VERIFY( !ec );
+  VERIFY( !result );
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/is_empty.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/is_empty.cc
new file mode 100644 (file)
index 0000000..d35967a
--- /dev/null
@@ -0,0 +1,109 @@
+// Copyright (C) 2016 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 "-lstdc++fs" }
+// { dg-do run { target c++11 } }E
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+namespace fs = std::experimental::filesystem;
+
+void
+test01()
+{
+  auto p = __gnu_test::nonexistent_path();
+  create_directory(p);
+  permissions(p, fs::perms::none);
+  std::error_code ec, ec2;
+
+  bool result = fs::is_empty(p, ec);
+  VERIFY( ec == std::make_error_code(std::errc::permission_denied) );
+  VERIFY( !result );
+
+  try {
+    fs::is_empty(p);
+  } catch (const fs::filesystem_error& e) {
+    ec2 = e.code();
+  }
+  VERIFY( ec2 == ec );
+
+  result = fs::is_empty(p/"f", ec);
+  VERIFY( ec == std::make_error_code(std::errc::permission_denied) );
+  VERIFY( !result );
+
+  try {
+    fs::is_empty(p/"f");
+  } catch (const fs::filesystem_error& e) {
+    ec2 = e.code();
+  }
+  VERIFY( ec2 == ec );
+
+  permissions(p, fs::perms::owner_all, ec);
+  remove_all(p, ec);
+}
+
+void
+test02()
+{
+  auto p = __gnu_test::nonexistent_path();
+  create_directory(p);
+  std::error_code ec, bad_ec = make_error_code(std::errc::invalid_argument);
+  bool empty;
+
+  ec = bad_ec;
+  empty = is_empty(p, ec);
+  VERIFY( !ec );
+  VERIFY( empty );
+  empty = is_empty(p);
+  VERIFY( empty );
+
+  __gnu_test::scoped_file f(p/"f");
+  ec = bad_ec;
+  empty = is_empty(f.path, ec);
+  VERIFY( !ec );
+  VERIFY( empty );
+  empty = is_empty(f.path);
+  VERIFY( empty );
+
+  std::ofstream{f.path.native()} << "data";
+  ec = bad_ec;
+  empty = is_empty(p, ec);
+  VERIFY( !ec );
+  VERIFY( !empty );
+  empty = is_empty(p);
+  VERIFY( !empty );
+
+  ec = bad_ec;
+  empty = is_empty(p, ec);
+  VERIFY( !ec );
+  VERIFY( !empty );
+  empty = is_empty(p);
+  VERIFY( !empty );
+
+  f.path.clear();
+  remove_all(p, ec);
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}
index c4137bd9369f09cc293bec74285e75996d353166..c67b3184fdb256a80fa4995822d625af05427566 100644 (file)
@@ -40,6 +40,8 @@ test01()
   auto result = read_symlink(p, ec);
   VERIFY( !ec );
   VERIFY( result == tgt );
+
+  remove(p);
 }
 
 int
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/remove_all.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/remove_all.cc
new file mode 100644 (file)
index 0000000..57d15af
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (C) 2016 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 "-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;
+  std::uintmax_t n;
+
+  n = fs::remove_all("", ec);
+  VERIFY( ec );
+  VERIFY( n == std::uintmax_t(-1) );
+
+  auto p = __gnu_test::nonexistent_path();
+  ec.clear();
+  n = remove_all(p, ec);
+  VERIFY( ec );
+  VERIFY( n == std::uintmax_t(-1) );
+
+  const auto bad_ec = ec;
+  auto link = __gnu_test::nonexistent_path();
+  create_symlink(p, link);  // dangling symlink
+  ec = bad_ec;
+  n = remove_all(link, ec);
+  VERIFY( !ec );
+  VERIFY( n == 1 );
+  VERIFY( !exists(symlink_status(link)) ); // DR 2721
+
+  __gnu_test::scoped_file f(p);
+  create_symlink(p, link);
+  ec = bad_ec;
+  n = remove_all(link, ec);
+  VERIFY( !ec );
+  VERIFY( n == 1 );
+  VERIFY( !exists(symlink_status(link)) );  // The symlink is removed, but
+  VERIFY( exists(p) );                      // its target is not.
+
+  auto dir = __gnu_test::nonexistent_path();
+  create_directories(dir/"a/b/c");
+  ec = bad_ec;
+  n = remove_all(dir/"a", ec);
+  VERIFY( !ec );
+  VERIFY( n == 3 );
+  VERIFY( exists(dir) );
+  VERIFY( !exists(dir/"a") );
+
+  create_directories(dir/"a/b/c");
+  __gnu_test::scoped_file a1(dir/"a/1");
+  __gnu_test::scoped_file a2(dir/"a/2");
+  __gnu_test::scoped_file b1(dir/"a/b/1");
+  __gnu_test::scoped_file b2(dir/"a/b/2");
+  ec = bad_ec;
+  n = remove_all(dir, ec);
+  VERIFY( !ec );
+  VERIFY( n == 8 );
+  VERIFY( !exists(dir) );
+
+  a1.path.clear();
+  a2.path.clear();
+  b1.path.clear();
+  b2.path.clear();
+}
+
+int
+main()
+{
+  test01();
+}
index 5b36670ed5285d7e809bce33fb6aedd84ac49970..7d9b20c2b72d62d6a59b9d7efd822aaffdcf85c4 100644 (file)
@@ -107,7 +107,7 @@ namespace __gnu_test
 
     scoped_file(path_type p, adopt_file_t) : path(p) { }
 
-    ~scoped_file() { remove(path); }
+    ~scoped_file() { if (!path.empty()) remove(path); }
 
     path_type path;
   };