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