1 //===----------------------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include <__assert> 10 #include <__config> 11 #include <__memory/shared_ptr.h> 12 #include <errno.h> 13 #include <filesystem> 14 #include <stack> 15 #include <utility> 16 17 #include "error.h" 18 #include "file_descriptor.h" 19 20 #if defined(_LIBCPP_WIN32API) 21 # define WIN32_LEAN_AND_MEAN 22 # define NOMINMAX 23 # include <windows.h> 24 #else 25 # include <dirent.h> // for DIR & friends 26 #endif 27 28 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM 29 30 using detail::ErrorHandler; 31 32 #if defined(_LIBCPP_WIN32API) 33 class __dir_stream { 34 public: 35 __dir_stream() = delete; 36 __dir_stream& operator=(const __dir_stream&) = delete; 37 38 __dir_stream(__dir_stream&& __ds) noexcept 39 : __stream_(__ds.__stream_), __root_(std::move(__ds.__root_)), __entry_(std::move(__ds.__entry_)) { 40 __ds.__stream_ = INVALID_HANDLE_VALUE; 41 } 42 43 __dir_stream(const path& root, directory_options opts, error_code& ec) 44 : __stream_(INVALID_HANDLE_VALUE), __root_(root) { 45 if (root.native().empty()) { 46 ec = make_error_code(errc::no_such_file_or_directory); 47 return; 48 } 49 __stream_ = ::FindFirstFileW((root / "*").c_str(), &__data_); 50 if (__stream_ == INVALID_HANDLE_VALUE) { 51 ec = detail::get_last_error(); 52 const bool ignore_permission_denied = bool(opts & directory_options::skip_permission_denied); 53 if (ignore_permission_denied && ec == errc::permission_denied) 54 ec.clear(); 55 return; 56 } 57 if (!assign()) 58 advance(ec); 59 } 60 61 ~__dir_stream() noexcept { 62 if (__stream_ == INVALID_HANDLE_VALUE) 63 return; 64 close(); 65 } 66 67 bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; } 68 69 bool advance(error_code& ec) { 70 while (::FindNextFileW(__stream_, &__data_)) { 71 if (assign()) 72 return true; 73 } 74 close(); 75 return false; 76 } 77 78 bool assign() { 79 if (!wcscmp(__data_.cFileName, L".") || !wcscmp(__data_.cFileName, L"..")) 80 return false; 81 __entry_.__assign_iter_entry( 82 __root_ / __data_.cFileName, 83 directory_entry::__create_iter_cached_result( 84 detail::get_file_type(__data_), 85 detail::get_file_size(__data_), 86 detail::get_file_perm(__data_), 87 detail::get_write_time(__data_))); 88 return true; 89 } 90 91 private: 92 error_code close() noexcept { 93 error_code ec; 94 if (!::FindClose(__stream_)) 95 ec = detail::get_last_error(); 96 __stream_ = INVALID_HANDLE_VALUE; 97 return ec; 98 } 99 100 HANDLE __stream_{INVALID_HANDLE_VALUE}; 101 WIN32_FIND_DATAW __data_; 102 103 public: 104 path __root_; 105 directory_entry __entry_; 106 }; 107 #else 108 class __dir_stream { 109 public: 110 __dir_stream() = delete; 111 __dir_stream& operator=(const __dir_stream&) = delete; 112 113 __dir_stream(__dir_stream&& other) noexcept 114 : __stream_(other.__stream_), __root_(std::move(other.__root_)), __entry_(std::move(other.__entry_)) { 115 other.__stream_ = nullptr; 116 } 117 118 __dir_stream(const path& root, directory_options opts, error_code& ec) : __stream_(nullptr), __root_(root) { 119 if ((__stream_ = ::opendir(root.c_str())) == nullptr) { 120 ec = detail::capture_errno(); 121 const bool allow_eacces = bool(opts & directory_options::skip_permission_denied); 122 if (allow_eacces && ec == errc::permission_denied) 123 ec.clear(); 124 return; 125 } 126 advance(ec); 127 } 128 129 ~__dir_stream() noexcept { 130 if (__stream_) 131 close(); 132 } 133 134 bool good() const noexcept { return __stream_ != nullptr; } 135 136 bool advance(error_code& ec) { 137 while (true) { 138 auto str_type_pair = detail::posix_readdir(__stream_, ec); 139 auto& str = str_type_pair.first; 140 if (str == "." || str == "..") { 141 continue; 142 } else if (ec || str.empty()) { 143 close(); 144 return false; 145 } else { 146 __entry_.__assign_iter_entry(__root_ / str, directory_entry::__create_iter_result(str_type_pair.second)); 147 return true; 148 } 149 } 150 } 151 152 private: 153 error_code close() noexcept { 154 error_code m_ec; 155 if (::closedir(__stream_) == -1) 156 m_ec = detail::capture_errno(); 157 __stream_ = nullptr; 158 return m_ec; 159 } 160 161 DIR* __stream_{nullptr}; 162 163 public: 164 path __root_; 165 directory_entry __entry_; 166 }; 167 #endif 168 169 // directory_iterator 170 171 directory_iterator::directory_iterator(const path& p, error_code* ec, directory_options opts) { 172 ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p); 173 174 error_code m_ec; 175 __imp_ = make_shared<__dir_stream>(p, opts, m_ec); 176 if (ec) 177 *ec = m_ec; 178 if (!__imp_->good()) { 179 __imp_.reset(); 180 if (m_ec) 181 err.report(m_ec); 182 } 183 } 184 185 directory_iterator& directory_iterator::__increment(error_code* ec) { 186 _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Attempting to increment an invalid iterator"); 187 ErrorHandler<void> err("directory_iterator::operator++()", ec); 188 189 error_code m_ec; 190 if (!__imp_->advance(m_ec)) { 191 path root = std::move(__imp_->__root_); 192 __imp_.reset(); 193 if (m_ec) 194 err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str()); 195 } 196 return *this; 197 } 198 199 directory_entry const& directory_iterator::__dereference() const { 200 _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Attempting to dereference an invalid iterator"); 201 return __imp_->__entry_; 202 } 203 204 // recursive_directory_iterator 205 206 struct recursive_directory_iterator::__shared_imp { 207 stack<__dir_stream> __stack_; 208 directory_options __options_; 209 }; 210 211 recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options opt, error_code* ec) 212 : __imp_(nullptr), __rec_(true) { 213 ErrorHandler<void> err("recursive_directory_iterator", ec, &p); 214 215 error_code m_ec; 216 __dir_stream new_s(p, opt, m_ec); 217 if (m_ec) 218 err.report(m_ec); 219 if (m_ec || !new_s.good()) 220 return; 221 222 __imp_ = make_shared<__shared_imp>(); 223 __imp_->__options_ = opt; 224 __imp_->__stack_.push(std::move(new_s)); 225 } 226 227 void recursive_directory_iterator::__pop(error_code* ec) { 228 _LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Popping the end iterator"); 229 if (ec) 230 ec->clear(); 231 __imp_->__stack_.pop(); 232 if (__imp_->__stack_.size() == 0) 233 __imp_.reset(); 234 else 235 __advance(ec); 236 } 237 238 directory_options recursive_directory_iterator::options() const { return __imp_->__options_; } 239 240 int recursive_directory_iterator::depth() const { return __imp_->__stack_.size() - 1; } 241 242 const directory_entry& recursive_directory_iterator::__dereference() const { return __imp_->__stack_.top().__entry_; } 243 244 recursive_directory_iterator& recursive_directory_iterator::__increment(error_code* ec) { 245 if (ec) 246 ec->clear(); 247 if (recursion_pending()) { 248 if (__try_recursion(ec) || (ec && *ec)) 249 return *this; 250 } 251 __rec_ = true; 252 __advance(ec); 253 return *this; 254 } 255 256 void recursive_directory_iterator::__advance(error_code* ec) { 257 ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec); 258 259 const directory_iterator end_it; 260 auto& stack = __imp_->__stack_; 261 error_code m_ec; 262 while (stack.size() > 0) { 263 if (stack.top().advance(m_ec)) 264 return; 265 if (m_ec) 266 break; 267 stack.pop(); 268 } 269 270 if (m_ec) { 271 path root = std::move(stack.top().__root_); 272 __imp_.reset(); 273 err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str()); 274 } else { 275 __imp_.reset(); 276 } 277 } 278 279 bool recursive_directory_iterator::__try_recursion(error_code* ec) { 280 ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec); 281 282 bool rec_sym = bool(options() & directory_options::follow_directory_symlink); 283 284 auto& curr_it = __imp_->__stack_.top(); 285 286 bool skip_rec = false; 287 error_code m_ec; 288 if (!rec_sym) { 289 file_status st(curr_it.__entry_.__get_sym_ft(&m_ec)); 290 if (m_ec && status_known(st)) 291 m_ec.clear(); 292 if (m_ec || is_symlink(st) || !is_directory(st)) 293 skip_rec = true; 294 } else { 295 file_status st(curr_it.__entry_.__get_ft(&m_ec)); 296 if (m_ec && status_known(st)) 297 m_ec.clear(); 298 if (m_ec || !is_directory(st)) 299 skip_rec = true; 300 } 301 302 if (!skip_rec) { 303 __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec); 304 if (new_it.good()) { 305 __imp_->__stack_.push(std::move(new_it)); 306 return true; 307 } 308 } 309 if (m_ec) { 310 const bool allow_eacess = bool(__imp_->__options_ & directory_options::skip_permission_denied); 311 if (m_ec == errc::permission_denied && allow_eacess) { 312 if (ec) 313 ec->clear(); 314 } else { 315 path at_ent = std::move(curr_it.__entry_.__p_); 316 __imp_.reset(); 317 err.report(m_ec, "attempting recursion into " PATH_CSTR_FMT, at_ent.c_str()); 318 } 319 } 320 return false; 321 } 322 323 _LIBCPP_END_NAMESPACE_FILESYSTEM 324