+2020-01-08 Jonathan Wakely <jwakely@redhat.com>
+
+ PR libstdc++/93201
+ * src/c++17/fs_ops.cc (do_remove_all): New function implementing more
+ detailed error reporting for remove_all. Check result of recursive
+ call before incrementing iterator.
+ (remove_all(const path&), remove_all(const path&, error_code&)): Use
+ do_remove_all.
+ * src/filesystem/ops.cc (remove_all(const path&, error_code&)): Check
+ result of recursive call before incrementing iterator.
+ * testsuite/27_io/filesystem/operations/remove_all.cc: Check errors
+ are reported correctly.
+ * testsuite/experimental/filesystem/operations/remove_all.cc: Likewise.
+
2020-01-07 Thomas Rodgers <trodgers@redhat.com>
* include/std/condition_variable
return false;
}
+namespace std::filesystem
+{
+namespace
+{
+ struct ErrorReporter
+ {
+ explicit
+ ErrorReporter(error_code& ec) : code(&ec)
+ { }
+
+ explicit
+ ErrorReporter(const char* s, const path& p)
+ : code(nullptr), msg(s), path1(&p)
+ { }
+
+ error_code* code;
+ const char* msg;
+ const path* path1;
+
+ void
+ report(const error_code& ec) const
+ {
+ if (code)
+ *code = ec;
+ else
+ _GLIBCXX_THROW_OR_ABORT(filesystem_error(msg, *path1, ec));
+ }
+
+ void
+ report(const error_code& ec, const path& path2) const
+ {
+ if (code)
+ *code = ec;
+ else if (path2 != *path1)
+ _GLIBCXX_THROW_OR_ABORT(filesystem_error(msg, *path1, path2, ec));
+ else
+ _GLIBCXX_THROW_OR_ABORT(filesystem_error(msg, *path1, ec));
+ }
+ };
+
+ uintmax_t
+ do_remove_all(const path& p, const ErrorReporter& err)
+ {
+ error_code ec;
+ const auto s = symlink_status(p, ec);
+ if (!status_known(s))
+ {
+ if (ec)
+ err.report(ec, p);
+ return -1;
+ }
+
+ ec.clear();
+ if (s.type() == file_type::not_found)
+ return 0;
+
+ uintmax_t count = 0;
+ if (s.type() == file_type::directory)
+ {
+ directory_iterator d(p, ec), end;
+ while (d != end)
+ {
+ const auto removed = fs::do_remove_all(d->path(), err);
+ if (removed == numeric_limits<uintmax_t>::max())
+ return -1;
+ count += removed;
+
+ d.increment(ec);
+ if (ec)
+ {
+ err.report(ec, p);
+ return -1;
+ }
+ }
+ }
+
+ if (fs::remove(p, ec))
+ ++count;
+ if (ec)
+ {
+ err.report(ec, p);
+ return -1;
+ }
+ return count;
+ }
+}
+}
std::uintmax_t
fs::remove_all(const path& p)
{
- error_code ec;
- const auto result = remove_all(p, ec);
- if (ec)
- _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot remove all", p, ec));
- return result;
+ return fs::do_remove_all(p, ErrorReporter{"cannot remove all", p});
}
std::uintmax_t
fs::remove_all(const path& p, error_code& ec)
{
- const auto s = symlink_status(p, ec);
- if (!status_known(s))
- return -1;
-
ec.clear();
- if (s.type() == file_type::not_found)
- return 0;
-
- uintmax_t count = 0;
- if (s.type() == file_type::directory)
- {
- for (directory_iterator d(p, ec), end; !ec && d != end; d.increment(ec))
- count += fs::remove_all(d->path(), ec);
- if (ec.value() == ENOENT)
- ec.clear();
- else if (ec)
- return -1;
- }
-
- if (fs::remove(p, ec))
- ++count;
- return ec ? -1 : count;
+ return fs::do_remove_all(p, ErrorReporter{ec});
}
void
uintmax_t count = 0;
if (s.type() == file_type::directory)
{
- for (directory_iterator d(p, ec), end; !ec && d != end; d.increment(ec))
- count += fs::remove_all(d->path(), ec);
- if (ec.value() == ENOENT)
- ec.clear();
- else if (ec)
- return -1;
+ directory_iterator d(p, ec), end;
+ while (!ec && d != end)
+ {
+ const auto removed = fs::remove_all(d->path(), ec);
+ if (removed == numeric_limits<uintmax_t>::max())
+ return -1;
+ count += removed;
+ d.increment(ec);
+ if (ec)
+ return -1;
+ }
}
if (fs::remove(p, ec))
VERIFY( !exists(p) );
}
+void
+test04()
+{
+#if defined(__MINGW32__) || defined(__MINGW64__)
+ // no permissions
+#else
+ // PR libstdc++/93201
+ std::error_code ec;
+ std::uintmax_t n;
+
+ auto dir = __gnu_test::nonexistent_path();
+ fs::create_directory(dir);
+ __gnu_test::scoped_file f(dir/"file");
+ // remove write permission on the directory:
+ fs::permissions(dir, fs::perms::owner_read|fs::perms::owner_exec);
+ n = fs::remove_all(dir, ec);
+ VERIFY( n == -1 );
+ VERIFY( ec == std::errc::permission_denied ); // not ENOTEMPTY
+
+ try {
+ fs::remove_all(dir);
+ VERIFY( false );
+ } catch (const fs::filesystem_error& e) {
+ VERIFY( e.code() == std::errc::permission_denied );
+ // First path is the argument to remove_all
+ VERIFY( e.path1() == dir );
+ // Second path is the first file that couldn't be removed
+ VERIFY( e.path2() == dir/"file" );
+ }
+
+ fs::permissions(dir, fs::perms::owner_write, fs::perm_options::add);
+#endif
+}
+
int
main()
{
test01();
test02();
test03();
+ test04();
}
VERIFY( !exists(dir) );
}
+void
+test04()
+{
+#if defined(__MINGW32__) || defined(__MINGW64__)
+ // no permissions
+#else
+ // PR libstdc++/93201
+ std::error_code ec;
+ std::uintmax_t n;
+
+ auto dir = __gnu_test::nonexistent_path();
+ fs::create_directory(dir);
+ __gnu_test::scoped_file f(dir/"file");
+ // remove write permission on the directory:
+ fs::permissions(dir, fs::perms::owner_read|fs::perms::owner_exec);
+ n = fs::remove_all(dir, ec);
+ VERIFY( n == -1 );
+ VERIFY( ec == std::errc::permission_denied ); // not ENOTEMPTY
+
+ try {
+ fs::remove_all(dir);
+ VERIFY( false );
+ } catch (const fs::filesystem_error& e) {
+ VERIFY( e.code() == std::errc::permission_denied );
+ // First path is the argument to remove_all
+ VERIFY( e.path1() == dir );
+ }
+
+ fs::permissions(dir, fs::perms::owner_write|fs::perms::add_perms);
+#endif
+}
+
int
main()
{
test01();
test02();
+ test04();
}