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