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