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 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