Skip to content

Commit 9dc0098

Browse files
committed
Make UNIX remove_dir_all() looping, use I/O safety
move remove_dir_all implementation to new dir_fd module
1 parent 5fb8a39 commit 9dc0098

File tree

3 files changed

+421
-222
lines changed

3 files changed

+421
-222
lines changed

library/std/src/sys/unix/fs.rs

Lines changed: 30 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ use libc::{
7575
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))]
7676
use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
7777

78+
#[cfg(not(any(target_os = "redox", target_os = "espidf")))]
79+
mod dir_fd;
80+
81+
// Modern implementation using openat(), unlinkat() and fdopendir()
82+
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))]
83+
pub use dir_fd::remove_dir_all;
84+
85+
// Fallback for REDOX, ESP-IDF, Horizon and Miri
86+
#[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))]
87+
pub use crate::sys_common::fs::remove_dir_all;
88+
7889
pub use crate::sys_common::fs::try_exists;
7990

8091
pub struct File(FileDesc);
@@ -525,6 +536,24 @@ impl FromInner<u32> for FilePermissions {
525536
}
526537
}
527538

539+
impl ReadDir {
540+
fn new(dirp: Dir, root: PathBuf) -> Self {
541+
let inner = InnerReadDir { dirp, root };
542+
ReadDir {
543+
inner: Arc::new(inner),
544+
#[cfg(not(any(
545+
target_os = "android",
546+
target_os = "linux",
547+
target_os = "solaris",
548+
target_os = "illumos",
549+
target_os = "fuchsia",
550+
target_os = "redox",
551+
)))]
552+
end_of_stream: false,
553+
}
554+
}
555+
}
556+
528557
impl fmt::Debug for ReadDir {
529558
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530559
// This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
@@ -1179,23 +1208,7 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
11791208
let p = cstr(p)?;
11801209
unsafe {
11811210
let ptr = libc::opendir(p.as_ptr());
1182-
if ptr.is_null() {
1183-
Err(Error::last_os_error())
1184-
} else {
1185-
let inner = InnerReadDir { dirp: Dir(ptr), root };
1186-
Ok(ReadDir {
1187-
inner: Arc::new(inner),
1188-
#[cfg(not(any(
1189-
target_os = "android",
1190-
target_os = "linux",
1191-
target_os = "solaris",
1192-
target_os = "illumos",
1193-
target_os = "fuchsia",
1194-
target_os = "redox",
1195-
)))]
1196-
end_of_stream: false,
1197-
})
1198-
}
1211+
if ptr.is_null() { Err(Error::last_os_error()) } else { Ok(ReadDir::new(Dir(ptr), root)) }
11991212
}
12001213
}
12011214

@@ -1557,208 +1570,3 @@ pub fn chroot(dir: &Path) -> io::Result<()> {
15571570
cvt(unsafe { libc::chroot(dir.as_ptr()) })?;
15581571
Ok(())
15591572
}
1560-
1561-
pub use remove_dir_impl::remove_dir_all;
1562-
1563-
// Fallback for REDOX, ESP-ID, Horizon, and Miri
1564-
#[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))]
1565-
mod remove_dir_impl {
1566-
pub use crate::sys_common::fs::remove_dir_all;
1567-
}
1568-
1569-
// Modern implementation using openat(), unlinkat() and fdopendir()
1570-
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))]
1571-
mod remove_dir_impl {
1572-
use super::{cstr, lstat, Dir, DirEntry, InnerReadDir, ReadDir};
1573-
use crate::ffi::CStr;
1574-
use crate::io;
1575-
use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
1576-
use crate::os::unix::prelude::{OwnedFd, RawFd};
1577-
use crate::path::{Path, PathBuf};
1578-
use crate::sync::Arc;
1579-
use crate::sys::{cvt, cvt_r};
1580-
1581-
#[cfg(not(all(target_os = "macos", not(target_arch = "aarch64")),))]
1582-
use libc::{fdopendir, openat, unlinkat};
1583-
#[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1584-
use macos_weak::{fdopendir, openat, unlinkat};
1585-
1586-
#[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1587-
mod macos_weak {
1588-
use crate::sys::weak::weak;
1589-
use libc::{c_char, c_int, DIR};
1590-
1591-
fn get_openat_fn() -> Option<unsafe extern "C" fn(c_int, *const c_char, c_int) -> c_int> {
1592-
weak!(fn openat(c_int, *const c_char, c_int) -> c_int);
1593-
openat.get()
1594-
}
1595-
1596-
pub fn has_openat() -> bool {
1597-
get_openat_fn().is_some()
1598-
}
1599-
1600-
pub unsafe fn openat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
1601-
get_openat_fn().map(|openat| openat(dirfd, pathname, flags)).unwrap_or_else(|| {
1602-
crate::sys::unix::os::set_errno(libc::ENOSYS);
1603-
-1
1604-
})
1605-
}
1606-
1607-
pub unsafe fn fdopendir(fd: c_int) -> *mut DIR {
1608-
#[cfg(all(target_os = "macos", target_arch = "x86"))]
1609-
weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64$UNIX2003");
1610-
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
1611-
weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64");
1612-
fdopendir.get().map(|fdopendir| fdopendir(fd)).unwrap_or_else(|| {
1613-
crate::sys::unix::os::set_errno(libc::ENOSYS);
1614-
crate::ptr::null_mut()
1615-
})
1616-
}
1617-
1618-
pub unsafe fn unlinkat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
1619-
weak!(fn unlinkat(c_int, *const c_char, c_int) -> c_int);
1620-
unlinkat.get().map(|unlinkat| unlinkat(dirfd, pathname, flags)).unwrap_or_else(|| {
1621-
crate::sys::unix::os::set_errno(libc::ENOSYS);
1622-
-1
1623-
})
1624-
}
1625-
}
1626-
1627-
pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> {
1628-
let fd = cvt_r(|| unsafe {
1629-
openat(
1630-
parent_fd.unwrap_or(libc::AT_FDCWD),
1631-
p.as_ptr(),
1632-
libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY,
1633-
)
1634-
})?;
1635-
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
1636-
}
1637-
1638-
fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> {
1639-
let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) };
1640-
if ptr.is_null() {
1641-
return Err(io::Error::last_os_error());
1642-
}
1643-
let dirp = Dir(ptr);
1644-
// file descriptor is automatically closed by libc::closedir() now, so give up ownership
1645-
let new_parent_fd = dir_fd.into_raw_fd();
1646-
// a valid root is not needed because we do not call any functions involving the full path
1647-
// of the DirEntrys.
1648-
let dummy_root = PathBuf::new();
1649-
Ok((
1650-
ReadDir {
1651-
inner: Arc::new(InnerReadDir { dirp, root: dummy_root }),
1652-
#[cfg(not(any(
1653-
target_os = "android",
1654-
target_os = "linux",
1655-
target_os = "solaris",
1656-
target_os = "illumos",
1657-
target_os = "fuchsia",
1658-
target_os = "redox",
1659-
)))]
1660-
end_of_stream: false,
1661-
},
1662-
new_parent_fd,
1663-
))
1664-
}
1665-
1666-
#[cfg(any(
1667-
target_os = "solaris",
1668-
target_os = "illumos",
1669-
target_os = "haiku",
1670-
target_os = "vxworks",
1671-
))]
1672-
fn is_dir(_ent: &DirEntry) -> Option<bool> {
1673-
None
1674-
}
1675-
1676-
#[cfg(not(any(
1677-
target_os = "solaris",
1678-
target_os = "illumos",
1679-
target_os = "haiku",
1680-
target_os = "vxworks",
1681-
)))]
1682-
fn is_dir(ent: &DirEntry) -> Option<bool> {
1683-
match ent.entry.d_type {
1684-
libc::DT_UNKNOWN => None,
1685-
libc::DT_DIR => Some(true),
1686-
_ => Some(false),
1687-
}
1688-
}
1689-
1690-
fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> {
1691-
// try opening as directory
1692-
let fd = match openat_nofollow_dironly(parent_fd, &path) {
1693-
Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => {
1694-
// not a directory - don't traverse further
1695-
// (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR)
1696-
return match parent_fd {
1697-
// unlink...
1698-
Some(parent_fd) => {
1699-
cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop)
1700-
}
1701-
// ...unless this was supposed to be the deletion root directory
1702-
None => Err(err),
1703-
};
1704-
}
1705-
result => result?,
1706-
};
1707-
1708-
// open the directory passing ownership of the fd
1709-
let (dir, fd) = fdreaddir(fd)?;
1710-
for child in dir {
1711-
let child = child?;
1712-
let child_name = child.name_cstr();
1713-
match is_dir(&child) {
1714-
Some(true) => {
1715-
remove_dir_all_recursive(Some(fd), child_name)?;
1716-
}
1717-
Some(false) => {
1718-
cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?;
1719-
}
1720-
None => {
1721-
// POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
1722-
// if the process has the appropriate privileges. This however can causing orphaned
1723-
// directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
1724-
// into it first instead of trying to unlink() it.
1725-
remove_dir_all_recursive(Some(fd), child_name)?;
1726-
}
1727-
}
1728-
}
1729-
1730-
// unlink the directory after removing its contents
1731-
cvt(unsafe {
1732-
unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR)
1733-
})?;
1734-
Ok(())
1735-
}
1736-
1737-
fn remove_dir_all_modern(p: &Path) -> io::Result<()> {
1738-
// We cannot just call remove_dir_all_recursive() here because that would not delete a passed
1739-
// symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
1740-
// into symlinks.
1741-
let attr = lstat(p)?;
1742-
if attr.file_type().is_symlink() {
1743-
crate::fs::remove_file(p)
1744-
} else {
1745-
remove_dir_all_recursive(None, &cstr(p)?)
1746-
}
1747-
}
1748-
1749-
#[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))]
1750-
pub fn remove_dir_all(p: &Path) -> io::Result<()> {
1751-
remove_dir_all_modern(p)
1752-
}
1753-
1754-
#[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1755-
pub fn remove_dir_all(p: &Path) -> io::Result<()> {
1756-
if macos_weak::has_openat() {
1757-
// openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
1758-
remove_dir_all_modern(p)
1759-
} else {
1760-
// fall back to classic implementation
1761-
crate::sys_common::fs::remove_dir_all(p)
1762-
}
1763-
}
1764-
}

0 commit comments

Comments
 (0)