summaryrefslogtreecommitdiff
path: root/utils/fs/directory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/fs/directory.cpp')
-rw-r--r--utils/fs/directory.cpp360
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();
+}