// Class filesystem::directory_entry etc. -*- C++ -*- // Copyright (C) 2014-2022 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. // Under Section 7 of GPL version 3, you are granted additional // permissions described in the GCC Runtime Library Exception, version // 3.1, as published by the Free Software Foundation. // You should have received a copy of the GNU General Public License and // a copy of the GCC Runtime Library Exception along with this program; // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see // . #ifndef _GLIBCXX_USE_CXX11_ABI # define _GLIBCXX_USE_CXX11_ABI 1 #endif #include #include #include #include #include #include #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem { #define _GLIBCXX_END_NAMESPACE_FILESYSTEM } #include "../filesystem/dir-common.h" namespace fs = std::filesystem; namespace posix = std::filesystem::__gnu_posix; template class std::__shared_ptr; template class std::__shared_ptr; struct fs::_Dir : _Dir_base { _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow, [[maybe_unused]] bool filename_only, error_code& ec) : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec) { #if _GLIBCXX_HAVE_DIRFD && _GLIBCXX_HAVE_OPENAT && _GLIBCXX_HAVE_UNLINKAT if (filename_only) return; // Do not store path p when we aren't going to use it. #endif if (!ec) path = p; } _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { } _Dir(_Dir&&) = default; // Returns false when the end of the directory entries is reached. // Reports errors by setting ec. bool advance(bool skip_permission_denied, error_code& ec) noexcept { if (const auto entp = _Dir_base::advance(skip_permission_denied, ec)) { auto name = path; name /= entp->d_name; file_type type = file_type::none; #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE // Even if the OS supports dirent::d_type the filesystem might not: if (entp->d_type != DT_UNKNOWN) type = get_file_type(*entp); #endif entry = fs::directory_entry{std::move(name), type}; return true; } else if (!ec) { // reached the end entry = {}; } return false; } bool advance(error_code& ec) noexcept { return advance(false, ec); } // Returns false when the end of the directory entries is reached. // Reports errors by throwing. bool advance(bool skip_permission_denied = false) { error_code ec; const bool ok = advance(skip_permission_denied, ec); if (ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "directory iterator cannot advance", ec)); return ok; } bool should_recurse(bool follow_symlink, error_code& ec) const { file_type type = entry._M_type; if (type == file_type::none) { type = entry.symlink_status(ec).type(); if (ec) return false; } if (type == file_type::directory) return true; if (type == file_type::symlink) return follow_symlink && is_directory(entry.status(ec)); return false; } // Return a pathname for the current directory entry, as an _At_path. _Dir_base::_At_path current() const noexcept { const fs::path& p = entry.path(); #if _GLIBCXX_HAVE_DIRFD if (!p.empty()) [[__likely__]] { auto len = std::prev(p.end())->native().size(); return {::dirfd(this->dirp), p.c_str(), p.native().size() - len}; } #endif return 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 { _Dir_base d(current(), 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::move(d), p); } bool do_unlink(bool is_directory, error_code& ec) const noexcept { #if _GLIBCXX_HAVE_UNLINKAT const auto atp = current(); if (::unlinkat(atp.dir(), atp.path_at_dir(), 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; }; namespace { template inline bool is_set(Bitmask obj, Bitmask bits) { 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:: directory_iterator(const path& p, directory_options options, error_code* ecptr) { // 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. const bool nofollow = is_set(options, __directory_iterator_nofollow); error_code ec; _Dir dir(p, skip_permission_denied, nofollow, /*filename only*/false, ec); if (dir.dirp) { auto sp = std::__make_shared(std::move(dir)); if (sp->advance(skip_permission_denied, ec)) _M_dir.swap(sp); } if (ecptr) *ecptr = ec; else if (ec) _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( "directory iterator cannot open directory", p, ec)); } const fs::directory_entry& fs::directory_iterator::operator*() const noexcept { return _M_dir->entry; } fs::directory_iterator& fs::directory_iterator::operator++() { if (!_M_dir) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "cannot advance non-dereferenceable directory iterator", std::make_error_code(errc::invalid_argument))); if (!_M_dir->advance()) _M_dir.reset(); return *this; } fs::directory_iterator& fs::directory_iterator::increment(error_code& ec) { if (!_M_dir) { ec = std::make_error_code(errc::invalid_argument); return *this; } if (!_M_dir->advance(ec)) _M_dir.reset(); return *this; } struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> { _Dir_stack(directory_options opts, _Dir&& dir) : options(opts), pending(true) { 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) { // 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) { 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)) { _M_dirs.swap(sp); if (filename_only) // Need to save original path for error reporting. _M_dirs->orig = p.native(); } } 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; fs::directory_options fs::recursive_directory_iterator::options() const noexcept { return _M_dirs->options; } int fs::recursive_directory_iterator::depth() const noexcept { return int(_M_dirs->size()) - 1; } bool fs::recursive_directory_iterator::recursion_pending() const noexcept { return _M_dirs->pending; } const fs::directory_entry& fs::recursive_directory_iterator::operator*() const noexcept { return _M_dirs->top().entry; } fs::recursive_directory_iterator& fs::recursive_directory_iterator:: operator=(const recursive_directory_iterator& other) noexcept = default; fs::recursive_directory_iterator& fs::recursive_directory_iterator:: operator=(recursive_directory_iterator&& other) noexcept = default; fs::recursive_directory_iterator& fs::recursive_directory_iterator::operator++() { error_code ec; increment(ec); if (ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "cannot increment recursive directory iterator", ec)); return *this; } fs::recursive_directory_iterator& fs::recursive_directory_iterator::increment(error_code& ec) { if (!_M_dirs) { ec = std::make_error_code(errc::invalid_argument); return *this; } const bool follow = is_set(_M_dirs->options, directory_options::follow_directory_symlink); const bool skip_permission_denied = is_set(_M_dirs->options, directory_options::skip_permission_denied); auto& top = _M_dirs->top(); if (std::exchange(_M_dirs->pending, true) && top.should_recurse(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)); } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec) { _M_dirs->pop(); if (_M_dirs->empty()) { _M_dirs.reset(); return *this; } } if (ec) _M_dirs.reset(); return *this; } void fs::recursive_directory_iterator::pop(error_code& ec) { if (!_M_dirs) { ec = std::make_error_code(errc::invalid_argument); return; } const bool skip_permission_denied = is_set(_M_dirs->options, directory_options::skip_permission_denied); do { _M_dirs->pop(); if (_M_dirs->empty()) { _M_dirs.reset(); ec.clear(); return; } } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec); if (ec) _M_dirs.reset(); } void fs::recursive_directory_iterator::pop() { [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr; error_code ec; pop(ec); if (ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable ? "recursive directory iterator cannot pop" : "non-dereferenceable recursive directory iterator cannot pop", ec)); } void fs::recursive_directory_iterator::disable_recursion_pending() noexcept { _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 _GLIBCXX_FILESYSTEM_IS_WINDOWS // _Dir::unlink uses fs::remove which uses std::system_category() for // Windows errror codes, so we can't just check for EPERM and EISDIR. // Use directory_entry::refresh() here to check if we have a directory. // This can be a TOCTTOU race, but we don't have openat or unlinkat to // solve that on Windows, and generally don't support symlinks anyway. if (top.entry._M_type == file_type::none) top.entry.refresh(); #endif 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 #if ! _GLIBCXX_FILESYSTEM_IS_WINDOWS 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 unlink on 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; } } #endif } 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; }