1 // Class filesystem::directory_entry etc. -*- C++ -*- 2 3 // Copyright (C) 2014-2022 Free Software Foundation, Inc. 4 // 5 // This file is part of the GNU ISO C++ Library. This library is free 6 // software; you can redistribute it and/or modify it under the 7 // terms of the GNU General Public License as published by the 8 // Free Software Foundation; either version 3, or (at your option) 9 // any later version. 10 11 // This library is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 16 // Under Section 7 of GPL version 3, you are granted additional 17 // permissions described in the GCC Runtime Library Exception, version 18 // 3.1, as published by the Free Software Foundation. 19 20 // You should have received a copy of the GNU General Public License and 21 // a copy of the GCC Runtime Library Exception along with this program; 22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 // <http://www.gnu.org/licenses/>. 24 25 #ifndef _GLIBCXX_USE_CXX11_ABI 26 # define _GLIBCXX_USE_CXX11_ABI 1 27 #endif 28 29 #include <bits/largefile-config.h> 30 #include <filesystem> 31 #include <utility> 32 #include <stack> 33 #include <string.h> 34 #include <errno.h> 35 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem { 36 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM } 37 #include "../filesystem/dir-common.h" 38 39 namespace fs = std::filesystem; 40 namespace posix = std::filesystem::__gnu_posix; 41 42 template class std::__shared_ptr<fs::_Dir>; 43 template class std::__shared_ptr<fs::recursive_directory_iterator::_Dir_stack>; 44 45 struct fs::_Dir : _Dir_base 46 { 47 _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow, 48 [[maybe_unused]] bool filename_only, error_code& ec) 49 : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec) 50 { 51 #if _GLIBCXX_HAVE_DIRFD && _GLIBCXX_HAVE_OPENAT && _GLIBCXX_HAVE_UNLINKAT 52 if (filename_only) 53 return; // Do not store path p when we aren't going to use it. 54 #endif 55 56 if (!ec) 57 path = p; 58 } 59 60 _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { } 61 62 _Dir(_Dir&&) = default; 63 64 // Returns false when the end of the directory entries is reached. 65 // Reports errors by setting ec. 66 bool advance(bool skip_permission_denied, error_code& ec) noexcept 67 { 68 if (const auto entp = _Dir_base::advance(skip_permission_denied, ec)) 69 { 70 auto name = path; 71 name /= entp->d_name; 72 file_type type = file_type::none; 73 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE 74 // Even if the OS supports dirent::d_type the filesystem might not: 75 if (entp->d_type != DT_UNKNOWN) 76 type = get_file_type(*entp); 77 #endif 78 entry = fs::directory_entry{std::move(name), type}; 79 return true; 80 } 81 else if (!ec) 82 { 83 // reached the end 84 entry = {}; 85 } 86 return false; 87 } 88 89 bool advance(error_code& ec) noexcept { return advance(false, ec); } 90 91 // Returns false when the end of the directory entries is reached. 92 // Reports errors by throwing. 93 bool advance(bool skip_permission_denied = false) 94 { 95 error_code ec; 96 const bool ok = advance(skip_permission_denied, ec); 97 if (ec) 98 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 99 "directory iterator cannot advance", ec)); 100 return ok; 101 } 102 103 bool should_recurse(bool follow_symlink, error_code& ec) const 104 { 105 file_type type = entry._M_type; 106 if (type == file_type::none) 107 { 108 type = entry.symlink_status(ec).type(); 109 if (ec) 110 return false; 111 } 112 113 if (type == file_type::directory) 114 return true; 115 if (type == file_type::symlink) 116 return follow_symlink && is_directory(entry.status(ec)); 117 return false; 118 } 119 120 // Return a pathname for the current directory entry, as an _At_path. 121 _Dir_base::_At_path 122 current() const noexcept 123 { 124 const fs::path& p = entry.path(); 125 #if _GLIBCXX_HAVE_DIRFD 126 if (!p.empty()) [[__likely__]] 127 { 128 auto len = std::prev(p.end())->native().size(); 129 return {::dirfd(this->dirp), p.c_str(), p.native().size() - len}; 130 } 131 #endif 132 return p.c_str(); 133 } 134 135 // Create a new _Dir for the directory this->entry.path(). 136 _Dir 137 open_subdir(bool skip_permission_denied, bool nofollow, 138 error_code& ec) const noexcept 139 { 140 _Dir_base d(current(), skip_permission_denied, nofollow, ec); 141 // If this->path is empty, the new _Dir should have an empty path too. 142 const fs::path& p = this->path.empty() ? this->path : this->entry.path(); 143 return _Dir(std::move(d), p); 144 } 145 146 bool 147 do_unlink(bool is_directory, error_code& ec) const noexcept 148 { 149 #if _GLIBCXX_HAVE_UNLINKAT 150 const auto atp = current(); 151 if (::unlinkat(atp.dir(), atp.path_at_dir(), 152 is_directory ? AT_REMOVEDIR : 0) == -1) 153 { 154 ec.assign(errno, std::generic_category()); 155 return false; 156 } 157 else 158 { 159 ec.clear(); 160 return true; 161 } 162 #else 163 return fs::remove(entry.path(), ec); 164 #endif 165 } 166 167 // Remove the non-directory that this->entry refers to. 168 bool 169 unlink(error_code& ec) const noexcept 170 { return do_unlink(/* is_directory*/ false, ec); } 171 172 // Remove the directory that this->entry refers to. 173 bool 174 rmdir(error_code& ec) const noexcept 175 { return do_unlink(/* is_directory*/ true, ec); } 176 177 fs::path path; // Empty if only using unlinkat with file descr. 178 directory_entry entry; 179 }; 180 181 namespace 182 { 183 template<typename Bitmask> 184 inline bool 185 is_set(Bitmask obj, Bitmask bits) 186 { 187 return (obj & bits) != Bitmask::none; 188 } 189 190 // Non-standard directory option flags, currently only for internal use: 191 // 192 // Do not allow directory iterator to open a symlink. 193 // This might seem redundant given directory_options::follow_directory_symlink 194 // but that is only checked for recursing into sub-directories, and we need 195 // something that controls the initial opendir() call in the constructor. 196 constexpr fs::directory_options __directory_iterator_nofollow{64}; 197 // Do not store full paths in std::filesystem::recursive_directory_iterator. 198 // When fs::remove_all uses recursive_directory_iterator::__erase and unlinkat 199 // is available in libc, we do not need the parent directory's path, only the 200 // filenames of the directory entries (and a file descriptor for the parent). 201 // This flag avoids allocating memory for full paths that won't be needed. 202 constexpr fs::directory_options __directory_iterator_filename_only{128}; 203 } 204 205 fs::directory_iterator:: 206 directory_iterator(const path& p, directory_options options, error_code* ecptr) 207 { 208 // Do not report an error for permission denied errors. 209 const bool skip_permission_denied 210 = is_set(options, directory_options::skip_permission_denied); 211 // Do not allow opening a symlink. 212 const bool nofollow = is_set(options, __directory_iterator_nofollow); 213 214 error_code ec; 215 _Dir dir(p, skip_permission_denied, nofollow, /*filename only*/false, ec); 216 217 if (dir.dirp) 218 { 219 auto sp = std::__make_shared<fs::_Dir>(std::move(dir)); 220 if (sp->advance(skip_permission_denied, ec)) 221 _M_dir.swap(sp); 222 } 223 if (ecptr) 224 *ecptr = ec; 225 else if (ec) 226 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( 227 "directory iterator cannot open directory", p, ec)); 228 } 229 230 const fs::directory_entry& 231 fs::directory_iterator::operator*() const noexcept 232 { 233 return _M_dir->entry; 234 } 235 236 fs::directory_iterator& 237 fs::directory_iterator::operator++() 238 { 239 if (!_M_dir) 240 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 241 "cannot advance non-dereferenceable directory iterator", 242 std::make_error_code(errc::invalid_argument))); 243 if (!_M_dir->advance()) 244 _M_dir.reset(); 245 return *this; 246 } 247 248 fs::directory_iterator& 249 fs::directory_iterator::increment(error_code& ec) 250 { 251 if (!_M_dir) 252 { 253 ec = std::make_error_code(errc::invalid_argument); 254 return *this; 255 } 256 if (!_M_dir->advance(ec)) 257 _M_dir.reset(); 258 return *this; 259 } 260 261 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> 262 { 263 _Dir_stack(directory_options opts, _Dir&& dir) 264 : options(opts), pending(true) 265 { 266 this->push(std::move(dir)); 267 } 268 269 path::string_type orig; 270 const directory_options options; 271 bool pending; 272 273 void clear() { c.clear(); } 274 275 path current_path() const 276 { 277 path p; 278 if (top().path.empty()) 279 { 280 // Reconstruct path that failed from dir stack. 281 p = orig; 282 for (auto& d : this->c) 283 p /= d.entry.path(); 284 } 285 else 286 p = top().entry.path(); 287 return p; 288 } 289 }; 290 291 fs::recursive_directory_iterator:: 292 recursive_directory_iterator(const path& p, directory_options options, 293 error_code* ecptr) 294 { 295 // Do not report an error for permission denied errors. 296 const bool skip_permission_denied 297 = is_set(options, directory_options::skip_permission_denied); 298 // Do not allow opening a symlink as the starting directory. 299 const bool nofollow = is_set(options, __directory_iterator_nofollow); 300 // Prefer to store only filenames (not full paths) in directory_entry values. 301 const bool filename_only 302 = is_set(options, __directory_iterator_filename_only); 303 304 error_code ec; 305 _Dir dir(p, skip_permission_denied, nofollow, filename_only, ec); 306 307 if (dir.dirp) 308 { 309 auto sp = std::__make_shared<_Dir_stack>(options, std::move(dir)); 310 if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr) 311 : sp->top().advance(skip_permission_denied)) 312 { 313 _M_dirs.swap(sp); 314 if (filename_only) // Need to save original path for error reporting. 315 _M_dirs->orig = p.native(); 316 } 317 } 318 else if (ecptr) 319 *ecptr = ec; 320 else if (ec) 321 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( 322 "recursive directory iterator cannot open directory", p, ec)); 323 } 324 325 fs::recursive_directory_iterator::~recursive_directory_iterator() = default; 326 327 fs::directory_options 328 fs::recursive_directory_iterator::options() const noexcept 329 { 330 return _M_dirs->options; 331 } 332 333 int 334 fs::recursive_directory_iterator::depth() const noexcept 335 { 336 return int(_M_dirs->size()) - 1; 337 } 338 339 bool 340 fs::recursive_directory_iterator::recursion_pending() const noexcept 341 { 342 return _M_dirs->pending; 343 } 344 345 const fs::directory_entry& 346 fs::recursive_directory_iterator::operator*() const noexcept 347 { 348 return _M_dirs->top().entry; 349 } 350 351 fs::recursive_directory_iterator& 352 fs::recursive_directory_iterator:: 353 operator=(const recursive_directory_iterator& other) noexcept = default; 354 355 fs::recursive_directory_iterator& 356 fs::recursive_directory_iterator:: 357 operator=(recursive_directory_iterator&& other) noexcept = default; 358 359 fs::recursive_directory_iterator& 360 fs::recursive_directory_iterator::operator++() 361 { 362 error_code ec; 363 increment(ec); 364 if (ec) 365 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 366 "cannot increment recursive directory iterator", ec)); 367 return *this; 368 } 369 370 fs::recursive_directory_iterator& 371 fs::recursive_directory_iterator::increment(error_code& ec) 372 { 373 if (!_M_dirs) 374 { 375 ec = std::make_error_code(errc::invalid_argument); 376 return *this; 377 } 378 379 const bool follow 380 = is_set(_M_dirs->options, directory_options::follow_directory_symlink); 381 const bool skip_permission_denied 382 = is_set(_M_dirs->options, directory_options::skip_permission_denied); 383 384 auto& top = _M_dirs->top(); 385 386 if (std::exchange(_M_dirs->pending, true) && top.should_recurse(follow, ec)) 387 { 388 _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec); 389 if (ec) 390 { 391 _M_dirs.reset(); 392 return *this; 393 } 394 if (dir.dirp) 395 _M_dirs->push(std::move(dir)); 396 } 397 398 while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec) 399 { 400 _M_dirs->pop(); 401 if (_M_dirs->empty()) 402 { 403 _M_dirs.reset(); 404 return *this; 405 } 406 } 407 408 if (ec) 409 _M_dirs.reset(); 410 411 return *this; 412 } 413 414 void 415 fs::recursive_directory_iterator::pop(error_code& ec) 416 { 417 if (!_M_dirs) 418 { 419 ec = std::make_error_code(errc::invalid_argument); 420 return; 421 } 422 423 const bool skip_permission_denied 424 = is_set(_M_dirs->options, directory_options::skip_permission_denied); 425 426 do { 427 _M_dirs->pop(); 428 if (_M_dirs->empty()) 429 { 430 _M_dirs.reset(); 431 ec.clear(); 432 return; 433 } 434 } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec); 435 436 if (ec) 437 _M_dirs.reset(); 438 } 439 440 void 441 fs::recursive_directory_iterator::pop() 442 { 443 [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr; 444 error_code ec; 445 pop(ec); 446 if (ec) 447 _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable 448 ? "recursive directory iterator cannot pop" 449 : "non-dereferenceable recursive directory iterator cannot pop", 450 ec)); 451 } 452 453 void 454 fs::recursive_directory_iterator::disable_recursion_pending() noexcept 455 { 456 _M_dirs->pending = false; 457 } 458 459 // Used to implement filesystem::remove_all. 460 fs::recursive_directory_iterator& 461 fs::recursive_directory_iterator::__erase(error_code* ecptr) 462 { 463 error_code ec; 464 if (!_M_dirs) 465 { 466 ec = std::make_error_code(errc::invalid_argument); 467 return *this; 468 } 469 470 // We never want to skip permission denied when removing files. 471 const bool skip_permission_denied = false; 472 // We never want to follow directory symlinks when removing files. 473 const bool nofollow = true; 474 475 // Loop until we find something we can remove. 476 while (!ec) 477 { 478 auto& top = _M_dirs->top(); 479 480 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS 481 // _Dir::unlink uses fs::remove which uses std::system_category() for 482 // Windows errror codes, so we can't just check for EPERM and EISDIR. 483 // Use directory_entry::refresh() here to check if we have a directory. 484 // This can be a TOCTTOU race, but we don't have openat or unlinkat to 485 // solve that on Windows, and generally don't support symlinks anyway. 486 if (top.entry._M_type == file_type::none) 487 top.entry.refresh(); 488 #endif 489 490 if (top.entry._M_type == file_type::directory) 491 { 492 _Dir dir = top.open_subdir(skip_permission_denied, nofollow, ec); 493 if (!ec) 494 { 495 __glibcxx_assert(dir.dirp != nullptr); 496 if (dir.advance(skip_permission_denied, ec)) 497 { 498 // Non-empty directory, recurse into it. 499 _M_dirs->push(std::move(dir)); 500 continue; 501 } 502 if (!ec) 503 { 504 // Directory is empty so we can remove it. 505 if (top.rmdir(ec)) 506 break; // Success 507 } 508 } 509 } 510 else if (top.unlink(ec)) 511 break; // Success 512 #if ! _GLIBCXX_FILESYSTEM_IS_WINDOWS 513 else if (top.entry._M_type == file_type::none) 514 { 515 // We did not have a cached type, so it's possible that top.entry 516 // is actually a directory, and that's why the unlink above failed. 517 #ifdef EPERM 518 // POSIX.1-2017 says unlink on a directory returns EPERM, 519 // but LSB allows EISDIR too. Some targets don't even define EPERM. 520 if (ec.value() == EPERM || ec.value() == EISDIR) 521 #else 522 if (ec.value() == EISDIR) 523 #endif 524 { 525 // Retry, treating it as a directory. 526 top.entry._M_type = file_type::directory; 527 ec.clear(); 528 continue; 529 } 530 } 531 #endif 532 } 533 534 if (!ec) 535 { 536 // We successfully removed the current entry, so advance to the next one. 537 if (_M_dirs->top().advance(skip_permission_denied, ec)) 538 return *this; 539 else if (!ec) 540 { 541 // Reached the end of the current directory. 542 _M_dirs->pop(); 543 if (_M_dirs->empty()) 544 _M_dirs.reset(); 545 return *this; 546 } 547 } 548 549 // Reset _M_dirs to empty. 550 auto dirs = std::move(_M_dirs); 551 552 // Need to report an error 553 if (ecptr) 554 *ecptr = ec; 555 else 556 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error("cannot remove all", 557 dirs->orig, 558 dirs->current_path(), 559 ec)); 560 561 return *this; 562 } 563