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
19 using detail::ErrorHandler;
20
21 #if defined(_LIBCPP_WIN32API)
22 class __dir_stream {
23 public:
24 __dir_stream() = delete;
25 __dir_stream& operator=(const __dir_stream&) = delete;
26
__dir_stream(__dir_stream && __ds)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
__dir_stream(const path & root,directory_options opts,error_code & ec)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
~__dir_stream()53 ~__dir_stream() noexcept {
54 if (__stream_ == INVALID_HANDLE_VALUE)
55 return;
56 close();
57 }
58
good() const59 bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
60
advance(error_code & ec)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
assign()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
84 private:
close()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
96 public:
97 path __root_;
98 directory_entry __entry_;
99 };
100 #else
101 class __dir_stream {
102 public:
103 __dir_stream() = delete;
104 __dir_stream& operator=(const __dir_stream&) = delete;
105
__dir_stream(__dir_stream && other)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
__dir_stream(const path & root,directory_options opts,error_code & ec)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
~__dir_stream()125 ~__dir_stream() noexcept {
126 if (__stream_)
127 close();
128 }
129
good() const130 bool good() const noexcept { return __stream_ != nullptr; }
131
advance(error_code & ec)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
150 private:
close()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
161 public:
162 path __root_;
163 directory_entry __entry_;
164 };
165 #endif
166
167 // directory_iterator
168
directory_iterator(const path & p,error_code * ec,directory_options opts)169 directory_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
__increment(error_code * ec)184 directory_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
__dereference() const198 directory_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
205 struct recursive_directory_iterator::__shared_imp {
206 stack<__dir_stream> __stack_;
207 directory_options __options_;
208 };
209
recursive_directory_iterator(const path & p,directory_options opt,error_code * ec)210 recursive_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
__pop(error_code * ec)227 void 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
options() const238 directory_options recursive_directory_iterator::options() const {
239 return __imp_->__options_;
240 }
241
depth() const242 int recursive_directory_iterator::depth() const {
243 return __imp_->__stack_.size() - 1;
244 }
245
__dereference() const246 const directory_entry& recursive_directory_iterator::__dereference() const {
247 return __imp_->__stack_.top().__entry_;
248 }
249
250 recursive_directory_iterator&
__increment(error_code * ec)251 recursive_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
__advance(error_code * ec)263 void 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
__try_recursion(error_code * ec)286 bool 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