xref: /freebsd/contrib/llvm-project/libcxx/src/filesystem/directory_iterator.cpp (revision e6bfd18d21b225af6a0ed67ceeaf1293b7b9eba5)
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 
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 
84 private:
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 
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 
150 private:
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 
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 
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 
198 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 
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 
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 
238 directory_options recursive_directory_iterator::options() const {
239   return __imp_->__options_;
240 }
241 
242 int recursive_directory_iterator::depth() const {
243   return __imp_->__stack_.size() - 1;
244 }
245 
246 const directory_entry& recursive_directory_iterator::__dereference() const {
247   return __imp_->__stack_.top().__entry_;
248 }
249 
250 recursive_directory_iterator&
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 
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 
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