// Class filesystem::directory_entry etc. -*- C++ -*- // Copyright (C) 2014-2017 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 #ifdef _GLIBCXX_HAVE_DIRENT_H # ifdef _GLIBCXX_HAVE_SYS_TYPES_H # include # endif # include #else # error "the header is needed to build the Filesystem TS" #endif #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS # undef opendir # define opendir _wopendir #endif namespace fs = std::experimental::filesystem; struct fs::_Dir { _Dir() : dirp(nullptr) { } _Dir(DIR* dirp, const fs::path& path) : dirp(dirp), path(path) { } _Dir(_Dir&& d) : dirp(std::exchange(d.dirp, nullptr)), path(std::move(d.path)), entry(std::move(d.entry)), type(d.type) { } _Dir& operator=(_Dir&&) = delete; ~_Dir() { if (dirp) ::closedir(dirp); } bool advance(std::error_code*, directory_options = directory_options::none); DIR* dirp; fs::path path; directory_entry entry; file_type type = file_type::none; }; namespace { template inline bool is_set(Bitmask obj, Bitmask bits) { return (obj & bits) != Bitmask::none; } // Returns {dirp, p} on success, {} on error (whether ignored or not). inline fs::_Dir open_dir(const fs::path& p, fs::directory_options options, std::error_code* ec) { if (ec) ec->clear(); if (DIR* dirp = ::opendir(p.c_str())) return {dirp, p}; const int err = errno; if (err == EACCES && is_set(options, fs::directory_options::skip_permission_denied)) return {}; if (!ec) _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( "directory iterator cannot open directory", p, std::error_code(err, std::generic_category()))); ec->assign(err, std::generic_category()); return {}; } inline fs::file_type get_file_type(const ::dirent& d __attribute__((__unused__))) { #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE switch (d.d_type) { case DT_BLK: return fs::file_type::block; case DT_CHR: return fs::file_type::character; case DT_DIR: return fs::file_type::directory; case DT_FIFO: return fs::file_type::fifo; case DT_LNK: return fs::file_type::symlink; case DT_REG: return fs::file_type::regular; case DT_SOCK: return fs::file_type::socket; case DT_UNKNOWN: return fs::file_type::unknown; default: return fs::file_type::none; } #else return fs::file_type::none; #endif } } // Returns false when the end of the directory entries is reached. // Reports errors by setting ec or throwing. bool fs::_Dir::advance(error_code* ec, directory_options options) { if (ec) ec->clear(); int err = std::exchange(errno, 0); const auto entp = readdir(dirp); std::swap(errno, err); if (entp) { // skip past dot and dot-dot if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, "..")) return advance(ec, options); entry = fs::directory_entry{path / entp->d_name}; type = get_file_type(*entp); return true; } else if (err) { if (err == EACCES && is_set(options, directory_options::skip_permission_denied)) return false; if (!ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "directory iterator cannot advance", std::error_code(err, std::generic_category()))); ec->assign(err, std::generic_category()); return false; } else { // reached the end entry = {}; type = fs::file_type::none; return false; } } fs::directory_iterator:: directory_iterator(const path& p, directory_options options, error_code* ec) { _Dir dir = open_dir(p, options, ec); if (dir.dirp) { auto sp = std::make_shared(std::move(dir)); if (sp->advance(ec, options)) _M_dir.swap(sp); } } const fs::directory_entry& fs::directory_iterator::operator*() const { if (!_M_dir) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "non-dereferenceable directory iterator", std::make_error_code(errc::invalid_argument))); 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(nullptr)) _M_dir.reset(); return *this; } fs::directory_iterator& fs::directory_iterator::increment(error_code& ec) noexcept { if (!_M_dir) { ec = std::make_error_code(errc::invalid_argument); return *this; } if (!_M_dir->advance(&ec)) _M_dir.reset(); return *this; } using Dir_iter_pair = std::pair; struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> { void clear() { c.clear(); } }; fs::recursive_directory_iterator:: recursive_directory_iterator(const path& p, directory_options options, error_code* ec) : _M_options(options), _M_pending(true) { if (DIR* dirp = ::opendir(p.c_str())) { auto sp = std::make_shared<_Dir_stack>(); sp->push(_Dir{ dirp, p }); if (sp->top().advance(ec)) _M_dirs.swap(sp); } else { const int err = errno; if (err == EACCES && is_set(options, fs::directory_options::skip_permission_denied)) { if (ec) ec->clear(); return; } if (!ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "recursive directory iterator cannot open directory", p, std::error_code(err, std::generic_category()))); ec->assign(err, std::generic_category()); } } fs::recursive_directory_iterator::~recursive_directory_iterator() = default; int fs::recursive_directory_iterator::depth() const { return int(_M_dirs->size()) - 1; } const fs::directory_entry& fs::recursive_directory_iterator::operator*() const { 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.value()) _GLIBCXX_THROW_OR_ABORT(filesystem_error( "cannot increment recursive directory iterator", ec)); return *this; } namespace { bool recurse(const fs::_Dir& d, fs::directory_options options, std::error_code& ec) { bool follow_symlink = is_set(options, fs::directory_options::follow_directory_symlink); #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE if (d.type == fs::file_type::directory) return true; if (d.type == fs::file_type::symlink && follow_symlink) return d.entry.status().type() == fs::file_type::directory; if (d.type != fs::file_type::none && d.type != fs::file_type::unknown) return false; #endif const fs::path& path = d.entry.path(); auto type = fs::symlink_status(path, ec).type(); if (ec.value()) return false; if (type == fs::file_type::symlink) { if (!follow_symlink) return false; type = fs::status(path, ec).type(); } return type == fs::file_type::directory; } } fs::recursive_directory_iterator& fs::recursive_directory_iterator::increment(error_code& ec) noexcept { if (!_M_dirs) { ec = std::make_error_code(errc::invalid_argument); return *this; } auto& top = _M_dirs->top(); if (std::exchange(_M_pending, true) && recurse(top, _M_options, ec)) { _Dir dir = open_dir(top.entry.path(), _M_options, &ec); if (ec) { _M_dirs.reset(); return *this; } if (dir.dirp) _M_dirs->push(std::move(dir)); } while (!_M_dirs->top().advance(&ec, _M_options) && !ec) { _M_dirs->pop(); if (_M_dirs->empty()) { _M_dirs.reset(); return *this; } } return *this; } void fs::recursive_directory_iterator::pop(error_code& ec) { if (!_M_dirs) { ec = std::make_error_code(errc::invalid_argument); return; } do { _M_dirs->pop(); if (_M_dirs->empty()) { _M_dirs.reset(); ec.clear(); return; } } while (!_M_dirs->top().advance(&ec, _M_options)); } void fs::recursive_directory_iterator::pop() { error_code ec; pop(ec); if (ec) _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs ? "recursive directory iterator cannot pop" : "non-dereferenceable recursive directory iterator cannot pop", ec)); }