diff options
Diffstat (limited to 'utils/fs/directory.cpp')
-rw-r--r-- | utils/fs/directory.cpp | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/utils/fs/directory.cpp b/utils/fs/directory.cpp new file mode 100644 index 0000000000000..ff7ad5e343575 --- /dev/null +++ b/utils/fs/directory.cpp @@ -0,0 +1,360 @@ +// Copyright 2015 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "utils/fs/directory.hpp" + +extern "C" { +#include <sys/types.h> + +#include <dirent.h> +} + +#include <cerrno> +#include <memory> + +#include "utils/format/macros.hpp" +#include "utils/fs/exceptions.hpp" +#include "utils/fs/path.hpp" +#include "utils/noncopyable.hpp" +#include "utils/sanity.hpp" +#include "utils/text/operations.ipp" + +namespace detail = utils::fs::detail; +namespace fs = utils::fs; +namespace text = utils::text; + + +/// Constructs a new directory entry. +/// +/// \param name_ Name of the directory entry. +fs::directory_entry::directory_entry(const std::string& name_) : name(name_) +{ +} + + +/// Checks if two directory entries are equal. +/// +/// \param other The entry to compare to. +/// +/// \return True if the two entries are equal; false otherwise. +bool +fs::directory_entry::operator==(const directory_entry& other) const +{ + return name == other.name; +} + + +/// Checks if two directory entries are different. +/// +/// \param other The entry to compare to. +/// +/// \return True if the two entries are different; false otherwise. +bool +fs::directory_entry::operator!=(const directory_entry& other) const +{ + return !(*this == other); +} + + +/// Checks if this entry sorts before another entry. +/// +/// \param other The entry to compare to. +/// +/// \return True if this entry sorts before the other entry; false otherwise. +bool +fs::directory_entry::operator<(const directory_entry& other) const +{ + return name < other.name; +} + + +/// Formats a directory entry. +/// +/// \param output Stream into which to inject the formatted entry. +/// \param entry The entry to format. +/// +/// \return A reference to output. +std::ostream& +fs::operator<<(std::ostream& output, const directory_entry& entry) +{ + output << F("directory_entry{name=%s}") % text::quote(entry.name, '\''); + return output; +} + + +/// Internal implementation details for the directory_iterator. +/// +/// In order to support multiple concurrent iterators over the same directory +/// object, this class is the one that performs all directory-level accesses. +/// In particular, even if it may seem surprising, this is the class that +/// handles the DIR object for the directory. +/// +/// Note that iterators implemented by this class do not rely on the container +/// directory class at all. This should not be relied on for object lifecycle +/// purposes. +struct utils::fs::detail::directory_iterator::impl : utils::noncopyable { + /// Path of the directory accessed by this iterator. + const fs::path _path; + + /// Raw pointer to the system representation of the directory. + /// + /// We also use this to determine if the iterator is valid (at the end) or + /// not. A null pointer means an invalid iterator. + ::DIR* _dirp; + + /// Raw representation of the system directory entry. + /// + /// We need to keep this at the class level so that we can use the + /// readdir_r(3) function. + ::dirent _dirent; + + /// Custom representation of the directory entry. + /// + /// This is separate from _dirent because this is the type we return to the + /// user. We must keep this as a pointer so that we can support the common + /// operators (* and ->) over iterators. + std::auto_ptr< directory_entry > _entry; + + /// Constructs an iterator pointing to the "end" of the directory. + impl(void) : _path("invalid-directory-entry"), _dirp(NULL) + { + } + + /// Constructs a new iterator to start scanning a directory. + /// + /// \param path The directory that will be scanned. + /// + /// \throw system_error If there is a problem opening the directory. + explicit impl(const path& path) : _path(path) + { + DIR* dirp = ::opendir(_path.c_str()); + if (dirp == NULL) { + const int original_errno = errno; + throw fs::system_error(F("opendir(%s) failed") % _path, + original_errno); + } + _dirp = dirp; + + // Initialize our first directory entry. Note that this may actually + // close the directory we just opened if the directory happens to be + // empty -- but directories are never empty because they at least have + // '.' and '..' entries. + next(); + } + + /// Destructor. + /// + /// This closes the directory if still open. + ~impl(void) + { + if (_dirp != NULL) + close(); + } + + /// Closes the directory and invalidates the iterator. + void + close(void) + { + PRE(_dirp != NULL); + if (::closedir(_dirp) == -1) { + UNREACHABLE_MSG("Invalid dirp provided to closedir(3)"); + } + _dirp = NULL; + } + + /// Advances the directory entry to the next one. + /// + /// It is possible to use this function on a new directory_entry object to + /// initialize the first entry. + /// + /// \throw system_error If the call to readdir_r fails. + void + next(void) + { + ::dirent* result; + + if (::readdir_r(_dirp, &_dirent, &result) == -1) { + const int original_errno = errno; + throw fs::system_error(F("readdir_r(%s) failed") % _path, + original_errno); + } + if (result == NULL) { + _entry.reset(NULL); + close(); + } else { + _entry.reset(new directory_entry(_dirent.d_name)); + } + } +}; + + +/// Constructs a new directory iterator. +/// +/// \param pimpl The constructed internal implementation structure to use. +detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) : + _pimpl(pimpl) +{ +} + + +/// Destructor. +detail::directory_iterator::~directory_iterator(void) +{ +} + + +/// Creates a new directory iterator for a directory. +/// +/// \return The directory iterator. Note that the result may be invalid. +/// +/// \throw system_error If opening the directory or reading its first entry +/// fails. +detail::directory_iterator +detail::directory_iterator::new_begin(const path& path) +{ + return directory_iterator(std::shared_ptr< impl >(new impl(path))); +} + + +/// Creates a new invalid directory iterator. +/// +/// \return The invalid directory iterator. +detail::directory_iterator +detail::directory_iterator::new_end(void) +{ + return directory_iterator(std::shared_ptr< impl >(new impl())); +} + + +/// Checks if two iterators are equal. +/// +/// We consider two iterators to be equal if both of them are invalid or, +/// otherwise, if they have the exact same internal representation (as given by +/// equality of the pimpl pointers). +/// +/// \param other The object to compare to. +/// +/// \return True if the two iterators are equal; false otherwise. +bool +detail::directory_iterator::operator==(const directory_iterator& other) const +{ + return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) || + _pimpl == other._pimpl; +} + + +/// Checks if two iterators are different. +/// +/// \param other The object to compare to. +/// +/// \return True if the two iterators are different; false otherwise. +bool +detail::directory_iterator::operator!=(const directory_iterator& other) const +{ + return !(*this == other); +} + + +/// Moves the iterator one element forward. +/// +/// \return A reference to the iterator. +/// +/// \throw system_error If advancing the iterator fails. +detail::directory_iterator& +detail::directory_iterator::operator++(void) +{ + _pimpl->next(); + return *this; +} + + +/// Dereferences the iterator to its contents. +/// +/// \return A reference to the directory entry pointed to by the iterator. +const fs::directory_entry& +detail::directory_iterator::operator*(void) const +{ + PRE(_pimpl->_entry.get() != NULL); + return *_pimpl->_entry; +} + + +/// Dereferences the iterator to its contents. +/// +/// \return A pointer to the directory entry pointed to by the iterator. +const fs::directory_entry* +detail::directory_iterator::operator->(void) const +{ + PRE(_pimpl->_entry.get() != NULL); + return _pimpl->_entry.get(); +} + + +/// Internal implementation details for the directory. +struct utils::fs::directory::impl : utils::noncopyable { + /// Path to the directory to scan. + fs::path _path; + + /// Constructs a new directory. + /// + /// \param path_ Path to the directory to scan. + impl(const fs::path& path_) : _path(path_) + { + } +}; + + +/// Constructs a new directory. +/// +/// \param path_ Path to the directory to scan. +fs::directory::directory(const path& path_) : _pimpl(new impl(path_)) +{ +} + + +/// Returns an iterator to start scanning the directory. +/// +/// \return An iterator on the directory. +/// +/// \throw system_error If the directory cannot be opened to obtain its first +/// entry. +fs::directory::const_iterator +fs::directory::begin(void) const +{ + return const_iterator::new_begin(_pimpl->_path); +} + + +/// Returns an invalid iterator to check for the end of an scan. +/// +/// \return An invalid iterator. +fs::directory::const_iterator +fs::directory::end(void) const +{ + return const_iterator::new_end(); +} |