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