]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libc++/src/experimental/filesystem/directory_iterator.cpp
MFV r310796, r310797:
[FreeBSD/FreeBSD.git] / contrib / libc++ / src / experimental / filesystem / directory_iterator.cpp
1 #include "experimental/filesystem"
2 #include <dirent.h>
3 #include <errno.h>
4
5 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
6
7 namespace { namespace detail {
8
9 inline error_code capture_errno() {
10     _LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
11     return error_code{errno, std::generic_category()};
12 }
13
14 template <class ...Args>
15 inline bool capture_error_or_throw(std::error_code* user_ec,
16                                    const char* msg, Args&&... args)
17 {
18     std::error_code my_ec = capture_errno();
19     if (user_ec) {
20         *user_ec = my_ec;
21         return true;
22     }
23     __libcpp_throw(filesystem_error(msg, std::forward<Args>(args)..., my_ec));
24     return false;
25 }
26
27 template <class ...Args>
28 inline bool set_or_throw(std::error_code& my_ec,
29                                std::error_code* user_ec,
30                                const char* msg, Args&&... args)
31 {
32     if (user_ec) {
33         *user_ec = my_ec;
34         return true;
35     }
36     __libcpp_throw(filesystem_error(msg, std::forward<Args>(args)..., my_ec));
37     return false;
38 }
39
40 typedef path::string_type string_type;
41
42
43 inline string_type posix_readdir(DIR *dir_stream, error_code& ec) {
44     struct dirent* dir_entry_ptr = nullptr;
45     errno = 0; // zero errno in order to detect errors
46     if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
47         ec = capture_errno();
48         return {};
49     } else {
50         ec.clear();
51         return dir_entry_ptr->d_name;
52     }
53 }
54
55 }}                                                       // namespace detail
56
57 using detail::set_or_throw;
58
59 class __dir_stream {
60 public:
61     __dir_stream() = delete;
62     __dir_stream& operator=(const __dir_stream&) = delete;
63
64     __dir_stream(__dir_stream&& other) noexcept
65         : __stream_(other.__stream_), __root_(std::move(other.__root_)),
66           __entry_(std::move(other.__entry_))
67     {
68         other.__stream_ = nullptr;
69     }
70
71
72     __dir_stream(const path& root, directory_options opts, error_code& ec)
73         : __stream_(nullptr),
74           __root_(root)
75     {
76         if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
77             ec = detail::capture_errno();
78             const bool allow_eacess =
79                 bool(opts & directory_options::skip_permission_denied);
80             if (allow_eacess && ec.value() == EACCES)
81                 ec.clear();
82             return;
83         }
84         advance(ec);
85     }
86
87     ~__dir_stream() noexcept
88       { if (__stream_) close(); }
89
90     bool good() const noexcept { return __stream_ != nullptr; }
91
92     bool advance(error_code &ec) {
93         while (true) {
94             auto str = detail::posix_readdir(__stream_,  ec);
95             if (str == "." || str == "..") {
96                 continue;
97             } else if (ec || str.empty()) {
98                 close();
99                 return false;
100             } else {
101                 __entry_.assign(__root_ / str);
102                 return true;
103             }
104         }
105     }
106 private:
107     std::error_code close() noexcept {
108         std::error_code m_ec;
109         if (::closedir(__stream_) == -1)
110            m_ec = detail::capture_errno();
111         __stream_ = nullptr;
112         return m_ec;
113     }
114
115     DIR * __stream_{nullptr};
116 public:
117     path __root_;
118     directory_entry __entry_;
119 };
120
121 // directory_iterator
122
123 directory_iterator::directory_iterator(const path& p, error_code *ec,
124                                        directory_options opts)
125 {
126     std::error_code m_ec;
127     __imp_ = make_shared<__dir_stream>(p, opts, m_ec);
128     if (ec) *ec = m_ec;
129     if (!__imp_->good()) {
130         __imp_.reset();
131         if (m_ec)
132             set_or_throw(m_ec, ec,
133                          "directory_iterator::directory_iterator(...)", p);
134     }
135 }
136
137 directory_iterator& directory_iterator::__increment(error_code *ec)
138 {
139     _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
140     std::error_code m_ec;
141     if (!__imp_->advance(m_ec)) {
142         __imp_.reset();
143         if (m_ec)
144             set_or_throw(m_ec, ec, "directory_iterator::operator++()");
145     } else {
146         if (ec) ec->clear();
147     }
148     return *this;
149
150 }
151
152 directory_entry const& directory_iterator::__deref() const {
153     _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
154     return __imp_->__entry_;
155 }
156
157 // recursive_directory_iterator
158
159 struct recursive_directory_iterator::__shared_imp {
160   stack<__dir_stream> __stack_;
161   directory_options   __options_;
162 };
163
164 recursive_directory_iterator::recursive_directory_iterator(const path& p,
165     directory_options opt, error_code *ec)
166     : __imp_(nullptr), __rec_(true)
167 {
168     if (ec) ec->clear();
169     std::error_code m_ec;
170     __dir_stream new_s(p, opt, m_ec);
171     if (m_ec) set_or_throw(m_ec, ec, "recursive_directory_iterator", p);
172     if (m_ec || !new_s.good()) return;
173
174     __imp_ = _VSTD::make_shared<__shared_imp>();
175     __imp_->__options_ = opt;
176     __imp_->__stack_.push(_VSTD::move(new_s));
177 }
178
179 void recursive_directory_iterator::__pop(error_code* ec)
180 {
181     _LIBCPP_ASSERT(__imp_, "Popping the end iterator");
182     if (ec) ec->clear();
183     __imp_->__stack_.pop();
184     if (__imp_->__stack_.size() == 0)
185         __imp_.reset();
186     else
187         __advance(ec);
188 }
189
190 directory_options recursive_directory_iterator::options() const {
191     return __imp_->__options_;
192 }
193
194 int recursive_directory_iterator::depth() const {
195     return __imp_->__stack_.size() - 1;
196 }
197
198 const directory_entry& recursive_directory_iterator::__deref() const {
199     return __imp_->__stack_.top().__entry_;
200 }
201
202 recursive_directory_iterator&
203 recursive_directory_iterator::__increment(error_code *ec)
204 {
205     if (ec) ec->clear();
206     if (recursion_pending()) {
207         if (__try_recursion(ec) || (ec && *ec))
208             return *this;
209     }
210     __rec_ = true;
211     __advance(ec);
212     return *this;
213 }
214
215 void recursive_directory_iterator::__advance(error_code* ec) {
216     // REQUIRES: ec must be cleared before calling this function.
217     const directory_iterator end_it;
218     auto& stack = __imp_->__stack_;
219     std::error_code m_ec;
220     while (stack.size() > 0) {
221         if (stack.top().advance(m_ec))
222             return;
223         if (m_ec) break;
224         stack.pop();
225     }
226     __imp_.reset();
227     if (m_ec)
228         set_or_throw(m_ec, ec, "recursive_directory_iterator::operator++()");
229 }
230
231 bool recursive_directory_iterator::__try_recursion(error_code *ec) {
232
233     bool rec_sym =
234         bool(options() & directory_options::follow_directory_symlink);
235     auto& curr_it = __imp_->__stack_.top();
236
237     if (is_directory(curr_it.__entry_.status()) &&
238         (!is_symlink(curr_it.__entry_.symlink_status()) || rec_sym))
239     {
240         std::error_code m_ec;
241         __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
242         if (new_it.good()) {
243             __imp_->__stack_.push(_VSTD::move(new_it));
244             return true;
245         }
246         if (m_ec) {
247             __imp_.reset();
248             set_or_throw(m_ec, ec,
249                                "recursive_directory_iterator::operator++()");
250         }
251     }
252     return false;
253 }
254
255
256 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM