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