|
24 | 24 | # define NOMINMAX
|
25 | 25 | # include <windows.h>
|
26 | 26 | #else
|
27 |
| -# include <unistd.h> |
| 27 | +# include <dirent.h> |
28 | 28 | # include <sys/stat.h>
|
29 | 29 | # include <sys/statvfs.h>
|
| 30 | +# include <unistd.h> |
30 | 31 | #endif
|
31 | 32 | #include <time.h>
|
32 | 33 | #include <fcntl.h> /* values for fchmodat */
|
@@ -1338,6 +1339,19 @@ bool __remove(const path& p, error_code* ec) {
|
1338 | 1339 | return true;
|
1339 | 1340 | }
|
1340 | 1341 |
|
| 1342 | +// We currently have two implementations of `__remove_all`. The first one is general and |
| 1343 | +// used on platforms where we don't have access to the `openat()` family of POSIX functions. |
| 1344 | +// That implementation uses `directory_iterator`, however it is vulnerable to some race |
| 1345 | +// conditions, see https://reviews.llvm.org/D118134 for details. |
| 1346 | +// |
| 1347 | +// The second implementation is used on platforms where `openat()` & friends are available, |
| 1348 | +// and it threads file descriptors through recursive calls to avoid such race conditions. |
| 1349 | +#if defined(_LIBCPP_WIN32API) |
| 1350 | +# define REMOVE_ALL_USE_DIRECTORY_ITERATOR |
| 1351 | +#endif |
| 1352 | + |
| 1353 | +#if defined(REMOVE_ALL_USE_DIRECTORY_ITERATOR) |
| 1354 | + |
1341 | 1355 | namespace {
|
1342 | 1356 |
|
1343 | 1357 | uintmax_t remove_all_impl(path const& p, error_code& ec) {
|
@@ -1377,6 +1391,97 @@ uintmax_t __remove_all(const path& p, error_code* ec) {
|
1377 | 1391 | return count;
|
1378 | 1392 | }
|
1379 | 1393 |
|
| 1394 | +#else // !REMOVE_ALL_USE_DIRECTORY_ITERATOR |
| 1395 | + |
| 1396 | +namespace { |
| 1397 | + |
| 1398 | +template <class Cleanup> |
| 1399 | +struct scope_exit { |
| 1400 | + explicit scope_exit(Cleanup const& cleanup) |
| 1401 | + : cleanup_(cleanup) |
| 1402 | + { } |
| 1403 | + |
| 1404 | + ~scope_exit() { cleanup_(); } |
| 1405 | + |
| 1406 | +private: |
| 1407 | + Cleanup cleanup_; |
| 1408 | +}; |
| 1409 | + |
| 1410 | +uintmax_t remove_all_impl(int parent_directory, const path& p, error_code& ec) { |
| 1411 | + // First, try to open the path as a directory. |
| 1412 | + const int options = O_CLOEXEC | O_RDONLY | O_DIRECTORY | O_NOFOLLOW; |
| 1413 | + int fd = ::openat(parent_directory, p.c_str(), options); |
| 1414 | + if (fd != -1) { |
| 1415 | + // If that worked, iterate over the contents of the directory and |
| 1416 | + // remove everything in it, recursively. |
| 1417 | + scope_exit close_fd([=] { ::close(fd); }); |
| 1418 | + DIR* stream = ::fdopendir(fd); |
| 1419 | + if (stream == nullptr) { |
| 1420 | + ec = detail::capture_errno(); |
| 1421 | + return 0; |
| 1422 | + } |
| 1423 | + scope_exit close_stream([=] { ::closedir(stream); }); |
| 1424 | + |
| 1425 | + uintmax_t count = 0; |
| 1426 | + while (true) { |
| 1427 | + auto [str, type] = detail::posix_readdir(stream, ec); |
| 1428 | + static_assert(std::is_same_v<decltype(str), std::string_view>); |
| 1429 | + if (str == "." || str == "..") { |
| 1430 | + continue; |
| 1431 | + } else if (ec || str.empty()) { |
| 1432 | + break; // we're done iterating through the directory |
| 1433 | + } else { |
| 1434 | + count += remove_all_impl(fd, str, ec); |
| 1435 | + } |
| 1436 | + } |
| 1437 | + |
| 1438 | + // Then, remove the now-empty directory itself. |
| 1439 | + if (::unlinkat(parent_directory, p.c_str(), AT_REMOVEDIR) == -1) { |
| 1440 | + ec = detail::capture_errno(); |
| 1441 | + return count; |
| 1442 | + } |
| 1443 | + |
| 1444 | + return count + 1; // the contents of the directory + the directory itself |
| 1445 | + } |
| 1446 | + |
| 1447 | + ec = detail::capture_errno(); |
| 1448 | + |
| 1449 | + // If we failed to open `p` because it didn't exist, it's not an |
| 1450 | + // error -- it might have moved or have been deleted already. |
| 1451 | + if (ec == errc::no_such_file_or_directory) { |
| 1452 | + ec.clear(); |
| 1453 | + return 0; |
| 1454 | + } |
| 1455 | + |
| 1456 | + // If opening `p` failed because it wasn't a directory, remove it as |
| 1457 | + // a normal file instead. Note that `openat()` can return either ENOTDIR |
| 1458 | + // or ELOOP depending on the exact reason of the failure. |
| 1459 | + if (ec == errc::not_a_directory || ec == errc::too_many_symbolic_link_levels) { |
| 1460 | + ec.clear(); |
| 1461 | + if (::unlinkat(parent_directory, p.c_str(), /* flags = */0) == -1) { |
| 1462 | + ec = detail::capture_errno(); |
| 1463 | + return 0; |
| 1464 | + } |
| 1465 | + return 1; |
| 1466 | + } |
| 1467 | + |
| 1468 | + // Otherwise, it's a real error -- we don't remove anything. |
| 1469 | + return 0; |
| 1470 | +} |
| 1471 | + |
| 1472 | +} // end namespace |
| 1473 | + |
| 1474 | +uintmax_t __remove_all(const path& p, error_code* ec) { |
| 1475 | + ErrorHandler<uintmax_t> err("remove_all", ec, &p); |
| 1476 | + error_code mec; |
| 1477 | + uintmax_t count = remove_all_impl(AT_FDCWD, p, mec); |
| 1478 | + if (mec) |
| 1479 | + return err.report(mec); |
| 1480 | + return count; |
| 1481 | +} |
| 1482 | + |
| 1483 | +#endif // REMOVE_ALL_USE_DIRECTORY_ITERATOR |
| 1484 | + |
1380 | 1485 | void __rename(const path& from, const path& to, error_code* ec) {
|
1381 | 1486 | ErrorHandler<void> err("rename", ec, &from, &to);
|
1382 | 1487 | if (detail::rename(from.c_str(), to.c_str()) == -1)
|
|
0 commit comments