Home | History | Annotate | Download | only in filesystem
      1 //===------------------ directory_iterator.cpp ----------------------------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is dual licensed under the MIT and the University of Illinois Open
      6 // Source Licenses. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 
     10 #include "experimental/filesystem"
     11 #include "__config"
     12 #if defined(_LIBCPP_WIN32API)
     13 #define WIN32_LEAN_AND_MEAN
     14 #include <Windows.h>
     15 #else
     16 #include <dirent.h>
     17 #endif
     18 #include <errno.h>
     19 
     20 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
     21 
     22 namespace { namespace detail {
     23 
     24 #if !defined(_LIBCPP_WIN32API)
     25 inline error_code capture_errno() {
     26     _LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
     27     return error_code{errno, std::generic_category()};
     28 }
     29 #endif
     30 
     31 template <class ...Args>
     32 inline bool set_or_throw(std::error_code& my_ec,
     33                                std::error_code* user_ec,
     34                                const char* msg, Args&&... args)
     35 {
     36     if (user_ec) {
     37         *user_ec = my_ec;
     38         return true;
     39     }
     40     __throw_filesystem_error(msg, std::forward<Args>(args)..., my_ec);
     41     return false;
     42 }
     43 
     44 #if !defined(_LIBCPP_WIN32API)
     45 inline path::string_type posix_readdir(DIR *dir_stream, error_code& ec) {
     46     struct dirent* dir_entry_ptr = nullptr;
     47     errno = 0; // zero errno in order to detect errors
     48     ec.clear();
     49     if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
     50         if (errno)
     51           ec = capture_errno();
     52         return {};
     53     } else {
     54         return dir_entry_ptr->d_name;
     55     }
     56 }
     57 #endif
     58 
     59 }}                                                       // namespace detail
     60 
     61 using detail::set_or_throw;
     62 
     63 #if defined(_LIBCPP_WIN32API)
     64 class __dir_stream {
     65 public:
     66   __dir_stream() = delete;
     67   __dir_stream& operator=(const __dir_stream&) = delete;
     68 
     69   __dir_stream(__dir_stream&& __ds) noexcept
     70       : __stream_(__ds.__stream_), __root_(std::move(__ds.__root_)),
     71         __entry_(std::move(__ds.__entry_)) {
     72     __ds.__stream_ = INVALID_HANDLE_VALUE;
     73   }
     74 
     75   __dir_stream(const path& root, directory_options opts, error_code& ec)
     76       : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
     77     __stream_ = ::FindFirstFile(root.c_str(), &__data_);
     78     if (__stream_ == INVALID_HANDLE_VALUE) {
     79       ec = error_code(::GetLastError(), std::generic_category());
     80       const bool ignore_permission_denied =
     81           bool(opts & directory_options::skip_permission_denied);
     82       if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
     83         ec.clear();
     84       return;
     85     }
     86   }
     87 
     88   ~__dir_stream() noexcept {
     89     if (__stream_ == INVALID_HANDLE_VALUE)
     90       return;
     91     close();
     92   }
     93 
     94   bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
     95 
     96   bool advance(error_code& ec) {
     97     while (::FindNextFile(__stream_, &__data_)) {
     98       if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
     99         continue;
    100       __entry_.assign(__root_ / __data_.cFileName);
    101       return true;
    102     }
    103     ec = error_code(::GetLastError(), std::generic_category());
    104     close();
    105     return false;
    106   }
    107 
    108 private:
    109   std::error_code close() noexcept {
    110     std::error_code ec;
    111     if (!::FindClose(__stream_))
    112       ec = error_code(::GetLastError(), std::generic_category());
    113     __stream_ = INVALID_HANDLE_VALUE;
    114     return ec;
    115   }
    116 
    117   HANDLE __stream_{INVALID_HANDLE_VALUE};
    118   WIN32_FIND_DATA __data_;
    119 
    120 public:
    121   path __root_;
    122   directory_entry __entry_;
    123 };
    124 #else
    125 class __dir_stream {
    126 public:
    127     __dir_stream() = delete;
    128     __dir_stream& operator=(const __dir_stream&) = delete;
    129 
    130     __dir_stream(__dir_stream&& other) noexcept
    131         : __stream_(other.__stream_), __root_(std::move(other.__root_)),
    132           __entry_(std::move(other.__entry_))
    133     {
    134         other.__stream_ = nullptr;
    135     }
    136 
    137 
    138     __dir_stream(const path& root, directory_options opts, error_code& ec)
    139         : __stream_(nullptr),
    140           __root_(root)
    141     {
    142         if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
    143             ec = detail::capture_errno();
    144             const bool allow_eacess =
    145                 bool(opts & directory_options::skip_permission_denied);
    146             if (allow_eacess && ec.value() == EACCES)
    147                 ec.clear();
    148             return;
    149         }
    150         advance(ec);
    151     }
    152 
    153     ~__dir_stream() noexcept
    154       { if (__stream_) close(); }
    155 
    156     bool good() const noexcept { return __stream_ != nullptr; }
    157 
    158     bool advance(error_code &ec) {
    159         while (true) {
    160             auto str = detail::posix_readdir(__stream_,  ec);
    161             if (str == "." || str == "..") {
    162                 continue;
    163             } else if (ec || str.empty()) {
    164                 close();
    165                 return false;
    166             } else {
    167                 __entry_.assign(__root_ / str);
    168                 return true;
    169             }
    170         }
    171     }
    172 private:
    173     std::error_code close() noexcept {
    174         std::error_code m_ec;
    175         if (::closedir(__stream_) == -1)
    176            m_ec = detail::capture_errno();
    177         __stream_ = nullptr;
    178         return m_ec;
    179     }
    180 
    181     DIR * __stream_{nullptr};
    182 public:
    183     path __root_;
    184     directory_entry __entry_;
    185 };
    186 #endif
    187 
    188 // directory_iterator
    189 
    190 directory_iterator::directory_iterator(const path& p, error_code *ec,
    191                                        directory_options opts)
    192 {
    193     std::error_code m_ec;
    194     __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
    195     if (ec) *ec = m_ec;
    196     if (!__imp_->good()) {
    197         __imp_.reset();
    198         if (m_ec)
    199             set_or_throw(m_ec, ec,
    200                          "directory_iterator::directory_iterator(...)", p);
    201     }
    202 }
    203 
    204 directory_iterator& directory_iterator::__increment(error_code *ec)
    205 {
    206     _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
    207     std::error_code m_ec;
    208     if (!__imp_->advance(m_ec)) {
    209         __imp_.reset();
    210         if (m_ec)
    211             set_or_throw(m_ec, ec, "directory_iterator::operator++()");
    212     } else {
    213         if (ec) ec->clear();
    214     }
    215     return *this;
    216 
    217 }
    218 
    219 directory_entry const& directory_iterator::__dereference() const {
    220     _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
    221     return __imp_->__entry_;
    222 }
    223 
    224 // recursive_directory_iterator
    225 
    226 struct recursive_directory_iterator::__shared_imp {
    227   stack<__dir_stream> __stack_;
    228   directory_options   __options_;
    229 };
    230 
    231 recursive_directory_iterator::recursive_directory_iterator(const path& p,
    232     directory_options opt, error_code *ec)
    233     : __imp_(nullptr), __rec_(true)
    234 {
    235     if (ec) ec->clear();
    236     std::error_code m_ec;
    237     __dir_stream new_s(p, opt, m_ec);
    238     if (m_ec) set_or_throw(m_ec, ec, "recursive_directory_iterator", p);
    239     if (m_ec || !new_s.good()) return;
    240 
    241     __imp_ = _VSTD::make_shared<__shared_imp>();
    242     __imp_->__options_ = opt;
    243     __imp_->__stack_.push(_VSTD::move(new_s));
    244 }
    245 
    246 void recursive_directory_iterator::__pop(error_code* ec)
    247 {
    248     _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
    249     if (ec) ec->clear();
    250     __imp_->__stack_.pop();
    251     if (__imp_->__stack_.size() == 0)
    252         __imp_.reset();
    253     else
    254         __advance(ec);
    255 }
    256 
    257 directory_options recursive_directory_iterator::options() const {
    258     return __imp_->__options_;
    259 }
    260 
    261 int recursive_directory_iterator::depth() const {
    262     return __imp_->__stack_.size() - 1;
    263 }
    264 
    265 const directory_entry& recursive_directory_iterator::__dereference() const {
    266     return __imp_->__stack_.top().__entry_;
    267 }
    268 
    269 recursive_directory_iterator&
    270 recursive_directory_iterator::__increment(error_code *ec)
    271 {
    272     if (ec) ec->clear();
    273     if (recursion_pending()) {
    274         if (__try_recursion(ec) || (ec && *ec))
    275             return *this;
    276     }
    277     __rec_ = true;
    278     __advance(ec);
    279     return *this;
    280 }
    281 
    282 void recursive_directory_iterator::__advance(error_code* ec) {
    283     // REQUIRES: ec must be cleared before calling this function.
    284     const directory_iterator end_it;
    285     auto& stack = __imp_->__stack_;
    286     std::error_code m_ec;
    287     while (stack.size() > 0) {
    288         if (stack.top().advance(m_ec))
    289             return;
    290         if (m_ec) break;
    291         stack.pop();
    292     }
    293     __imp_.reset();
    294     if (m_ec)
    295         set_or_throw(m_ec, ec, "recursive_directory_iterator::operator++()");
    296 }
    297 
    298 bool recursive_directory_iterator::__try_recursion(error_code *ec) {
    299 
    300     bool rec_sym =
    301         bool(options() & directory_options::follow_directory_symlink);
    302     auto& curr_it = __imp_->__stack_.top();
    303 
    304     if (is_directory(curr_it.__entry_.status()) &&
    305         (!is_symlink(curr_it.__entry_.symlink_status()) || rec_sym))
    306     {
    307         std::error_code m_ec;
    308         __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
    309         if (new_it.good()) {
    310             __imp_->__stack_.push(_VSTD::move(new_it));
    311             return true;
    312         }
    313         if (m_ec) {
    314             __imp_.reset();
    315             set_or_throw(m_ec, ec,
    316                                "recursive_directory_iterator::operator++()");
    317         }
    318     }
    319     return false;
    320 }
    321 
    322 
    323 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM
    324