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)
capture_errno()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>
set_or_throw(std::error_code & my_ec,std::error_code * user_ec,const char * msg,Args &&...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)
posix_readdir(DIR * dir_stream,error_code & ec)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
__dir_stream(__dir_stream && __ds)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
__dir_stream(const path & root,directory_options opts,error_code & ec)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
~__dir_stream()88 ~__dir_stream() noexcept {
89 if (__stream_ == INVALID_HANDLE_VALUE)
90 return;
91 close();
92 }
93
good() const94 bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
95
advance(error_code & ec)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:
close()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
__dir_stream(__dir_stream && other)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
__dir_stream(const path & root,directory_options opts,error_code & ec)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
~__dir_stream()153 ~__dir_stream() noexcept
154 { if (__stream_) close(); }
155
good() const156 bool good() const noexcept { return __stream_ != nullptr; }
157
advance(error_code & ec)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:
close()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
directory_iterator(const path & p,error_code * ec,directory_options opts)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
__increment(error_code * ec)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
__dereference() const219 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
recursive_directory_iterator(const path & p,directory_options opt,error_code * ec)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
__pop(error_code * ec)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
options() const257 directory_options recursive_directory_iterator::options() const {
258 return __imp_->__options_;
259 }
260
depth() const261 int recursive_directory_iterator::depth() const {
262 return __imp_->__stack_.size() - 1;
263 }
264
__dereference() const265 const directory_entry& recursive_directory_iterator::__dereference() const {
266 return __imp_->__stack_.top().__entry_;
267 }
268
269 recursive_directory_iterator&
__increment(error_code * ec)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
__advance(error_code * ec)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
__try_recursion(error_code * ec)298 bool recursive_directory_iterator::__try_recursion(error_code *ec) {
299
300 bool rec_sym =
301 bool(options() & directory_options::follow_directory_symlink);
302 auto& curr_it = __imp_->__stack_.top();
303
304 if (is_directory(curr_it.__entry_.status()) &&
305 (!is_symlink(curr_it.__entry_.symlink_status()) || rec_sym))
306 {
307 std::error_code m_ec;
308 __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
309 if (new_it.good()) {
310 __imp_->__stack_.push(_VSTD::move(new_it));
311 return true;
312 }
313 if (m_ec) {
314 __imp_.reset();
315 set_or_throw(m_ec, ec,
316 "recursive_directory_iterator::operator++()");
317 }
318 }
319 return false;
320 }
321
322
323 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM
324