]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libc++/src/experimental/filesystem/directory_iterator.cpp
MFC r343601:
[FreeBSD/FreeBSD.git] / contrib / libc++ / src / experimental / filesystem / directory_iterator.cpp
1 //===------------------ directory_iterator.cpp ----------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is dual licensed under the MIT and the University of Illinois Open
6 // Source Licenses. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "experimental/filesystem"
11 #include "__config"
12 #if defined(_LIBCPP_WIN32API)
13 #define WIN32_LEAN_AND_MEAN
14 #include <Windows.h>
15 #else
16 #include <dirent.h>
17 #endif
18 #include <errno.h>
19
20 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
21
22 namespace { namespace detail {
23
24 #if !defined(_LIBCPP_WIN32API)
25 inline error_code capture_errno() {
26     _LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
27     return error_code{errno, std::generic_category()};
28 }
29 #endif
30
31 template <class ...Args>
32 inline bool set_or_throw(std::error_code& my_ec,
33                                std::error_code* user_ec,
34                                const char* msg, Args&&... args)
35 {
36     if (user_ec) {
37         *user_ec = my_ec;
38         return true;
39     }
40     __throw_filesystem_error(msg, std::forward<Args>(args)..., my_ec);
41     return false;
42 }
43
44 #if !defined(_LIBCPP_WIN32API)
45 inline path::string_type posix_readdir(DIR *dir_stream, error_code& ec) {
46     struct dirent* dir_entry_ptr = nullptr;
47     errno = 0; // zero errno in order to detect errors
48     ec.clear();
49     if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
50         if (errno)
51           ec = capture_errno();
52         return {};
53     } else {
54         return dir_entry_ptr->d_name;
55     }
56 }
57 #endif
58
59 }}                                                       // namespace detail
60
61 using detail::set_or_throw;
62
63 #if defined(_LIBCPP_WIN32API)
64 class __dir_stream {
65 public:
66   __dir_stream() = delete;
67   __dir_stream& operator=(const __dir_stream&) = delete;
68
69   __dir_stream(__dir_stream&& __ds) noexcept
70       : __stream_(__ds.__stream_), __root_(std::move(__ds.__root_)),
71         __entry_(std::move(__ds.__entry_)) {
72     __ds.__stream_ = INVALID_HANDLE_VALUE;
73   }
74
75   __dir_stream(const path& root, directory_options opts, error_code& ec)
76       : __stream_(INVALID_HANDLE_VALUE), __root_(root) {
77     __stream_ = ::FindFirstFile(root.c_str(), &__data_);
78     if (__stream_ == INVALID_HANDLE_VALUE) {
79       ec = error_code(::GetLastError(), std::generic_category());
80       const bool ignore_permission_denied =
81           bool(opts & directory_options::skip_permission_denied);
82       if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
83         ec.clear();
84       return;
85     }
86   }
87
88   ~__dir_stream() noexcept {
89     if (__stream_ == INVALID_HANDLE_VALUE)
90       return;
91     close();
92   }
93
94   bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
95
96   bool advance(error_code& ec) {
97     while (::FindNextFile(__stream_, &__data_)) {
98       if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
99         continue;
100       __entry_.assign(__root_ / __data_.cFileName);
101       return true;
102     }
103     ec = error_code(::GetLastError(), std::generic_category());
104     close();
105     return false;
106   }
107
108 private:
109   std::error_code close() noexcept {
110     std::error_code ec;
111     if (!::FindClose(__stream_))
112       ec = error_code(::GetLastError(), std::generic_category());
113     __stream_ = INVALID_HANDLE_VALUE;
114     return ec;
115   }
116
117   HANDLE __stream_{INVALID_HANDLE_VALUE};
118   WIN32_FIND_DATA __data_;
119
120 public:
121   path __root_;
122   directory_entry __entry_;
123 };
124 #else
125 class __dir_stream {
126 public:
127     __dir_stream() = delete;
128     __dir_stream& operator=(const __dir_stream&) = delete;
129
130     __dir_stream(__dir_stream&& other) noexcept
131         : __stream_(other.__stream_), __root_(std::move(other.__root_)),
132           __entry_(std::move(other.__entry_))
133     {
134         other.__stream_ = nullptr;
135     }
136
137
138     __dir_stream(const path& root, directory_options opts, error_code& ec)
139         : __stream_(nullptr),
140           __root_(root)
141     {
142         if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
143             ec = detail::capture_errno();
144             const bool allow_eacess =
145                 bool(opts & directory_options::skip_permission_denied);
146             if (allow_eacess && ec.value() == EACCES)
147                 ec.clear();
148             return;
149         }
150         advance(ec);
151     }
152
153     ~__dir_stream() noexcept
154       { if (__stream_) close(); }
155
156     bool good() const noexcept { return __stream_ != nullptr; }
157
158     bool advance(error_code &ec) {
159         while (true) {
160             auto str = detail::posix_readdir(__stream_,  ec);
161             if (str == "." || str == "..") {
162                 continue;
163             } else if (ec || str.empty()) {
164                 close();
165                 return false;
166             } else {
167                 __entry_.assign(__root_ / str);
168                 return true;
169             }
170         }
171     }
172 private:
173     std::error_code close() noexcept {
174         std::error_code m_ec;
175         if (::closedir(__stream_) == -1)
176            m_ec = detail::capture_errno();
177         __stream_ = nullptr;
178         return m_ec;
179     }
180
181     DIR * __stream_{nullptr};
182 public:
183     path __root_;
184     directory_entry __entry_;
185 };
186 #endif
187
188 // directory_iterator
189
190 directory_iterator::directory_iterator(const path& p, error_code *ec,
191                                        directory_options opts)
192 {
193     std::error_code m_ec;
194     __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
195     if (ec) *ec = m_ec;
196     if (!__imp_->good()) {
197         __imp_.reset();
198         if (m_ec)
199             set_or_throw(m_ec, ec,
200                          "directory_iterator::directory_iterator(...)", p);
201     }
202 }
203
204 directory_iterator& directory_iterator::__increment(error_code *ec)
205 {
206     _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
207     std::error_code m_ec;
208     if (!__imp_->advance(m_ec)) {
209         __imp_.reset();
210         if (m_ec)
211             set_or_throw(m_ec, ec, "directory_iterator::operator++()");
212     } else {
213         if (ec) ec->clear();
214     }
215     return *this;
216
217 }
218
219 directory_entry const& directory_iterator::__dereference() const {
220     _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
221     return __imp_->__entry_;
222 }
223
224 // recursive_directory_iterator
225
226 struct recursive_directory_iterator::__shared_imp {
227   stack<__dir_stream> __stack_;
228   directory_options   __options_;
229 };
230
231 recursive_directory_iterator::recursive_directory_iterator(const path& p,
232     directory_options opt, error_code *ec)
233     : __imp_(nullptr), __rec_(true)
234 {
235     if (ec) ec->clear();
236     std::error_code m_ec;
237     __dir_stream new_s(p, opt, m_ec);
238     if (m_ec) set_or_throw(m_ec, ec, "recursive_directory_iterator", p);
239     if (m_ec || !new_s.good()) return;
240
241     __imp_ = _VSTD::make_shared<__shared_imp>();
242     __imp_->__options_ = opt;
243     __imp_->__stack_.push(_VSTD::move(new_s));
244 }
245
246 void recursive_directory_iterator::__pop(error_code* ec)
247 {
248     _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
249     if (ec) ec->clear();
250     __imp_->__stack_.pop();
251     if (__imp_->__stack_.size() == 0)
252         __imp_.reset();
253     else
254         __advance(ec);
255 }
256
257 directory_options recursive_directory_iterator::options() const {
258     return __imp_->__options_;
259 }
260
261 int recursive_directory_iterator::depth() const {
262     return __imp_->__stack_.size() - 1;
263 }
264
265 const directory_entry& recursive_directory_iterator::__dereference() const {
266     return __imp_->__stack_.top().__entry_;
267 }
268
269 recursive_directory_iterator&
270 recursive_directory_iterator::__increment(error_code *ec)
271 {
272     if (ec) ec->clear();
273     if (recursion_pending()) {
274         if (__try_recursion(ec) || (ec && *ec))
275             return *this;
276     }
277     __rec_ = true;
278     __advance(ec);
279     return *this;
280 }
281
282 void recursive_directory_iterator::__advance(error_code* ec) {
283     // REQUIRES: ec must be cleared before calling this function.
284     const directory_iterator end_it;
285     auto& stack = __imp_->__stack_;
286     std::error_code m_ec;
287     while (stack.size() > 0) {
288         if (stack.top().advance(m_ec))
289             return;
290         if (m_ec) break;
291         stack.pop();
292     }
293     __imp_.reset();
294     if (m_ec)
295         set_or_throw(m_ec, ec, "recursive_directory_iterator::operator++()");
296 }
297
298 bool recursive_directory_iterator::__try_recursion(error_code *ec) {
299     bool rec_sym =
300         bool(options() & directory_options::follow_directory_symlink);
301
302     auto& curr_it = __imp_->__stack_.top();
303
304     bool skip_rec = false;
305     std::error_code m_ec;
306     if (!rec_sym) {
307       file_status st = curr_it.__entry_.symlink_status(m_ec);
308       if (m_ec && status_known(st))
309         m_ec.clear();
310       if (m_ec || is_symlink(st) || !is_directory(st))
311         skip_rec = true;
312     } else {
313       file_status st = curr_it.__entry_.status(m_ec);
314       if (m_ec && status_known(st))
315         m_ec.clear();
316       if (m_ec || !is_directory(st))
317         skip_rec = true;
318     }
319
320     if (!skip_rec) {
321         __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
322         if (new_it.good()) {
323             __imp_->__stack_.push(_VSTD::move(new_it));
324             return true;
325         }
326     }
327     if (m_ec) {
328         const bool allow_eacess = bool(__imp_->__options_
329             & directory_options::skip_permission_denied);
330         if (m_ec.value() == EACCES && allow_eacess) {
331           if (ec) ec->clear();
332         } else {
333           __imp_.reset();
334           set_or_throw(m_ec, ec,
335                        "recursive_directory_iterator::operator++()");
336         }
337     }
338     return false;
339 }
340
341
342 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM