Home | History | Annotate | Line # | Download | only in filesystem
      1 //===------------------ directory_iterator.cpp ----------------------------===//
      2 //
      3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4 // See https://llvm.org/LICENSE.txt for license information.
      5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 
      9 #include "filesystem"
     10 #include "__config"
     11 #if defined(_LIBCPP_WIN32API)
     12 #define WIN32_LEAN_AND_MEAN
     13 #define NOMINMAX
     14 #include <windows.h>
     15 #else
     16 #include <dirent.h>
     17 #endif
     18 #include <errno.h>
     19 
     20 #include "filesystem_common.h"
     21 
     22 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
     23 
     24 namespace detail {
     25 namespace {
     26 
     27 #if !defined(_LIBCPP_WIN32API)
     28 
     29 #if defined(DT_BLK)
     30 template <class DirEntT, class = decltype(DirEntT::d_type)>
     31 static file_type get_file_type(DirEntT* ent, int) {
     32   switch (ent->d_type) {
     33   case DT_BLK:
     34     return file_type::block;
     35   case DT_CHR:
     36     return file_type::character;
     37   case DT_DIR:
     38     return file_type::directory;
     39   case DT_FIFO:
     40     return file_type::fifo;
     41   case DT_LNK:
     42     return file_type::symlink;
     43   case DT_REG:
     44     return file_type::regular;
     45   case DT_SOCK:
     46     return file_type::socket;
     47   // Unlike in lstat, hitting "unknown" here simply means that the underlying
     48   // filesystem doesn't support d_type. Report is as 'none' so we correctly
     49   // set the cache to empty.
     50   case DT_UNKNOWN:
     51     break;
     52   }
     53   return file_type::none;
     54 }
     55 #endif // defined(DT_BLK)
     56 
     57 template <class DirEntT>
     58 static file_type get_file_type(DirEntT* ent, long) {
     59   return file_type::none;
     60 }
     61 
     62 static pair<string_view, file_type> posix_readdir(DIR* dir_stream,
     63                                                   error_code& ec) {
     64   struct dirent* dir_entry_ptr = nullptr;
     65   errno = 0; // zero errno in order to detect errors
     66   ec.clear();
     67   if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
     68     if (errno)
     69       ec = capture_errno();
     70     return {};
     71   } else {
     72     return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
     73   }
     74 }
     75 #else
     76 // defined(_LIBCPP_WIN32API)
     77 
     78 static file_type get_file_type(const WIN32_FIND_DATAW& data) {
     79   if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
     80       data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
     81     return file_type::symlink;
     82   if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
     83     return file_type::directory;
     84   return file_type::regular;
     85 }
     86 static uintmax_t get_file_size(const WIN32_FIND_DATAW& data) {
     87   return (static_cast<uint64_t>(data.nFileSizeHigh) << 32) + data.nFileSizeLow;
     88 }
     89 static file_time_type get_write_time(const WIN32_FIND_DATAW& data) {
     90   ULARGE_INTEGER tmp;
     91   const FILETIME& time = data.ftLastWriteTime;
     92   tmp.u.LowPart = time.dwLowDateTime;
     93   tmp.u.HighPart = time.dwHighDateTime;
     94   return file_time_type(file_time_type::duration(tmp.QuadPart));
     95 }
     96 
     97 #endif
     98 
     99 } // namespace
    100 } // namespace detail
    101 
    102 using detail::ErrorHandler;
    103 
    104 #if defined(_LIBCPP_WIN32API)
    105 class __dir_stream {
    106 public:
    107   __dir_stream() = delete;
    108   __dir_stream& operator=(const __dir_stream&) = delete;
    109 
    110   __dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_),
    111                                                __root_(move(__ds.__root_)),
    112                                                __entry_(move(__ds.__entry_)) {
    113     __ds.__stream_ = INVALID_HANDLE_VALUE;
    114   }
    115 
    116   __dir_stream(const path& root, directory_options opts, error_code& ec)
    117       : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
    118     if (root.native().empty()) {
    119       ec = make_error_code(errc::no_such_file_or_directory);
    120       return;
    121     }
    122     __stream_ = ::FindFirstFileW((root / "*").c_str(), &__data_);
    123     if (__stream_ == INVALID_HANDLE_VALUE) {
    124       ec = detail::make_windows_error(GetLastError());
    125       const bool ignore_permission_denied =
    126           bool(opts & directory_options::skip_permission_denied);
    127       if (ignore_permission_denied &&
    128           ec.value() == static_cast<int>(errc::permission_denied))
    129         ec.clear();
    130       return;
    131     }
    132     if (!assign())
    133       advance(ec);
    134   }
    135 
    136   ~__dir_stream() noexcept {
    137     if (__stream_ == INVALID_HANDLE_VALUE)
    138       return;
    139     close();
    140   }
    141 
    142   bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
    143 
    144   bool advance(error_code& ec) {
    145     while (::FindNextFileW(__stream_, &__data_)) {
    146       if (assign())
    147         return true;
    148     }
    149     close();
    150     return false;
    151   }
    152 
    153   bool assign() {
    154     if (!wcscmp(__data_.cFileName, L".") || !wcscmp(__data_.cFileName, L".."))
    155       return false;
    156     // FIXME: Cache more of this
    157     //directory_entry::__cached_data cdata;
    158     //cdata.__type_ = get_file_type(__data_);
    159     //cdata.__size_ = get_file_size(__data_);
    160     //cdata.__write_time_ = get_write_time(__data_);
    161     __entry_.__assign_iter_entry(
    162         __root_ / __data_.cFileName,
    163         directory_entry::__create_iter_result(detail::get_file_type(__data_)));
    164     return true;
    165   }
    166 
    167 private:
    168   error_code close() noexcept {
    169     error_code ec;
    170     if (!::FindClose(__stream_))
    171       ec = detail::make_windows_error(GetLastError());
    172     __stream_ = INVALID_HANDLE_VALUE;
    173     return ec;
    174   }
    175 
    176   HANDLE __stream_{INVALID_HANDLE_VALUE};
    177   WIN32_FIND_DATAW __data_;
    178 
    179 public:
    180   path __root_;
    181   directory_entry __entry_;
    182 };
    183 #else
    184 class __dir_stream {
    185 public:
    186   __dir_stream() = delete;
    187   __dir_stream& operator=(const __dir_stream&) = delete;
    188 
    189   __dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_),
    190                                                 __root_(move(other.__root_)),
    191                                                 __entry_(move(other.__entry_)) {
    192     other.__stream_ = nullptr;
    193   }
    194 
    195   __dir_stream(const path& root, directory_options opts, error_code& ec)
    196       : __stream_(nullptr), __root_(root) {
    197     if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
    198       ec = detail::capture_errno();
    199       const bool allow_eacess =
    200           bool(opts & directory_options::skip_permission_denied);
    201       if (allow_eacess && ec.value() == EACCES)
    202         ec.clear();
    203       return;
    204     }
    205     advance(ec);
    206   }
    207 
    208   ~__dir_stream() noexcept {
    209     if (__stream_)
    210       close();
    211   }
    212 
    213   bool good() const noexcept { return __stream_ != nullptr; }
    214 
    215   bool advance(error_code& ec) {
    216     while (true) {
    217       auto str_type_pair = detail::posix_readdir(__stream_, ec);
    218       auto& str = str_type_pair.first;
    219       if (str == "." || str == "..") {
    220         continue;
    221       } else if (ec || str.empty()) {
    222         close();
    223         return false;
    224       } else {
    225         __entry_.__assign_iter_entry(
    226             __root_ / str,
    227             directory_entry::__create_iter_result(str_type_pair.second));
    228         return true;
    229       }
    230     }
    231   }
    232 
    233 private:
    234   error_code close() noexcept {
    235     error_code m_ec;
    236     if (::closedir(__stream_) == -1)
    237       m_ec = detail::capture_errno();
    238     __stream_ = nullptr;
    239     return m_ec;
    240   }
    241 
    242   DIR* __stream_{nullptr};
    243 
    244 public:
    245   path __root_;
    246   directory_entry __entry_;
    247 };
    248 #endif
    249 
    250 // directory_iterator
    251 
    252 directory_iterator::directory_iterator(const path& p, error_code* ec,
    253                                        directory_options opts) {
    254   ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
    255 
    256   error_code m_ec;
    257   __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
    258   if (ec)
    259     *ec = m_ec;
    260   if (!__imp_->good()) {
    261     __imp_.reset();
    262     if (m_ec)
    263       err.report(m_ec);
    264   }
    265 }
    266 
    267 directory_iterator& directory_iterator::__increment(error_code* ec) {
    268   _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
    269   ErrorHandler<void> err("directory_iterator::operator++()", ec);
    270 
    271   error_code m_ec;
    272   if (!__imp_->advance(m_ec)) {
    273     path root = move(__imp_->__root_);
    274     __imp_.reset();
    275     if (m_ec)
    276       err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
    277   }
    278   return *this;
    279 }
    280 
    281 directory_entry const& directory_iterator::__dereference() const {
    282   _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
    283   return __imp_->__entry_;
    284 }
    285 
    286 // recursive_directory_iterator
    287 
    288 struct recursive_directory_iterator::__shared_imp {
    289   stack<__dir_stream> __stack_;
    290   directory_options __options_;
    291 };
    292 
    293 recursive_directory_iterator::recursive_directory_iterator(
    294     const path& p, directory_options opt, error_code* ec)
    295     : __imp_(nullptr), __rec_(true) {
    296   ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
    297 
    298   error_code m_ec;
    299   __dir_stream new_s(p, opt, m_ec);
    300   if (m_ec)
    301     err.report(m_ec);
    302   if (m_ec || !new_s.good())
    303     return;
    304 
    305   __imp_ = make_shared<__shared_imp>();
    306   __imp_->__options_ = opt;
    307   __imp_->__stack_.push(move(new_s));
    308 }
    309 
    310 void recursive_directory_iterator::__pop(error_code* ec) {
    311   _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
    312   if (ec)
    313     ec->clear();
    314   __imp_->__stack_.pop();
    315   if (__imp_->__stack_.size() == 0)
    316     __imp_.reset();
    317   else
    318     __advance(ec);
    319 }
    320 
    321 directory_options recursive_directory_iterator::options() const {
    322   return __imp_->__options_;
    323 }
    324 
    325 int recursive_directory_iterator::depth() const {
    326   return __imp_->__stack_.size() - 1;
    327 }
    328 
    329 const directory_entry& recursive_directory_iterator::__dereference() const {
    330   return __imp_->__stack_.top().__entry_;
    331 }
    332 
    333 recursive_directory_iterator&
    334 recursive_directory_iterator::__increment(error_code* ec) {
    335   if (ec)
    336     ec->clear();
    337   if (recursion_pending()) {
    338     if (__try_recursion(ec) || (ec && *ec))
    339       return *this;
    340   }
    341   __rec_ = true;
    342   __advance(ec);
    343   return *this;
    344 }
    345 
    346 void recursive_directory_iterator::__advance(error_code* ec) {
    347   ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
    348 
    349   const directory_iterator end_it;
    350   auto& stack = __imp_->__stack_;
    351   error_code m_ec;
    352   while (stack.size() > 0) {
    353     if (stack.top().advance(m_ec))
    354       return;
    355     if (m_ec)
    356       break;
    357     stack.pop();
    358   }
    359 
    360   if (m_ec) {
    361     path root = move(stack.top().__root_);
    362     __imp_.reset();
    363     err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
    364   } else {
    365     __imp_.reset();
    366   }
    367 }
    368 
    369 bool recursive_directory_iterator::__try_recursion(error_code* ec) {
    370   ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
    371 
    372   bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
    373 
    374   auto& curr_it = __imp_->__stack_.top();
    375 
    376   bool skip_rec = false;
    377   error_code m_ec;
    378   if (!rec_sym) {
    379     file_status st(curr_it.__entry_.__get_sym_ft(&m_ec));
    380     if (m_ec && status_known(st))
    381       m_ec.clear();
    382     if (m_ec || is_symlink(st) || !is_directory(st))
    383       skip_rec = true;
    384   } else {
    385     file_status st(curr_it.__entry_.__get_ft(&m_ec));
    386     if (m_ec && status_known(st))
    387       m_ec.clear();
    388     if (m_ec || !is_directory(st))
    389       skip_rec = true;
    390   }
    391 
    392   if (!skip_rec) {
    393     __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
    394     if (new_it.good()) {
    395       __imp_->__stack_.push(move(new_it));
    396       return true;
    397     }
    398   }
    399   if (m_ec) {
    400     const bool allow_eacess =
    401         bool(__imp_->__options_ & directory_options::skip_permission_denied);
    402     if (m_ec.value() == EACCES && allow_eacess) {
    403       if (ec)
    404         ec->clear();
    405     } else {
    406       path at_ent = move(curr_it.__entry_.__p_);
    407       __imp_.reset();
    408       err.report(m_ec, "attempting recursion into " PATH_CSTR_FMT,
    409                  at_ent.c_str());
    410     }
    411   }
    412   return false;
    413 }
    414 
    415 _LIBCPP_END_NAMESPACE_FILESYSTEM
    416