aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/libcxx/src/filesystem/operations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/libcxx/src/filesystem/operations.cpp')
-rw-r--r--contrib/llvm-project/libcxx/src/filesystem/operations.cpp107
1 files changed, 106 insertions, 1 deletions
diff --git a/contrib/llvm-project/libcxx/src/filesystem/operations.cpp b/contrib/llvm-project/libcxx/src/filesystem/operations.cpp
index 62bcfbff097f..7aeeffaae8f3 100644
--- a/contrib/llvm-project/libcxx/src/filesystem/operations.cpp
+++ b/contrib/llvm-project/libcxx/src/filesystem/operations.cpp
@@ -24,9 +24,10 @@
# define NOMINMAX
# include <windows.h>
#else
-# include <unistd.h>
+# include <dirent.h>
# include <sys/stat.h>
# include <sys/statvfs.h>
+# include <unistd.h>
#endif
#include <time.h>
#include <fcntl.h> /* values for fchmodat */
@@ -1338,6 +1339,19 @@ bool __remove(const path& p, error_code* ec) {
return true;
}
+// We currently have two implementations of `__remove_all`. The first one is general and
+// used on platforms where we don't have access to the `openat()` family of POSIX functions.
+// That implementation uses `directory_iterator`, however it is vulnerable to some race
+// conditions, see https://reviews.llvm.org/D118134 for details.
+//
+// The second implementation is used on platforms where `openat()` & friends are available,
+// and it threads file descriptors through recursive calls to avoid such race conditions.
+#if defined(_LIBCPP_WIN32API)
+# define REMOVE_ALL_USE_DIRECTORY_ITERATOR
+#endif
+
+#if defined(REMOVE_ALL_USE_DIRECTORY_ITERATOR)
+
namespace {
uintmax_t remove_all_impl(path const& p, error_code& ec) {
@@ -1377,6 +1391,97 @@ uintmax_t __remove_all(const path& p, error_code* ec) {
return count;
}
+#else // !REMOVE_ALL_USE_DIRECTORY_ITERATOR
+
+namespace {
+
+template <class Cleanup>
+struct scope_exit {
+ explicit scope_exit(Cleanup const& cleanup)
+ : cleanup_(cleanup)
+ { }
+
+ ~scope_exit() { cleanup_(); }
+
+private:
+ Cleanup cleanup_;
+};
+
+uintmax_t remove_all_impl(int parent_directory, const path& p, error_code& ec) {
+ // First, try to open the path as a directory.
+ const int options = O_CLOEXEC | O_RDONLY | O_DIRECTORY | O_NOFOLLOW;
+ int fd = ::openat(parent_directory, p.c_str(), options);
+ if (fd != -1) {
+ // If that worked, iterate over the contents of the directory and
+ // remove everything in it, recursively.
+ scope_exit close_fd([=] { ::close(fd); });
+ DIR* stream = ::fdopendir(fd);
+ if (stream == nullptr) {
+ ec = detail::capture_errno();
+ return 0;
+ }
+ scope_exit close_stream([=] { ::closedir(stream); });
+
+ uintmax_t count = 0;
+ while (true) {
+ auto [str, type] = detail::posix_readdir(stream, ec);
+ static_assert(std::is_same_v<decltype(str), std::string_view>);
+ if (str == "." || str == "..") {
+ continue;
+ } else if (ec || str.empty()) {
+ break; // we're done iterating through the directory
+ } else {
+ count += remove_all_impl(fd, str, ec);
+ }
+ }
+
+ // Then, remove the now-empty directory itself.
+ if (::unlinkat(parent_directory, p.c_str(), AT_REMOVEDIR) == -1) {
+ ec = detail::capture_errno();
+ return count;
+ }
+
+ return count + 1; // the contents of the directory + the directory itself
+ }
+
+ ec = detail::capture_errno();
+
+ // If we failed to open `p` because it didn't exist, it's not an
+ // error -- it might have moved or have been deleted already.
+ if (ec == errc::no_such_file_or_directory) {
+ ec.clear();
+ return 0;
+ }
+
+ // If opening `p` failed because it wasn't a directory, remove it as
+ // a normal file instead. Note that `openat()` can return either ENOTDIR
+ // or ELOOP depending on the exact reason of the failure.
+ if (ec == errc::not_a_directory || ec == errc::too_many_symbolic_link_levels) {
+ ec.clear();
+ if (::unlinkat(parent_directory, p.c_str(), /* flags = */0) == -1) {
+ ec = detail::capture_errno();
+ return 0;
+ }
+ return 1;
+ }
+
+ // Otherwise, it's a real error -- we don't remove anything.
+ return 0;
+}
+
+} // end namespace
+
+uintmax_t __remove_all(const path& p, error_code* ec) {
+ ErrorHandler<uintmax_t> err("remove_all", ec, &p);
+ error_code mec;
+ uintmax_t count = remove_all_impl(AT_FDCWD, p, mec);
+ if (mec)
+ return err.report(mec);
+ return count;
+}
+
+#endif // REMOVE_ALL_USE_DIRECTORY_ITERATOR
+
void __rename(const path& from, const path& to, error_code* ec) {
ErrorHandler<void> err("rename", ec, &from, &to);
if (detail::rename(from.c_str(), to.c_str()) == -1)