xref: /freebsd/contrib/llvm-project/libcxx/src/filesystem/directory_iterator.cpp (revision 2f513db72b034fd5ef7f080b11be5c711c15186a)
1 //===------------------ directory_iterator.cpp ----------------------------===//
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 "filesystem"
10 #include "__config"
11 #if defined(_LIBCPP_WIN32API)
12 #define WIN32_LEAN_AND_MEAN
13 #include <Windows.h>
14 #else
15 #include <dirent.h>
16 #endif
17 #include <errno.h>
18 
19 #include "filesystem_common.h"
20 
21 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
22 
23 namespace detail {
24 namespace {
25 
26 #if !defined(_LIBCPP_WIN32API)
27 
28 #if defined(DT_BLK)
29 template <class DirEntT, class = decltype(DirEntT::d_type)>
30 static file_type get_file_type(DirEntT* ent, int) {
31   switch (ent->d_type) {
32   case DT_BLK:
33     return file_type::block;
34   case DT_CHR:
35     return file_type::character;
36   case DT_DIR:
37     return file_type::directory;
38   case DT_FIFO:
39     return file_type::fifo;
40   case DT_LNK:
41     return file_type::symlink;
42   case DT_REG:
43     return file_type::regular;
44   case DT_SOCK:
45     return file_type::socket;
46   // Unlike in lstat, hitting "unknown" here simply means that the underlying
47   // filesystem doesn't support d_type. Report is as 'none' so we correctly
48   // set the cache to empty.
49   case DT_UNKNOWN:
50     break;
51   }
52   return file_type::none;
53 }
54 #endif // defined(DT_BLK)
55 
56 template <class DirEntT>
57 static file_type get_file_type(DirEntT* ent, long) {
58   return file_type::none;
59 }
60 
61 static pair<string_view, file_type> posix_readdir(DIR* dir_stream,
62                                                   error_code& ec) {
63   struct dirent* dir_entry_ptr = nullptr;
64   errno = 0; // zero errno in order to detect errors
65   ec.clear();
66   if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
67     if (errno)
68       ec = capture_errno();
69     return {};
70   } else {
71     return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
72   }
73 }
74 #else
75 
76 static file_type get_file_type(const WIN32_FIND_DATA& data) {
77   //auto attrs = data.dwFileAttributes;
78   // FIXME(EricWF)
79   return file_type::unknown;
80 }
81 static uintmax_t get_file_size(const WIN32_FIND_DATA& data) {
82   return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow;
83 }
84 static file_time_type get_write_time(const WIN32_FIND_DATA& data) {
85   ULARGE_INTEGER tmp;
86   FILETIME& time = data.ftLastWriteTime;
87   tmp.u.LowPart = time.dwLowDateTime;
88   tmp.u.HighPart = time.dwHighDateTime;
89   return file_time_type(file_time_type::duration(time.QuadPart));
90 }
91 
92 #endif
93 
94 } // namespace
95 } // namespace detail
96 
97 using detail::ErrorHandler;
98 
99 #if defined(_LIBCPP_WIN32API)
100 class __dir_stream {
101 public:
102   __dir_stream() = delete;
103   __dir_stream& operator=(const __dir_stream&) = delete;
104 
105   __dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_),
106                                                __root_(move(__ds.__root_)),
107                                                __entry_(move(__ds.__entry_)) {
108     __ds.__stream_ = INVALID_HANDLE_VALUE;
109   }
110 
111   __dir_stream(const path& root, directory_options opts, error_code& ec)
112       : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
113     __stream_ = ::FindFirstFileEx(root.c_str(), &__data_);
114     if (__stream_ == INVALID_HANDLE_VALUE) {
115       ec = error_code(::GetLastError(), generic_category());
116       const bool ignore_permission_denied =
117           bool(opts & directory_options::skip_permission_denied);
118       if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
119         ec.clear();
120       return;
121     }
122   }
123 
124   ~__dir_stream() noexcept {
125     if (__stream_ == INVALID_HANDLE_VALUE)
126       return;
127     close();
128   }
129 
130   bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
131 
132   bool advance(error_code& ec) {
133     while (::FindNextFile(__stream_, &__data_)) {
134       if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
135         continue;
136       // FIXME: Cache more of this
137       //directory_entry::__cached_data cdata;
138       //cdata.__type_ = get_file_type(__data_);
139       //cdata.__size_ = get_file_size(__data_);
140       //cdata.__write_time_ = get_write_time(__data_);
141       __entry_.__assign_iter_entry(
142           __root_ / __data_.cFileName,
143           directory_entry::__create_iter_result(get_file_type(__data)));
144       return true;
145     }
146     ec = error_code(::GetLastError(), generic_category());
147     close();
148     return false;
149   }
150 
151 private:
152   error_code close() noexcept {
153     error_code ec;
154     if (!::FindClose(__stream_))
155       ec = error_code(::GetLastError(), generic_category());
156     __stream_ = INVALID_HANDLE_VALUE;
157     return ec;
158   }
159 
160   HANDLE __stream_{INVALID_HANDLE_VALUE};
161   WIN32_FIND_DATA __data_;
162 
163 public:
164   path __root_;
165   directory_entry __entry_;
166 };
167 #else
168 class __dir_stream {
169 public:
170   __dir_stream() = delete;
171   __dir_stream& operator=(const __dir_stream&) = delete;
172 
173   __dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_),
174                                                 __root_(move(other.__root_)),
175                                                 __entry_(move(other.__entry_)) {
176     other.__stream_ = nullptr;
177   }
178 
179   __dir_stream(const path& root, directory_options opts, error_code& ec)
180       : __stream_(nullptr), __root_(root) {
181     if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
182       ec = detail::capture_errno();
183       const bool allow_eacess =
184           bool(opts & directory_options::skip_permission_denied);
185       if (allow_eacess && ec.value() == EACCES)
186         ec.clear();
187       return;
188     }
189     advance(ec);
190   }
191 
192   ~__dir_stream() noexcept {
193     if (__stream_)
194       close();
195   }
196 
197   bool good() const noexcept { return __stream_ != nullptr; }
198 
199   bool advance(error_code& ec) {
200     while (true) {
201       auto str_type_pair = detail::posix_readdir(__stream_, ec);
202       auto& str = str_type_pair.first;
203       if (str == "." || str == "..") {
204         continue;
205       } else if (ec || str.empty()) {
206         close();
207         return false;
208       } else {
209         __entry_.__assign_iter_entry(
210             __root_ / str,
211             directory_entry::__create_iter_result(str_type_pair.second));
212         return true;
213       }
214     }
215   }
216 
217 private:
218   error_code close() noexcept {
219     error_code m_ec;
220     if (::closedir(__stream_) == -1)
221       m_ec = detail::capture_errno();
222     __stream_ = nullptr;
223     return m_ec;
224   }
225 
226   DIR* __stream_{nullptr};
227 
228 public:
229   path __root_;
230   directory_entry __entry_;
231 };
232 #endif
233 
234 // directory_iterator
235 
236 directory_iterator::directory_iterator(const path& p, error_code* ec,
237                                        directory_options opts) {
238   ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
239 
240   error_code m_ec;
241   __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
242   if (ec)
243     *ec = m_ec;
244   if (!__imp_->good()) {
245     __imp_.reset();
246     if (m_ec)
247       err.report(m_ec);
248   }
249 }
250 
251 directory_iterator& directory_iterator::__increment(error_code* ec) {
252   _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
253   ErrorHandler<void> err("directory_iterator::operator++()", ec);
254 
255   error_code m_ec;
256   if (!__imp_->advance(m_ec)) {
257     path root = move(__imp_->__root_);
258     __imp_.reset();
259     if (m_ec)
260       err.report(m_ec, "at root \"%s\"", root);
261   }
262   return *this;
263 }
264 
265 directory_entry const& directory_iterator::__dereference() const {
266   _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
267   return __imp_->__entry_;
268 }
269 
270 // recursive_directory_iterator
271 
272 struct recursive_directory_iterator::__shared_imp {
273   stack<__dir_stream> __stack_;
274   directory_options __options_;
275 };
276 
277 recursive_directory_iterator::recursive_directory_iterator(
278     const path& p, directory_options opt, error_code* ec)
279     : __imp_(nullptr), __rec_(true) {
280   ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
281 
282   error_code m_ec;
283   __dir_stream new_s(p, opt, m_ec);
284   if (m_ec)
285     err.report(m_ec);
286   if (m_ec || !new_s.good())
287     return;
288 
289   __imp_ = make_shared<__shared_imp>();
290   __imp_->__options_ = opt;
291   __imp_->__stack_.push(move(new_s));
292 }
293 
294 void recursive_directory_iterator::__pop(error_code* ec) {
295   _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
296   if (ec)
297     ec->clear();
298   __imp_->__stack_.pop();
299   if (__imp_->__stack_.size() == 0)
300     __imp_.reset();
301   else
302     __advance(ec);
303 }
304 
305 directory_options recursive_directory_iterator::options() const {
306   return __imp_->__options_;
307 }
308 
309 int recursive_directory_iterator::depth() const {
310   return __imp_->__stack_.size() - 1;
311 }
312 
313 const directory_entry& recursive_directory_iterator::__dereference() const {
314   return __imp_->__stack_.top().__entry_;
315 }
316 
317 recursive_directory_iterator&
318 recursive_directory_iterator::__increment(error_code* ec) {
319   if (ec)
320     ec->clear();
321   if (recursion_pending()) {
322     if (__try_recursion(ec) || (ec && *ec))
323       return *this;
324   }
325   __rec_ = true;
326   __advance(ec);
327   return *this;
328 }
329 
330 void recursive_directory_iterator::__advance(error_code* ec) {
331   ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
332 
333   const directory_iterator end_it;
334   auto& stack = __imp_->__stack_;
335   error_code m_ec;
336   while (stack.size() > 0) {
337     if (stack.top().advance(m_ec))
338       return;
339     if (m_ec)
340       break;
341     stack.pop();
342   }
343 
344   if (m_ec) {
345     path root = move(stack.top().__root_);
346     __imp_.reset();
347     err.report(m_ec, "at root \"%s\"", root);
348   } else {
349     __imp_.reset();
350   }
351 }
352 
353 bool recursive_directory_iterator::__try_recursion(error_code* ec) {
354   ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
355 
356   bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
357 
358   auto& curr_it = __imp_->__stack_.top();
359 
360   bool skip_rec = false;
361   error_code m_ec;
362   if (!rec_sym) {
363     file_status st(curr_it.__entry_.__get_sym_ft(&m_ec));
364     if (m_ec && status_known(st))
365       m_ec.clear();
366     if (m_ec || is_symlink(st) || !is_directory(st))
367       skip_rec = true;
368   } else {
369     file_status st(curr_it.__entry_.__get_ft(&m_ec));
370     if (m_ec && status_known(st))
371       m_ec.clear();
372     if (m_ec || !is_directory(st))
373       skip_rec = true;
374   }
375 
376   if (!skip_rec) {
377     __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
378     if (new_it.good()) {
379       __imp_->__stack_.push(move(new_it));
380       return true;
381     }
382   }
383   if (m_ec) {
384     const bool allow_eacess =
385         bool(__imp_->__options_ & directory_options::skip_permission_denied);
386     if (m_ec.value() == EACCES && allow_eacess) {
387       if (ec)
388         ec->clear();
389     } else {
390       path at_ent = move(curr_it.__entry_.__p_);
391       __imp_.reset();
392       err.report(m_ec, "attempting recursion into \"%s\"", at_ent);
393     }
394   }
395   return false;
396 }
397 
398 _LIBCPP_END_NAMESPACE_FILESYSTEM
399