glibcxx_cv_fdopendir, [dnl
GCC_TRY_COMPILE_OR_LINK(
[#include <dirent.h>],
- [::fdopendir(1);],
+ [::DIR* dir = ::fdopendir(1);],
[glibcxx_cv_fdopendir=yes],
[glibcxx_cv_fdopendir=no])
])
AC_DEFINE(HAVE_FDOPENDIR, 1, [Define if fdopendir is available in <dirent.h>.])
fi
dnl
+ AC_CACHE_CHECK([for dirfd],
+ glibcxx_cv_dirfd, [dnl
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <dirent.h>],
+ [int fd = ::dirfd((::DIR*)0);],
+ [glibcxx_cv_dirfd=yes],
+ [glibcxx_cv_dirfd=no])
+ ])
+ if test $glibcxx_cv_dirfd = yes; then
+ AC_DEFINE(HAVE_DIRFD, 1, [Define if dirfd is available in <dirent.h>.])
+ fi
+dnl
+ AC_CACHE_CHECK([for unlinkat],
+ glibcxx_cv_unlinkat, [dnl
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <fcntl.h>
+ #include <unistd.h>],
+ [::unlinkat(AT_FDCWD, "", AT_REMOVEDIR);],
+ [glibcxx_cv_unlinkat=yes],
+ [glibcxx_cv_unlinkat=no])
+ ])
+ if test $glibcxx_cv_unlinkat = yes; then
+ AC_DEFINE(HAVE_UNLINKAT, 1, [Define if unlinkat is available in <fcntl.h>.])
+ fi
+dnl
CXXFLAGS="$ac_save_CXXFLAGS"
AC_LANG_RESTORE
])
/* Define to 1 if you have the <dirent.h> header file. */
#undef HAVE_DIRENT_H
+/* Define if dirfd is available in <dirent.h>. */
+#undef HAVE_DIRFD
+
/* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
+/* Define if unlinkat is available in <fcntl.h>. */
+#undef HAVE_UNLINKAT
+
/* Define to 1 if you have the `uselocale' function. */
#undef HAVE_USELOCALE
int
main ()
{
-::fdopendir(1);
+::DIR* dir = ::fdopendir(1);
;
return 0;
}
int
main ()
{
-::fdopendir(1);
+::DIR* dir = ::fdopendir(1);
;
return 0;
}
$as_echo "#define HAVE_FDOPENDIR 1" >>confdefs.h
fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dirfd" >&5
+$as_echo_n "checking for dirfd... " >&6; }
+if ${glibcxx_cv_dirfd+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test x$gcc_no_link = xyes; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <dirent.h>
+int
+main ()
+{
+int fd = ::dirfd((::DIR*)0);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ glibcxx_cv_dirfd=yes
+else
+ glibcxx_cv_dirfd=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+ if test x$gcc_no_link = xyes; then
+ as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
+fi
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <dirent.h>
+int
+main ()
+{
+int fd = ::dirfd((::DIR*)0);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+ glibcxx_cv_dirfd=yes
+else
+ glibcxx_cv_dirfd=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_dirfd" >&5
+$as_echo "$glibcxx_cv_dirfd" >&6; }
+ if test $glibcxx_cv_dirfd = yes; then
+
+$as_echo "#define HAVE_DIRFD 1" >>confdefs.h
+
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for unlinkat" >&5
+$as_echo_n "checking for unlinkat... " >&6; }
+if ${glibcxx_cv_unlinkat+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test x$gcc_no_link = xyes; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <fcntl.h>
+ #include <unistd.h>
+int
+main ()
+{
+::unlinkat(AT_FDCWD, "", AT_REMOVEDIR);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ glibcxx_cv_unlinkat=yes
+else
+ glibcxx_cv_unlinkat=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+ if test x$gcc_no_link = xyes; then
+ as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
+fi
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <fcntl.h>
+ #include <unistd.h>
+int
+main ()
+{
+::unlinkat(AT_FDCWD, "", AT_REMOVEDIR);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+ glibcxx_cv_unlinkat=yes
+else
+ glibcxx_cv_unlinkat=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_unlinkat" >&5
+$as_echo "$glibcxx_cv_unlinkat" >&6; }
+ if test $glibcxx_cv_unlinkat = yes; then
+
+$as_echo "#define HAVE_UNLINKAT 1" >>confdefs.h
+
+ fi
CXXFLAGS="$ac_save_CXXFLAGS"
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
struct _Dir_stack;
std::__shared_ptr<_Dir_stack> _M_dirs;
+
+ recursive_directory_iterator&
+ __erase(error_code* = nullptr);
+
+ friend uintmax_t
+ filesystem::remove_all(const path&, error_code&);
+ friend uintmax_t
+ filesystem::remove_all(const path&);
};
/// @relates std::filesystem::recursive_directory_iterator @{
bool is_regular_file(file_status) noexcept;
bool is_symlink(file_status) noexcept;
+ bool remove(const path&, error_code&) noexcept;
+ uintmax_t remove_all(const path&);
+ uintmax_t remove_all(const path&, error_code&);
+
/// @}
} // namespace filesystem
_GLIBCXX_END_NAMESPACE_VERSION
struct fs::_Dir : _Dir_base
{
_Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
- error_code& ec)
- : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
+ [[maybe_unused]] bool filename_only, error_code& ec)
+ : _Dir_base(fdcwd(), p.c_str(), skip_permission_denied, nofollow, ec)
{
+#if _GLIBCXX_HAVE_DIRFD
+ if (filename_only)
+ return; // Do not store path p when we aren't going to use it.
+#endif
+
if (!ec)
path = p;
}
return false;
}
- fs::path path;
+ // Return a file descriptor for the directory and current entry's path.
+ // If dirfd is available, use it and return only the filename.
+ // Otherwise, return AT_FDCWD and return the full path.
+ pair<int, const posix::char_type*>
+ dir_and_pathname() const noexcept
+ {
+ const fs::path& p = entry.path();
+#if _GLIBCXX_HAVE_DIRFD
+ if (!p.empty())
+ return {::dirfd(this->dirp), std::prev(p.end())->c_str()};
+#endif
+ return {this->fdcwd(), p.c_str()};
+ }
+
+ // Create a new _Dir for the directory this->entry.path().
+ _Dir
+ open_subdir(bool skip_permission_denied, bool nofollow,
+ error_code& ec) const noexcept
+ {
+ auto [dirfd, pathname] = dir_and_pathname();
+ _Dir_base d(dirfd, pathname, skip_permission_denied, nofollow, ec);
+ // If this->path is empty, the new _Dir should have an empty path too.
+ const fs::path& p = this->path.empty() ? this->path : this->entry.path();
+ return _Dir(std::exchange(d.dirp, nullptr), p);
+ }
+
+ bool
+ do_unlink(bool is_directory, error_code& ec) const noexcept
+ {
+#if _GLIBCXX_HAVE_UNLINKAT
+ auto [dirfd, pathname] = dir_and_pathname();
+ if (::unlinkat(dirfd, pathname, is_directory ? AT_REMOVEDIR : 0) == -1)
+ {
+ ec.assign(errno, std::generic_category());
+ return false;
+ }
+ else
+ {
+ ec.clear();
+ return true;
+ }
+#else
+ return fs::remove(entry.path(), ec);
+#endif
+ }
+
+ // Remove the non-directory that this->entry refers to.
+ bool
+ unlink(error_code& ec) const noexcept
+ { return do_unlink(/* is_directory*/ false, ec); }
+
+ // Remove the directory that this->entry refers to.
+ bool
+ rmdir(error_code& ec) const noexcept
+ { return do_unlink(/* is_directory*/ true, ec); }
+
+ fs::path path; // Empty if only using unlinkat with file descr.
directory_entry entry;
};
{
return (obj & bits) != Bitmask::none;
}
+
+// Non-standard directory option flags, currently only for internal use:
+//
+// Do not allow directory iterator to open a symlink.
+// This might seem redundant given directory_options::follow_directory_symlink
+// but that is only checked for recursing into sub-directories, and we need
+// something that controls the initial opendir() call in the constructor.
+constexpr fs::directory_options __directory_iterator_nofollow{64};
+// Do not store full paths in std::filesystem::recursive_directory_iterator.
+// When fs::remove_all uses recursive_directory_iterator::__erase and unlinkat
+// is available in libc, we do not need the parent directory's path, only the
+// filenames of the directory entries (and a file descriptor for the parent).
+// This flag avoids allocating memory for full paths that won't be needed.
+constexpr fs::directory_options __directory_iterator_filename_only{128};
}
fs::directory_iterator::
// Do not report an error for permission denied errors.
const bool skip_permission_denied
= is_set(options, directory_options::skip_permission_denied);
- // Do not allow opening a symlink (used by filesystem::remove_all)
- const bool nofollow
- = is_set(options, __directory_iterator_nofollow);
+ // Do not allow opening a symlink.
+ const bool nofollow = is_set(options, __directory_iterator_nofollow);
error_code ec;
- _Dir dir(p, skip_permission_denied, nofollow, ec);
+ _Dir dir(p, skip_permission_denied, nofollow, /*filename only*/false, ec);
if (dir.dirp)
{
struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
{
- _Dir_stack(directory_options opts, posix::DIR* dirp, const path& p)
+ _Dir_stack(directory_options opts, _Dir&& dir)
: options(opts), pending(true)
{
- this->emplace(dirp, p);
+ this->push(std::move(dir));
}
+ path::string_type orig;
const directory_options options;
bool pending;
void clear() { c.clear(); }
+
+ path current_path() const
+ {
+ path p;
+ if (top().path.empty())
+ {
+ // Reconstruct path that failed from dir stack.
+ p = orig;
+ for (auto& d : this->c)
+ p /= d.entry.path();
+ }
+ else
+ p = top().entry.path();
+ return p;
+ }
};
fs::recursive_directory_iterator::
recursive_directory_iterator(const path& p, directory_options options,
error_code* ecptr)
{
- if (posix::DIR* dirp = posix::opendir(p.c_str()))
- {
- if (ecptr)
- ecptr->clear();
- auto sp = std::__make_shared<_Dir_stack>(options, dirp, p);
- if (ecptr ? sp->top().advance(*ecptr) : sp->top().advance())
- _M_dirs.swap(sp);
- }
- else
+ // Do not report an error for permission denied errors.
+ const bool skip_permission_denied
+ = is_set(options, directory_options::skip_permission_denied);
+ // Do not allow opening a symlink as the starting directory.
+ const bool nofollow = is_set(options, __directory_iterator_nofollow);
+ // Prefer to store only filenames (not full paths) in directory_entry values.
+ const bool filename_only
+ = is_set(options, __directory_iterator_filename_only);
+
+ error_code ec;
+ _Dir dir(p, skip_permission_denied, nofollow, filename_only, ec);
+
+ if (dir.dirp)
{
- const int err = errno;
- if (fs::is_permission_denied_error(err)
- && is_set(options, fs::directory_options::skip_permission_denied))
+ auto sp = std::__make_shared<_Dir_stack>(options, std::move(dir));
+ if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr)
+ : sp->top().advance(skip_permission_denied))
{
- if (ecptr)
- ecptr->clear();
- return;
+ _M_dirs.swap(sp);
+ if (filename_only) // Need to save original path for error reporting.
+ _M_dirs->orig = p.native();
}
-
- if (!ecptr)
- _GLIBCXX_THROW_OR_ABORT(filesystem_error(
- "recursive directory iterator cannot open directory", p,
- std::error_code(err, std::generic_category())));
-
- ecptr->assign(err, std::generic_category());
}
+ else if (ecptr)
+ *ecptr = ec;
+ else if (ec)
+ _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
+ "recursive directory iterator cannot open directory", p, ec));
}
fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
if (std::exchange(_M_dirs->pending, true) && top.should_recurse(follow, ec))
{
- _Dir dir(top.entry.path(), skip_permission_denied, !follow, ec);
+ _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec);
if (ec)
{
_M_dirs.reset();
return *this;
}
if (dir.dirp)
- _M_dirs->push(std::move(dir));
+ _M_dirs->push(std::move(dir));
}
while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec)
{
_M_dirs->pending = false;
}
+
+// Used to implement filesystem::remove_all.
+fs::recursive_directory_iterator&
+fs::recursive_directory_iterator::__erase(error_code* ecptr)
+{
+ error_code ec;
+ if (!_M_dirs)
+ {
+ ec = std::make_error_code(errc::invalid_argument);
+ return *this;
+ }
+
+ // We never want to skip permission denied when removing files.
+ const bool skip_permission_denied = false;
+ // We never want to follow directory symlinks when removing files.
+ const bool nofollow = true;
+
+ // Loop until we find something we can remove.
+ while (!ec)
+ {
+ auto& top = _M_dirs->top();
+
+ if (top.entry._M_type == file_type::directory)
+ {
+ _Dir dir = top.open_subdir(skip_permission_denied, nofollow, ec);
+ if (!ec)
+ {
+ __glibcxx_assert(dir.dirp != nullptr);
+ if (dir.advance(skip_permission_denied, ec))
+ {
+ // Non-empty directory, recurse into it.
+ _M_dirs->push(std::move(dir));
+ continue;
+ }
+ if (!ec)
+ {
+ // Directory is empty so we can remove it.
+ if (top.rmdir(ec))
+ break; // Success
+ }
+ }
+ }
+ else if (top.unlink(ec))
+ break; // Success
+ else if (top.entry._M_type == file_type::none)
+ {
+ // We did not have a cached type, so it's possible that top.entry
+ // is actually a directory, and that's why the unlink above failed.
+#ifdef EPERM
+ // POSIX.1-2017 says unlinking a directory returns EPERM,
+ // but LSB allows EISDIR too. Some targets don't even define EPERM.
+ if (ec.value() == EPERM || ec.value() == EISDIR)
+#else
+ if (ec.value() == EISDIR)
+#endif
+ {
+ // Retry, treating it as a directory.
+ top.entry._M_type = file_type::directory;
+ ec.clear();
+ continue;
+ }
+ }
+ }
+
+ if (!ec)
+ {
+ // We successfully removed the current entry, so advance to the next one.
+ if (_M_dirs->top().advance(skip_permission_denied, ec))
+ return *this;
+ else if (!ec)
+ {
+ // Reached the end of the current directory.
+ _M_dirs->pop();
+ if (_M_dirs->empty())
+ _M_dirs.reset();
+ return *this;
+ }
+ }
+
+ // Reset _M_dirs to empty.
+ auto dirs = std::move(_M_dirs);
+
+ // Need to report an error
+ if (ecptr)
+ *ecptr = ec;
+ else
+ _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error("cannot remove all",
+ dirs->orig,
+ dirs->current_path(),
+ ec));
+
+ return *this;
+}
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, directory_options{99}, 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)
{
- return fs::do_remove_all(p, ErrorReporter{"cannot remove all", p});
+ uintmax_t count = 0;
+ auto st = filesystem::status(p);
+ if (!exists(st))
+ return 0;
+ if (is_directory(st))
+ {
+ recursive_directory_iterator dir(p, directory_options{64|128}), end;
+ path failed;
+ while (dir != end)
+ {
+ failed = dir->path();
+ dir.__erase();
+ ++count;
+ }
+ }
+ return count + fs::remove(p);
}
std::uintmax_t
fs::remove_all(const path& p, error_code& ec)
{
- ec.clear();
- return fs::do_remove_all(p, ErrorReporter{ec});
+ uintmax_t count = 0;
+ recursive_directory_iterator dir(p, directory_options{64|128}, ec);
+ switch (ec.value())
+ {
+ case 0:
+ {
+ recursive_directory_iterator end;
+ while (dir != end)
+ {
+ dir.__erase(&ec);
+ if (ec)
+ return -1;
+ ++count;
+ }
+ }
+ break;
+ case ENOENT:
+ // Our work here is done.
+ ec.clear();
+ return 0;
+ case ENOTDIR:
+ case ELOOP:
+ // Not a directory, will remove below.
+ break;
+ default:
+ // An error occurred.
+ return -1;
+ }
+ // Remove p itself, which is either a non-directory or is now empty.
+ if (int last = fs::remove(p, ec); !ec)
+ return count + last;
+ return -1;
}
void
# ifdef _GLIBCXX_HAVE_SYS_TYPES_H
# include <sys/types.h>
# endif
-# include <dirent.h>
-#endif
-#ifdef _GLIBCXX_HAVE_FCNTL_H
-# include <fcntl.h> // O_NOFOLLOW, O_DIRECTORY
-# include <unistd.h> // close
+# include <dirent.h> // opendir, readdir, fdopendir, dirfd
+# ifdef _GLIBCXX_HAVE_FCNTL_H
+# include <fcntl.h> // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc.
+# include <unistd.h> // close, unlinkat
+# endif
#endif
namespace std _GLIBCXX_VISIBILITY(default)
namespace posix = __gnu_posix;
+inline bool
+is_permission_denied_error(int e)
+{
+ if (e == EACCES)
+ return true;
+#ifdef __APPLE__
+ if (e == EPERM) // See PR 99533
+ return true;
+#endif
+ return false;
+}
+
struct _Dir_base
{
_Dir_base(posix::DIR* dirp = nullptr) : dirp(dirp) { }
// If no error occurs then dirp is non-null,
- // otherwise null (even if an EACCES error is ignored).
- _Dir_base(const posix::char_type* pathname, bool skip_permission_denied,
- [[maybe_unused]] bool nofollow, error_code& ec) noexcept
- : dirp(nullptr)
+ // otherwise null (even if a permission denied error is ignored).
+ _Dir_base(int fd, const posix::char_type* pathname,
+ bool skip_permission_denied, bool nofollow,
+ error_code& ec) noexcept
+ : dirp(_Dir_base::openat(fd, pathname, nofollow))
{
-#if defined O_RDONLY && O_NOFOLLOW && defined O_DIRECTORY && defined O_CLOEXEC \
- && defined _GLIBCXX_HAVE_FDOPENDIR && !_GLIBCXX_FILESYSTEM_IS_WINDOWS
- if (nofollow)
- {
- // Do not allow opening a symlink (used by filesystem::remove_all)
- const int flags = O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC;
- int fd = ::open(pathname, flags);
- if (fd != -1)
- {
- if ((dirp = ::fdopendir(fd)))
- {
- ec.clear();
- return;
- }
- }
- if (errno == EACCES && skip_permission_denied)
- ec.clear();
- else
- ec.assign(errno, std::generic_category());
- return;
- }
-#endif
-
- if ((dirp = posix::opendir(pathname)))
+ if (dirp)
ec.clear();
- else if (errno == EACCES && skip_permission_denied)
+ else if (is_permission_denied_error(errno) && skip_permission_denied)
ec.clear();
else
ec.assign(errno, std::generic_category());
}
}
+ static constexpr int
+ fdcwd() noexcept
+ {
+#ifdef AT_FDCWD
+ return AT_FDCWD;
+#else
+ return -1; // Use invalid fd if AT_FDCWD isn't supported.
+#endif
+ }
+
static bool is_dot_or_dotdot(const char* s) noexcept
{ return !strcmp(s, ".") || !strcmp(s, ".."); }
{ return !wcscmp(s, L".") || !wcscmp(s, L".."); }
#endif
- posix::DIR* dirp;
-};
-
-inline bool
-is_permission_denied_error(int e)
-{
- if (e == EACCES)
- return true;
-#ifdef __APPLE__
- if (e == EPERM) // See PR 99533
+ // Set the close-on-exec flag if not already done via O_CLOEXEC.
+ static bool
+ set_close_on_exec([[maybe_unused]] int fd)
+ {
+#if ! defined O_CLOEXEC && defined FD_CLOEXEC
+ int flags = ::fcntl(fd, F_GETFD);
+ if (flags == -1 || ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ return false;
+#endif
return true;
+ }
+
+ static ::DIR*
+ openat(int fd, const posix::char_type* pathname, bool nofollow)
+ {
+#if _GLIBCXX_HAVE_FDOPENDIR && defined O_RDONLY && defined O_DIRECTORY \
+ && ! _GLIBCXX_FILESYSTEM_IS_WINDOWS
+
+ // Any file descriptor we open here should be closed on exec.
+#ifdef O_CLOEXEC
+ constexpr int close_on_exec = O_CLOEXEC;
+#else
+ constexpr int close_on_exec = 0;
+#endif
+
+ int flags = O_RDONLY | O_DIRECTORY | close_on_exec;
+
+ // Directory iterators are vulnerable to race conditions unless O_NOFOLLOW
+ // is supported, because a directory could be replaced with a symlink after
+ // checking is_directory(symlink_status(f)). O_NOFOLLOW avoids the race.
+#ifdef O_NOFOLLOW
+ if (nofollow)
+ flags |= O_NOFOLLOW;
+#else
+ nofollow = false;
+#endif
+
+
+#ifdef AT_FDCWD
+ fd = ::openat(fd, pathname, flags);
+#else
+ // If we cannot use openat, there's no benefit to using posix::open unless
+ // we will use O_NOFOLLOW, so just use the simpler posix::opendir.
+ if (!nofollow)
+ return posix::opendir(pathname);
+
+ fd = ::open(pathname, flags);
#endif
- return false;
-}
+
+ if (fd == -1)
+ return nullptr;
+ if (set_close_on_exec(fd))
+ if (::DIR* dirp = ::fdopendir(fd))
+ return dirp;
+ int err = errno;
+ ::close(fd);
+ errno = err;
+ return nullptr;
+#else
+ return posix::opendir(pathname);
+#endif
+ }
+
+ posix::DIR* dirp;
+};
} // namespace filesystem
#endif
}
-constexpr directory_options __directory_iterator_nofollow{99};
-
_GLIBCXX_END_NAMESPACE_FILESYSTEM
_GLIBCXX_END_NAMESPACE_VERSION
{
_Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
error_code& ec)
- : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
+ : _Dir_base(this->fdcwd(), p.c_str(), skip_permission_denied, nofollow, ec)
{
if (!ec)
path = p;
return false;
}
+ // Return a file descriptor for the directory and current entry's path.
+ // If dirfd is available, use it and return only the filename.
+ // Otherwise, return AT_FDCWD and return the full path.
+ pair<int, const posix::char_type*>
+ dir_and_pathname() const noexcept
+ {
+ const fs::path& p = entry.path();
+#if _GLIBCXX_HAVE_DIRFD
+ return {::dirfd(this->dirp), std::prev(p.end())->c_str()};
+#endif
+ return {this->fdcwd(), p.c_str()};
+ }
+
+ // Create a new _Dir for the directory this->entry.path().
+ _Dir
+ open_subdir(bool skip_permission_denied, bool nofollow,
+ error_code& ec) noexcept
+ {
+ auto [dirfd, pathname] = dir_and_pathname();
+ _Dir_base d(dirfd, pathname, skip_permission_denied, nofollow, ec);
+ return _Dir(std::exchange(d.dirp, nullptr), entry.path());
+ }
+
fs::path path;
directory_entry entry;
file_type type = file_type::none;
// Do not report an error for permission denied errors.
const bool skip_permission_denied
= is_set(options, directory_options::skip_permission_denied);
- // Do not allow opening a symlink (used by filesystem::remove_all)
- const bool nofollow
- = is_set(options, __directory_iterator_nofollow);
error_code ec;
- _Dir dir(p, skip_permission_denied, nofollow, ec);
+ _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec);
if (dir.dirp)
{
struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
{
+ _Dir_stack(_Dir&& dir)
+ {
+ this->push(std::move(dir));
+ }
+
void clear() { c.clear(); }
};
error_code* ecptr)
: _M_options(options), _M_pending(true)
{
- if (posix::DIR* dirp = posix::opendir(p.c_str()))
- {
- if (ecptr)
- ecptr->clear();
- auto sp = std::make_shared<_Dir_stack>();
- sp->push(_Dir{ dirp, p });
- if (ecptr ? sp->top().advance(*ecptr) : sp->top().advance())
- _M_dirs.swap(sp);
- }
- else
+ // Do not report an error for permission denied errors.
+ const bool skip_permission_denied
+ = is_set(options, directory_options::skip_permission_denied);
+
+ error_code ec;
+ _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec);
+
+ if (dir.dirp)
{
- const int err = errno;
- if (std::filesystem::is_permission_denied_error(err)
- && is_set(options, fs::directory_options::skip_permission_denied))
+ auto sp = std::__make_shared<_Dir_stack>(std::move(dir));
+ if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr)
+ : sp->top().advance(skip_permission_denied))
{
- if (ecptr)
- ecptr->clear();
- return;
+ _M_dirs.swap(sp);
}
-
- if (!ecptr)
- _GLIBCXX_THROW_OR_ABORT(filesystem_error(
- "recursive directory iterator cannot open directory", p,
- std::error_code(err, std::generic_category())));
-
- ecptr->assign(err, std::generic_category());
}
+ else if (ecptr)
+ *ecptr = ec;
+ else if (ec)
+ _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
+ "recursive directory iterator cannot open directory", p, ec));
}
fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec))
{
- _Dir dir(top.entry.path(), skip_permission_denied, !follow, ec);
+ _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec);
if (ec)
{
_M_dirs.reset();
#define _GLIBCXX_END_NAMESPACE_FILESYSTEM } }
#include "ops-common.h"
+#include <filesystem> // std::filesystem::remove_all
+
namespace fs = std::experimental::filesystem;
namespace posix = std::filesystem::__gnu_posix;
std::uintmax_t
fs::remove_all(const path& p, error_code& ec) noexcept
{
- 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)
- {
- directory_iterator d(p, directory_options{99}, 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))
- ++count;
- return ec ? -1 : count;
+ // Use the C++17 implementation.
+ return std::filesystem::remove_all(p.native(), ec);
}
void