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