1 //===------------------ directory_iterator.cpp ----------------------------===// 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 "filesystem" 10 #include "__config" 11 #if defined(_LIBCPP_WIN32API) 12 #define WIN32_LEAN_AND_MEAN 13 #include <Windows.h> 14 #else 15 #include <dirent.h> 16 #endif 17 #include <errno.h> 18 19 #include "filesystem_common.h" 20 21 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM 22 23 namespace detail { 24 namespace { 25 26 #if !defined(_LIBCPP_WIN32API) 27 28 #if defined(DT_BLK) 29 template <class DirEntT, class = decltype(DirEntT::d_type)> 30 static file_type get_file_type(DirEntT* ent, int) { 31 switch (ent->d_type) { 32 case DT_BLK: 33 return file_type::block; 34 case DT_CHR: 35 return file_type::character; 36 case DT_DIR: 37 return file_type::directory; 38 case DT_FIFO: 39 return file_type::fifo; 40 case DT_LNK: 41 return file_type::symlink; 42 case DT_REG: 43 return file_type::regular; 44 case DT_SOCK: 45 return file_type::socket; 46 // Unlike in lstat, hitting "unknown" here simply means that the underlying 47 // filesystem doesn't support d_type. Report is as 'none' so we correctly 48 // set the cache to empty. 49 case DT_UNKNOWN: 50 break; 51 } 52 return file_type::none; 53 } 54 #endif // defined(DT_BLK) 55 56 template <class DirEntT> 57 static file_type get_file_type(DirEntT* ent, long) { 58 return file_type::none; 59 } 60 61 static pair<string_view, file_type> posix_readdir(DIR* dir_stream, 62 error_code& ec) { 63 struct dirent* dir_entry_ptr = nullptr; 64 errno = 0; // zero errno in order to detect errors 65 ec.clear(); 66 if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) { 67 if (errno) 68 ec = capture_errno(); 69 return {}; 70 } else { 71 return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)}; 72 } 73 } 74 #else 75 76 static file_type get_file_type(const WIN32_FIND_DATA& data) { 77 //auto attrs = data.dwFileAttributes; 78 // FIXME(EricWF) 79 return file_type::unknown; 80 } 81 static uintmax_t get_file_size(const WIN32_FIND_DATA& data) { 82 return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow; 83 } 84 static file_time_type get_write_time(const WIN32_FIND_DATA& data) { 85 ULARGE_INTEGER tmp; 86 FILETIME& time = data.ftLastWriteTime; 87 tmp.u.LowPart = time.dwLowDateTime; 88 tmp.u.HighPart = time.dwHighDateTime; 89 return file_time_type(file_time_type::duration(time.QuadPart)); 90 } 91 92 #endif 93 94 } // namespace 95 } // namespace detail 96 97 using detail::ErrorHandler; 98 99 #if defined(_LIBCPP_WIN32API) 100 class __dir_stream { 101 public: 102 __dir_stream() = delete; 103 __dir_stream& operator=(const __dir_stream&) = delete; 104 105 __dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_), 106 __root_(move(__ds.__root_)), 107 __entry_(move(__ds.__entry_)) { 108 __ds.__stream_ = INVALID_HANDLE_VALUE; 109 } 110 111 __dir_stream(const path& root, directory_options opts, error_code& ec) 112 : __stream_(INVALID_HANDLE_VALUE), __root_(root) { 113 __stream_ = ::FindFirstFileEx(root.c_str(), &__data_); 114 if (__stream_ == INVALID_HANDLE_VALUE) { 115 ec = error_code(::GetLastError(), generic_category()); 116 const bool ignore_permission_denied = 117 bool(opts & directory_options::skip_permission_denied); 118 if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED) 119 ec.clear(); 120 return; 121 } 122 } 123 124 ~__dir_stream() noexcept { 125 if (__stream_ == INVALID_HANDLE_VALUE) 126 return; 127 close(); 128 } 129 130 bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; } 131 132 bool advance(error_code& ec) { 133 while (::FindNextFile(__stream_, &__data_)) { 134 if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, "..")) 135 continue; 136 // FIXME: Cache more of this 137 //directory_entry::__cached_data cdata; 138 //cdata.__type_ = get_file_type(__data_); 139 //cdata.__size_ = get_file_size(__data_); 140 //cdata.__write_time_ = get_write_time(__data_); 141 __entry_.__assign_iter_entry( 142 __root_ / __data_.cFileName, 143 directory_entry::__create_iter_result(get_file_type(__data))); 144 return true; 145 } 146 ec = error_code(::GetLastError(), generic_category()); 147 close(); 148 return false; 149 } 150 151 private: 152 error_code close() noexcept { 153 error_code ec; 154 if (!::FindClose(__stream_)) 155 ec = error_code(::GetLastError(), generic_category()); 156 __stream_ = INVALID_HANDLE_VALUE; 157 return ec; 158 } 159 160 HANDLE __stream_{INVALID_HANDLE_VALUE}; 161 WIN32_FIND_DATA __data_; 162 163 public: 164 path __root_; 165 directory_entry __entry_; 166 }; 167 #else 168 class __dir_stream { 169 public: 170 __dir_stream() = delete; 171 __dir_stream& operator=(const __dir_stream&) = delete; 172 173 __dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_), 174 __root_(move(other.__root_)), 175 __entry_(move(other.__entry_)) { 176 other.__stream_ = nullptr; 177 } 178 179 __dir_stream(const path& root, directory_options opts, error_code& ec) 180 : __stream_(nullptr), __root_(root) { 181 if ((__stream_ = ::opendir(root.c_str())) == nullptr) { 182 ec = detail::capture_errno(); 183 const bool allow_eacess = 184 bool(opts & directory_options::skip_permission_denied); 185 if (allow_eacess && ec.value() == EACCES) 186 ec.clear(); 187 return; 188 } 189 advance(ec); 190 } 191 192 ~__dir_stream() noexcept { 193 if (__stream_) 194 close(); 195 } 196 197 bool good() const noexcept { return __stream_ != nullptr; } 198 199 bool advance(error_code& ec) { 200 while (true) { 201 auto str_type_pair = detail::posix_readdir(__stream_, ec); 202 auto& str = str_type_pair.first; 203 if (str == "." || str == "..") { 204 continue; 205 } else if (ec || str.empty()) { 206 close(); 207 return false; 208 } else { 209 __entry_.__assign_iter_entry( 210 __root_ / str, 211 directory_entry::__create_iter_result(str_type_pair.second)); 212 return true; 213 } 214 } 215 } 216 217 private: 218 error_code close() noexcept { 219 error_code m_ec; 220 if (::closedir(__stream_) == -1) 221 m_ec = detail::capture_errno(); 222 __stream_ = nullptr; 223 return m_ec; 224 } 225 226 DIR* __stream_{nullptr}; 227 228 public: 229 path __root_; 230 directory_entry __entry_; 231 }; 232 #endif 233 234 // directory_iterator 235 236 directory_iterator::directory_iterator(const path& p, error_code* ec, 237 directory_options opts) { 238 ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p); 239 240 error_code m_ec; 241 __imp_ = make_shared<__dir_stream>(p, opts, m_ec); 242 if (ec) 243 *ec = m_ec; 244 if (!__imp_->good()) { 245 __imp_.reset(); 246 if (m_ec) 247 err.report(m_ec); 248 } 249 } 250 251 directory_iterator& directory_iterator::__increment(error_code* ec) { 252 _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator"); 253 ErrorHandler<void> err("directory_iterator::operator++()", ec); 254 255 error_code m_ec; 256 if (!__imp_->advance(m_ec)) { 257 path root = move(__imp_->__root_); 258 __imp_.reset(); 259 if (m_ec) 260 err.report(m_ec, "at root \"%s\"", root); 261 } 262 return *this; 263 } 264 265 directory_entry const& directory_iterator::__dereference() const { 266 _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator"); 267 return __imp_->__entry_; 268 } 269 270 // recursive_directory_iterator 271 272 struct recursive_directory_iterator::__shared_imp { 273 stack<__dir_stream> __stack_; 274 directory_options __options_; 275 }; 276 277 recursive_directory_iterator::recursive_directory_iterator( 278 const path& p, directory_options opt, error_code* ec) 279 : __imp_(nullptr), __rec_(true) { 280 ErrorHandler<void> err("recursive_directory_iterator", ec, &p); 281 282 error_code m_ec; 283 __dir_stream new_s(p, opt, m_ec); 284 if (m_ec) 285 err.report(m_ec); 286 if (m_ec || !new_s.good()) 287 return; 288 289 __imp_ = make_shared<__shared_imp>(); 290 __imp_->__options_ = opt; 291 __imp_->__stack_.push(move(new_s)); 292 } 293 294 void recursive_directory_iterator::__pop(error_code* ec) { 295 _LIBCPP_ASSERT(__imp_, "Popping the end iterator"); 296 if (ec) 297 ec->clear(); 298 __imp_->__stack_.pop(); 299 if (__imp_->__stack_.size() == 0) 300 __imp_.reset(); 301 else 302 __advance(ec); 303 } 304 305 directory_options recursive_directory_iterator::options() const { 306 return __imp_->__options_; 307 } 308 309 int recursive_directory_iterator::depth() const { 310 return __imp_->__stack_.size() - 1; 311 } 312 313 const directory_entry& recursive_directory_iterator::__dereference() const { 314 return __imp_->__stack_.top().__entry_; 315 } 316 317 recursive_directory_iterator& 318 recursive_directory_iterator::__increment(error_code* ec) { 319 if (ec) 320 ec->clear(); 321 if (recursion_pending()) { 322 if (__try_recursion(ec) || (ec && *ec)) 323 return *this; 324 } 325 __rec_ = true; 326 __advance(ec); 327 return *this; 328 } 329 330 void recursive_directory_iterator::__advance(error_code* ec) { 331 ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec); 332 333 const directory_iterator end_it; 334 auto& stack = __imp_->__stack_; 335 error_code m_ec; 336 while (stack.size() > 0) { 337 if (stack.top().advance(m_ec)) 338 return; 339 if (m_ec) 340 break; 341 stack.pop(); 342 } 343 344 if (m_ec) { 345 path root = move(stack.top().__root_); 346 __imp_.reset(); 347 err.report(m_ec, "at root \"%s\"", root); 348 } else { 349 __imp_.reset(); 350 } 351 } 352 353 bool recursive_directory_iterator::__try_recursion(error_code* ec) { 354 ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec); 355 356 bool rec_sym = bool(options() & directory_options::follow_directory_symlink); 357 358 auto& curr_it = __imp_->__stack_.top(); 359 360 bool skip_rec = false; 361 error_code m_ec; 362 if (!rec_sym) { 363 file_status st(curr_it.__entry_.__get_sym_ft(&m_ec)); 364 if (m_ec && status_known(st)) 365 m_ec.clear(); 366 if (m_ec || is_symlink(st) || !is_directory(st)) 367 skip_rec = true; 368 } else { 369 file_status st(curr_it.__entry_.__get_ft(&m_ec)); 370 if (m_ec && status_known(st)) 371 m_ec.clear(); 372 if (m_ec || !is_directory(st)) 373 skip_rec = true; 374 } 375 376 if (!skip_rec) { 377 __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec); 378 if (new_it.good()) { 379 __imp_->__stack_.push(move(new_it)); 380 return true; 381 } 382 } 383 if (m_ec) { 384 const bool allow_eacess = 385 bool(__imp_->__options_ & directory_options::skip_permission_denied); 386 if (m_ec.value() == EACCES && allow_eacess) { 387 if (ec) 388 ec->clear(); 389 } else { 390 path at_ent = move(curr_it.__entry_.__p_); 391 __imp_.reset(); 392 err.report(m_ec, "attempting recursion into \"%s\"", at_ent); 393 } 394 } 395 return false; 396 } 397 398 _LIBCPP_END_NAMESPACE_FILESYSTEM 399