From ec04aad76d5a4ac4759a78a04cbd950f4a1e9517 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Tue, 25 Oct 2016 16:32:37 +0100 Subject: [PATCH] Fix error handling in copy_file and equivalent * 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 | 17 +++ libstdc++-v3/src/filesystem/ops.cc | 48 ++++++-- .../filesystem/operations/copy.cc | 34 ++++++ .../filesystem/operations/copy_file.cc | 3 + .../filesystem/operations/equivalent.cc | 74 ++++++++++++ .../filesystem/operations/is_empty.cc | 109 ++++++++++++++++++ .../filesystem/operations/read_symlink.cc | 2 + .../filesystem/operations/remove_all.cc | 92 +++++++++++++++ libstdc++-v3/testsuite/util/testsuite_fs.h | 2 +- 9 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 libstdc++-v3/testsuite/experimental/filesystem/operations/equivalent.cc create mode 100644 libstdc++-v3/testsuite/experimental/filesystem/operations/is_empty.cc create mode 100644 libstdc++-v3/testsuite/experimental/filesystem/operations/remove_all.cc diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index e551d392da1..5086417d463 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,20 @@ +2016-10-25 Jonathan Wakely + + * 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 * src/filesystem/ops.cc (is_empty): Fix error handling. diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc index d9a12df5fa2..fcf747e6a01 100644 --- a/libstdc++-v3/src/filesystem/ops.cc +++ b/libstdc++-v3/src/filesystem/ops.cc @@ -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 diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/copy.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/copy.cc index 2cfb1c1fea4..7173788c451 100644 --- a/libstdc++-v3/testsuite/experimental/filesystem/operations/copy.cc +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/copy.cc @@ -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. diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/copy_file.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/copy_file.cc index 9cb09d3ebaf..1e487cd0b0c 100644 --- a/libstdc++-v3/testsuite/experimental/filesystem/operations/copy_file.cc +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/copy_file.cc @@ -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 index 00000000000..77ed0547712 --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/equivalent.cc @@ -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 +// . + +// { dg-options "-lstdc++fs" } +// { dg-do run { target c++11 } } +// { dg-require-filesystem-ts "" } + +#include +#include +#include + +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 index 00000000000..d35967a8173 --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/is_empty.cc @@ -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 +// . + +// { dg-options "-lstdc++fs" } +// { dg-do run { target c++11 } }E +// { dg-require-filesystem-ts "" } + +#include +#include +#include + +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(); +} diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/read_symlink.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/read_symlink.cc index c4137bd9369..c67b3184fdb 100644 --- a/libstdc++-v3/testsuite/experimental/filesystem/operations/read_symlink.cc +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/read_symlink.cc @@ -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 index 00000000000..57d15af9c5c --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/remove_all.cc @@ -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 +// . + +// { dg-options "-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; + 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(); +} diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h b/libstdc++-v3/testsuite/util/testsuite_fs.h index 5b36670ed52..7d9b20c2b72 100644 --- a/libstdc++-v3/testsuite/util/testsuite_fs.h +++ b/libstdc++-v3/testsuite/util/testsuite_fs.h @@ -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; };