Path: blob/main/contrib/kyua/utils/fs/directory.cpp
48081 views
// Copyright 2015 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/fs/directory.hpp"2930extern "C" {31#include <sys/types.h>3233#include <dirent.h>34}3536#include <cerrno>37#include <memory>3839#include "utils/format/macros.hpp"40#include "utils/fs/exceptions.hpp"41#include "utils/fs/path.hpp"42#include "utils/noncopyable.hpp"43#include "utils/sanity.hpp"44#include "utils/text/operations.ipp"4546namespace detail = utils::fs::detail;47namespace fs = utils::fs;48namespace text = utils::text;495051/// Constructs a new directory entry.52///53/// \param name_ Name of the directory entry.54fs::directory_entry::directory_entry(const std::string& name_) : name(name_)55{56}575859/// Checks if two directory entries are equal.60///61/// \param other The entry to compare to.62///63/// \return True if the two entries are equal; false otherwise.64bool65fs::directory_entry::operator==(const directory_entry& other) const66{67return name == other.name;68}697071/// Checks if two directory entries are different.72///73/// \param other The entry to compare to.74///75/// \return True if the two entries are different; false otherwise.76bool77fs::directory_entry::operator!=(const directory_entry& other) const78{79return !(*this == other);80}818283/// Checks if this entry sorts before another entry.84///85/// \param other The entry to compare to.86///87/// \return True if this entry sorts before the other entry; false otherwise.88bool89fs::directory_entry::operator<(const directory_entry& other) const90{91return name < other.name;92}939495/// Formats a directory entry.96///97/// \param output Stream into which to inject the formatted entry.98/// \param entry The entry to format.99///100/// \return A reference to output.101std::ostream&102fs::operator<<(std::ostream& output, const directory_entry& entry)103{104output << F("directory_entry{name=%s}") % text::quote(entry.name, '\'');105return output;106}107108109/// Internal implementation details for the directory_iterator.110///111/// In order to support multiple concurrent iterators over the same directory112/// object, this class is the one that performs all directory-level accesses.113/// In particular, even if it may seem surprising, this is the class that114/// handles the DIR object for the directory.115///116/// Note that iterators implemented by this class do not rely on the container117/// directory class at all. This should not be relied on for object lifecycle118/// purposes.119struct utils::fs::detail::directory_iterator::impl : utils::noncopyable {120/// Path of the directory accessed by this iterator.121const fs::path _path;122123/// Raw pointer to the system representation of the directory.124///125/// We also use this to determine if the iterator is valid (at the end) or126/// not. A null pointer means an invalid iterator.127::DIR* _dirp;128129/// Custom representation of the directory entry.130///131/// We must keep this as a pointer so that we can support the common132/// operators (* and ->) over iterators.133std::unique_ptr< directory_entry > _entry;134135/// Constructs an iterator pointing to the "end" of the directory.136impl(void) : _path("invalid-directory-entry"), _dirp(NULL)137{138}139140/// Constructs a new iterator to start scanning a directory.141///142/// \param path The directory that will be scanned.143///144/// \throw system_error If there is a problem opening the directory.145explicit impl(const path& path) : _path(path)146{147DIR* dirp = ::opendir(_path.c_str());148if (dirp == NULL) {149const int original_errno = errno;150throw fs::system_error(F("opendir(%s) failed") % _path,151original_errno);152}153_dirp = dirp;154155// Initialize our first directory entry. Note that this may actually156// close the directory we just opened if the directory happens to be157// empty -- but directories are never empty because they at least have158// '.' and '..' entries.159next();160}161162/// Destructor.163///164/// This closes the directory if still open.165~impl(void)166{167if (_dirp != NULL)168close();169}170171/// Closes the directory and invalidates the iterator.172void173close(void)174{175PRE(_dirp != NULL);176if (::closedir(_dirp) == -1) {177UNREACHABLE_MSG("Invalid dirp provided to closedir(3)");178}179_dirp = NULL;180}181182/// Advances the directory entry to the next one.183///184/// It is possible to use this function on a new directory_entry object to185/// initialize the first entry.186///187/// \throw system_error If the call to readdir fails.188void189next(void)190{191::dirent* result;192193errno = 0;194if ((result = ::readdir(_dirp)) == NULL && errno != 0) {195const int original_errno = errno;196throw fs::system_error(F("readdir(%s) failed") % _path,197original_errno);198}199if (result == NULL) {200_entry.reset();201close();202} else {203_entry.reset(new directory_entry(result->d_name));204}205}206};207208209/// Constructs a new directory iterator.210///211/// \param pimpl The constructed internal implementation structure to use.212detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :213_pimpl(pimpl)214{215}216217218/// Destructor.219detail::directory_iterator::~directory_iterator(void)220{221}222223224/// Creates a new directory iterator for a directory.225///226/// \return The directory iterator. Note that the result may be invalid.227///228/// \throw system_error If opening the directory or reading its first entry229/// fails.230detail::directory_iterator231detail::directory_iterator::new_begin(const path& path)232{233return directory_iterator(std::shared_ptr< impl >(new impl(path)));234}235236237/// Creates a new invalid directory iterator.238///239/// \return The invalid directory iterator.240detail::directory_iterator241detail::directory_iterator::new_end(void)242{243return directory_iterator(std::shared_ptr< impl >(new impl()));244}245246247/// Checks if two iterators are equal.248///249/// We consider two iterators to be equal if both of them are invalid or,250/// otherwise, if they have the exact same internal representation (as given by251/// equality of the pimpl pointers).252///253/// \param other The object to compare to.254///255/// \return True if the two iterators are equal; false otherwise.256bool257detail::directory_iterator::operator==(const directory_iterator& other) const258{259return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) ||260_pimpl == other._pimpl;261}262263264/// Checks if two iterators are different.265///266/// \param other The object to compare to.267///268/// \return True if the two iterators are different; false otherwise.269bool270detail::directory_iterator::operator!=(const directory_iterator& other) const271{272return !(*this == other);273}274275276/// Moves the iterator one element forward.277///278/// \return A reference to the iterator.279///280/// \throw system_error If advancing the iterator fails.281detail::directory_iterator&282detail::directory_iterator::operator++(void)283{284_pimpl->next();285return *this;286}287288289/// Dereferences the iterator to its contents.290///291/// \return A reference to the directory entry pointed to by the iterator.292const fs::directory_entry&293detail::directory_iterator::operator*(void) const294{295PRE(_pimpl->_entry.get() != NULL);296return *_pimpl->_entry;297}298299300/// Dereferences the iterator to its contents.301///302/// \return A pointer to the directory entry pointed to by the iterator.303const fs::directory_entry*304detail::directory_iterator::operator->(void) const305{306PRE(_pimpl->_entry.get() != NULL);307return _pimpl->_entry.get();308}309310311/// Internal implementation details for the directory.312struct utils::fs::directory::impl : utils::noncopyable {313/// Path to the directory to scan.314fs::path _path;315316/// Constructs a new directory.317///318/// \param path_ Path to the directory to scan.319impl(const fs::path& path_) : _path(path_)320{321}322};323324325/// Constructs a new directory.326///327/// \param path_ Path to the directory to scan.328fs::directory::directory(const path& path_) : _pimpl(new impl(path_))329{330}331332333/// Returns an iterator to start scanning the directory.334///335/// \return An iterator on the directory.336///337/// \throw system_error If the directory cannot be opened to obtain its first338/// entry.339fs::directory::const_iterator340fs::directory::begin(void) const341{342return const_iterator::new_begin(_pimpl->_path);343}344345346/// Returns an invalid iterator to check for the end of an scan.347///348/// \return An invalid iterator.349fs::directory::const_iterator350fs::directory::end(void) const351{352return const_iterator::new_end();353}354355356