// 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();
}