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