xref: /freebsd/contrib/llvm-project/libcxx/src/filesystem/directory_iterator.cpp (revision cb14a3fe5122c879eae1fb480ed7ce82a699ddb6)
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
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 
90 private:
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 
102 public:
103   path __root_;
104   directory_entry __entry_;
105 };
106 #else
107 class __dir_stream {
108 public:
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 
151 private:
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 
162 public:
163   path __root_;
164   directory_entry __entry_;
165 };
166 #endif
167 
168 // directory_iterator
169 
170 directory_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 
184 directory_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 
198 directory_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 
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(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 
226 void 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 
237 directory_options recursive_directory_iterator::options() const { return __imp_->__options_; }
238 
239 int recursive_directory_iterator::depth() const { return __imp_->__stack_.size() - 1; }
240 
241 const directory_entry& recursive_directory_iterator::__dereference() const { return __imp_->__stack_.top().__entry_; }
242 
243 recursive_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 
255 void 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 
278 bool 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