master
  1//===----------------------------------------------------------------------===//
  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 <__assert>
 10#include <__config>
 11#include <__memory/shared_ptr.h>
 12#include <errno.h>
 13#include <filesystem>
 14#include <stack>
 15#include <utility>
 16
 17#include "error.h"
 18#include "file_descriptor.h"
 19
 20#if defined(_LIBCPP_WIN32API)
 21#  define WIN32_LEAN_AND_MEAN
 22#  define NOMINMAX
 23#  include <windows.h>
 24#else
 25#  include <dirent.h> // for DIR & friends
 26#endif
 27
 28_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
 29
 30using detail::ErrorHandler;
 31
 32#if defined(_LIBCPP_WIN32API)
 33class __dir_stream {
 34public:
 35  __dir_stream()                               = delete;
 36  __dir_stream& operator=(const __dir_stream&) = delete;
 37
 38  __dir_stream(__dir_stream&& __ds) noexcept
 39      : __stream_(__ds.__stream_), __root_(std::move(__ds.__root_)), __entry_(std::move(__ds.__entry_)) {
 40    __ds.__stream_ = INVALID_HANDLE_VALUE;
 41  }
 42
 43  __dir_stream(const path& root, directory_options opts, error_code& ec)
 44      : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
 45    if (root.native().empty()) {
 46      ec = make_error_code(errc::no_such_file_or_directory);
 47      return;
 48    }
 49    __stream_ = ::FindFirstFileW((root / "*").c_str(), &__data_);
 50    if (__stream_ == INVALID_HANDLE_VALUE) {
 51      ec                                  = detail::get_last_error();
 52      const bool ignore_permission_denied = bool(opts & directory_options::skip_permission_denied);
 53      if (ignore_permission_denied && ec == errc::permission_denied)
 54        ec.clear();
 55      return;
 56    }
 57    if (!assign())
 58      advance(ec);
 59  }
 60
 61  ~__dir_stream() noexcept {
 62    if (__stream_ == INVALID_HANDLE_VALUE)
 63      return;
 64    close();
 65  }
 66
 67  bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
 68
 69  bool advance(error_code& ec) {
 70    while (::FindNextFileW(__stream_, &__data_)) {
 71      if (assign())
 72        return true;
 73    }
 74    close();
 75    return false;
 76  }
 77
 78  bool assign() {
 79    if (!wcscmp(__data_.cFileName, L".") || !wcscmp(__data_.cFileName, L".."))
 80      return false;
 81    __entry_.__assign_iter_entry(
 82        __root_ / __data_.cFileName,
 83        directory_entry::__create_iter_cached_result(
 84            detail::get_file_type(__data_),
 85            detail::get_file_size(__data_),
 86            detail::get_file_perm(__data_),
 87            detail::get_write_time(__data_)));
 88    return true;
 89  }
 90
 91private:
 92  error_code close() noexcept {
 93    error_code ec;
 94    if (!::FindClose(__stream_))
 95      ec = detail::get_last_error();
 96    __stream_ = INVALID_HANDLE_VALUE;
 97    return ec;
 98  }
 99
100  HANDLE __stream_{INVALID_HANDLE_VALUE};
101  WIN32_FIND_DATAW __data_;
102
103public:
104  path __root_;
105  directory_entry __entry_;
106};
107#else
108class __dir_stream {
109public:
110  __dir_stream()                               = delete;
111  __dir_stream& operator=(const __dir_stream&) = delete;
112
113  __dir_stream(__dir_stream&& other) noexcept
114      : __stream_(other.__stream_), __root_(std::move(other.__root_)), __entry_(std::move(other.__entry_)) {
115    other.__stream_ = nullptr;
116  }
117
118  __dir_stream(const path& root, directory_options opts, error_code& ec) : __stream_(nullptr), __root_(root) {
119    if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
120      ec                      = detail::capture_errno();
121      const bool allow_eacces = bool(opts & directory_options::skip_permission_denied);
122      if (allow_eacces && ec == errc::permission_denied)
123        ec.clear();
124      return;
125    }
126    advance(ec);
127  }
128
129  ~__dir_stream() noexcept {
130    if (__stream_)
131      close();
132  }
133
134  bool good() const noexcept { return __stream_ != nullptr; }
135
136  bool advance(error_code& ec) {
137    while (true) {
138      auto str_type_pair = detail::posix_readdir(__stream_, ec);
139      auto& str          = str_type_pair.first;
140      if (str == "." || str == "..") {
141        continue;
142      } else if (ec || str.empty()) {
143        close();
144        return false;
145      } else {
146        __entry_.__assign_iter_entry(__root_ / str, directory_entry::__create_iter_result(str_type_pair.second));
147        return true;
148      }
149    }
150  }
151
152private:
153  error_code close() noexcept {
154    error_code m_ec;
155    if (::closedir(__stream_) == -1)
156      m_ec = detail::capture_errno();
157    __stream_ = nullptr;
158    return m_ec;
159  }
160
161  DIR* __stream_{nullptr};
162
163public:
164  path __root_;
165  directory_entry __entry_;
166};
167#endif
168
169// directory_iterator
170
171directory_iterator::directory_iterator(const path& p, error_code* ec, directory_options opts) {
172  ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
173
174  error_code m_ec;
175  __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
176  if (ec)
177    *ec = m_ec;
178  if (!__imp_->good()) {
179    __imp_.reset();
180    if (m_ec)
181      err.report(m_ec);
182  }
183}
184
185directory_iterator& directory_iterator::__increment(error_code* ec) {
186  _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Attempting to increment an invalid iterator");
187  ErrorHandler<void> err("directory_iterator::operator++()", ec);
188
189  error_code m_ec;
190  if (!__imp_->advance(m_ec)) {
191    path root = std::move(__imp_->__root_);
192    __imp_.reset();
193    if (m_ec)
194      err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
195  }
196  return *this;
197}
198
199directory_entry const& directory_iterator::__dereference() const {
200  _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Attempting to dereference an invalid iterator");
201  return __imp_->__entry_;
202}
203
204// recursive_directory_iterator
205
206struct recursive_directory_iterator::__shared_imp {
207  stack<__dir_stream> __stack_;
208  directory_options __options_;
209};
210
211recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options opt, error_code* ec)
212    : __imp_(nullptr), __rec_(true) {
213  ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
214
215  error_code m_ec;
216  __dir_stream new_s(p, opt, m_ec);
217  if (m_ec)
218    err.report(m_ec);
219  if (m_ec || !new_s.good())
220    return;
221
222  __imp_             = make_shared<__shared_imp>();
223  __imp_->__options_ = opt;
224  __imp_->__stack_.push(std::move(new_s));
225}
226
227void recursive_directory_iterator::__pop(error_code* ec) {
228  _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Popping the end iterator");
229  if (ec)
230    ec->clear();
231  __imp_->__stack_.pop();
232  if (__imp_->__stack_.size() == 0)
233    __imp_.reset();
234  else
235    __advance(ec);
236}
237
238directory_options recursive_directory_iterator::options() const { return __imp_->__options_; }
239
240int recursive_directory_iterator::depth() const { return __imp_->__stack_.size() - 1; }
241
242const directory_entry& recursive_directory_iterator::__dereference() const { return __imp_->__stack_.top().__entry_; }
243
244recursive_directory_iterator& recursive_directory_iterator::__increment(error_code* ec) {
245  if (ec)
246    ec->clear();
247  if (recursion_pending()) {
248    if (__try_recursion(ec) || (ec && *ec))
249      return *this;
250  }
251  __rec_ = true;
252  __advance(ec);
253  return *this;
254}
255
256void recursive_directory_iterator::__advance(error_code* ec) {
257  ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
258
259  const directory_iterator end_it;
260  auto& stack = __imp_->__stack_;
261  error_code m_ec;
262  while (stack.size() > 0) {
263    if (stack.top().advance(m_ec))
264      return;
265    if (m_ec)
266      break;
267    stack.pop();
268  }
269
270  if (m_ec) {
271    path root = std::move(stack.top().__root_);
272    __imp_.reset();
273    err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
274  } else {
275    __imp_.reset();
276  }
277}
278
279bool recursive_directory_iterator::__try_recursion(error_code* ec) {
280  ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
281
282  bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
283
284  auto& curr_it = __imp_->__stack_.top();
285
286  bool skip_rec = false;
287  error_code m_ec;
288  if (!rec_sym) {
289    file_status st(curr_it.__entry_.__get_sym_ft(&m_ec));
290    if (m_ec && status_known(st))
291      m_ec.clear();
292    if (m_ec || is_symlink(st) || !is_directory(st))
293      skip_rec = true;
294  } else {
295    file_status st(curr_it.__entry_.__get_ft(&m_ec));
296    if (m_ec && status_known(st))
297      m_ec.clear();
298    if (m_ec || !is_directory(st))
299      skip_rec = true;
300  }
301
302  if (!skip_rec) {
303    __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
304    if (new_it.good()) {
305      __imp_->__stack_.push(std::move(new_it));
306      return true;
307    }
308  }
309  if (m_ec) {
310    const bool allow_eacess = bool(__imp_->__options_ & directory_options::skip_permission_denied);
311    if (m_ec == errc::permission_denied && allow_eacess) {
312      if (ec)
313        ec->clear();
314    } else {
315      path at_ent = std::move(curr_it.__entry_.__p_);
316      __imp_.reset();
317      err.report(m_ec, "attempting recursion into " PATH_CSTR_FMT, at_ent.c_str());
318    }
319  }
320  return false;
321}
322
323_LIBCPP_END_NAMESPACE_FILESYSTEM