@@ -1478,13 +1478,15 @@ mod remove_dir_impl {
14781478#[ cfg( not( any( target_os = "redox" , target_os = "espidf" ) ) ) ]
14791479mod remove_dir_impl {
14801480 use super :: { cstr, lstat, Dir , DirEntry , InnerReadDir , ReadDir } ;
1481- use crate :: ffi:: CStr ;
1481+ use crate :: ffi:: { CStr , CString } ;
14821482 use crate :: io;
1483+ use crate :: mem;
14831484 use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , IntoRawFd } ;
14841485 use crate :: os:: unix:: prelude:: { OwnedFd , RawFd } ;
14851486 use crate :: path:: { Path , PathBuf } ;
14861487 use crate :: sync:: Arc ;
14871488 use crate :: sys:: { cvt, cvt_r} ;
1489+ use alloc:: collections:: VecDeque ;
14881490
14891491 #[ cfg( not( all( target_os = "macos" , target_arch = "x86_64" ) , ) ) ]
14901492 use libc:: { fdopendir, openat, unlinkat} ;
@@ -1518,6 +1520,23 @@ mod remove_dir_impl {
15181520 #[ cfg( all( target_os = "macos" , target_arch = "x86_64" ) ) ]
15191521 use macos_weak:: { fdopendir, openat, unlinkat} ;
15201522
1523+ #[ cfg( not( any(
1524+ target_os = "linux" ,
1525+ target_os = "emscripten" ,
1526+ target_os = "l4re" ,
1527+ target_os = "android"
1528+ ) ) ) ]
1529+ use libc:: fstat as fstat64;
1530+ #[ cfg( any(
1531+ target_os = "linux" ,
1532+ target_os = "emscripten" ,
1533+ target_os = "l4re" ,
1534+ target_os = "android"
1535+ ) ) ]
1536+ use libc:: fstat64;
1537+
1538+ const MAX_OPEN_FDS : usize = 32 ;
1539+
15211540 pub fn openat_nofollow_dironly ( parent_fd : Option < RawFd > , p : & CStr ) -> io:: Result < OwnedFd > {
15221541 let fd = cvt_r ( || unsafe {
15231542 openat (
@@ -1605,55 +1624,120 @@ mod remove_dir_impl {
16051624 }
16061625 }
16071626
1608- fn remove_dir_all_loop ( p : & Path ) -> io:: Result < ( ) > {
1609- use crate :: ffi:: CString ;
1627+ struct CachedReadDir {
1628+ readdir : ReadDir ,
1629+ raw_fd : RawFd ,
1630+ }
16101631
1611- struct State {
1612- dir : ReadDir ,
1613- fd : RawFd ,
1614- parent_fd : Option < RawFd > ,
1615- pcstr : CString ,
1616- }
1632+ struct DirComponent {
1633+ name : CString ,
1634+ ino : u64 ,
1635+ }
16171636
1618- impl State {
1619- fn new ( parent_fd : Option < RawFd > , pcstr : CString ) -> io:: Result < Self > {
1620- // entry is expected to be a directory, open as such
1621- let fd = openat_nofollow_dironly ( parent_fd, & pcstr) ?;
1637+ fn readdir_open_path ( path : & CStr ) -> io:: Result < ( DirComponent , CachedReadDir ) > {
1638+ let dir_fd = openat_nofollow_dironly ( None , path) ?;
16221639
1623- // open the directory passing ownership of the fd
1624- let ( dir, fd) = fdreaddir ( fd) ?;
1640+ // use fstat() to get the inode of the directory
1641+ let mut stat = unsafe { mem:: zeroed ( ) } ;
1642+ cvt ( unsafe { fstat64 ( dir_fd. as_raw_fd ( ) , & mut stat) } ) ?;
1643+ let ( readdir, raw_fd) = fdreaddir ( dir_fd) ?;
1644+ Ok ( (
1645+ DirComponent { name : CString :: new ( "" ) ?, ino : stat. st_ino } ,
1646+ CachedReadDir { readdir, raw_fd } ,
1647+ ) )
1648+ }
16251649
1626- Ok ( Self { dir, fd, parent_fd, pcstr } )
1627- }
1628- }
1650+ fn readdir_open_child (
1651+ readdir : & CachedReadDir ,
1652+ child : & DirEntry ,
1653+ ) -> io:: Result < ( DirComponent , CachedReadDir ) > {
1654+ let dir_fd = openat_nofollow_dironly ( Some ( readdir. raw_fd ) , child. name_cstr ( ) ) ?;
1655+ let ( readdir, raw_fd) = fdreaddir ( dir_fd) ?;
1656+ Ok ( (
1657+ DirComponent { name : child. name_cstr ( ) . into ( ) , ino : child. entry . d_ino } ,
1658+ CachedReadDir { readdir, raw_fd } ,
1659+ ) )
1660+ }
16291661
1630- let mut parents = Vec :: < State > :: new ( ) ;
1631- let mut current = State :: new ( None , cstr ( p) ?) ?;
1662+ fn readdir_reopen_parent (
1663+ dir : & CachedReadDir ,
1664+ expected_parent_dir : & DirComponent ,
1665+ ) -> io:: Result < CachedReadDir > {
1666+ let parent_dir_fd = openat_nofollow_dironly ( Some ( dir. raw_fd ) , unsafe {
1667+ CStr :: from_bytes_with_nul_unchecked ( b"..\0 " )
1668+ } ) ?;
1669+ let mut stat = unsafe { mem:: zeroed ( ) } ;
1670+ cvt ( unsafe { fstat64 ( parent_dir_fd. as_raw_fd ( ) , & mut stat) } ) ?;
1671+ // Make sure that the reopened parent directory has the same inode as when we visited it descending
1672+ // the directory tree. More detailed risk analysis TBD.
1673+ if expected_parent_dir. ino != stat. st_ino {
1674+ return Err ( io:: Error :: new (
1675+ io:: ErrorKind :: Uncategorized ,
1676+ "parent directory inode does not match" ,
1677+ ) ) ;
1678+ }
1679+ let ( readdir, raw_fd) = fdreaddir ( parent_dir_fd) ?;
1680+ Ok ( CachedReadDir { readdir, raw_fd } )
1681+ }
1682+
1683+ fn remove_dir_all_loop ( root : & Path ) -> io:: Result < ( ) > {
1684+ // all ancestor names and inodes from the deletion root directory to the parent of the currently processed
1685+ // directory
1686+ let mut parent_dir_components = Vec :: new ( ) ;
1687+ // cache of up to MAX_OPEN_FDS ancestor ReadDirs and associated file descriptors
1688+ let mut readdir_cache = VecDeque :: with_capacity ( MAX_OPEN_FDS ) ;
1689+ // the directory name, inode pair and ReadDir currently being processed
1690+ let ( mut current_dir_component, mut current_readdir) = readdir_open_path ( & cstr ( root) ?) ?;
16321691 loop {
1633- while let Some ( child) = current . dir . next ( ) {
1692+ while let Some ( child) = current_readdir . readdir . next ( ) {
16341693 let child = child?;
1635- if !unlink_direntry ( & child, current . fd ) ? {
1694+ if !unlink_direntry ( & child, current_readdir . raw_fd ) ? {
16361695 // Descend into this child directory
1637- let parent = current;
1638- current = State :: new ( Some ( parent. fd ) , child. name_cstr ( ) . into ( ) ) ?;
1639- parents. push ( parent) ;
1696+
1697+ let ( child_dir_compoment, child_readdir) =
1698+ readdir_open_child ( & current_readdir, & child) ?;
1699+ parent_dir_components. push ( current_dir_component) ;
1700+
1701+ // avoid growing the cache over capacity
1702+ if readdir_cache. len ( ) == readdir_cache. capacity ( ) {
1703+ readdir_cache. pop_front ( ) ;
1704+ }
1705+ readdir_cache. push_back ( current_readdir) ;
1706+
1707+ current_readdir = child_readdir;
1708+ current_dir_component = child_dir_compoment;
16401709 }
16411710 }
16421711
1643- // unlink the directory after removing its contents
1644- cvt ( unsafe {
1645- unlinkat (
1646- current. parent_fd . unwrap_or ( libc:: AT_FDCWD ) ,
1647- current. pcstr . as_ptr ( ) ,
1648- libc:: AT_REMOVEDIR ,
1649- )
1650- } ) ?;
1651-
1652- match parents. pop ( ) {
1653- Some ( parent) => current = parent,
1654- None => return Ok ( ( ) ) ,
1712+ match parent_dir_components. pop ( ) {
1713+ Some ( parent) => {
1714+ // Going back up...
1715+ let parent_readdir = match readdir_cache. pop_back ( ) {
1716+ Some ( readdir) => readdir,
1717+ None => {
1718+ // not cached -> reopen
1719+ readdir_reopen_parent ( & current_readdir, & parent) ?
1720+ }
1721+ } ;
1722+ // unlink the directory after having removed its contents
1723+ cvt ( unsafe {
1724+ unlinkat (
1725+ parent_readdir. raw_fd ,
1726+ current_dir_component. name . as_ptr ( ) ,
1727+ libc:: AT_REMOVEDIR ,
1728+ )
1729+ } ) ?;
1730+
1731+ current_dir_component = parent;
1732+ current_readdir = parent_readdir;
1733+ }
1734+ None => break ,
16551735 }
16561736 }
1737+
1738+ // unlink root dir
1739+ cvt ( unsafe { unlinkat ( libc:: AT_FDCWD , cstr ( root) ?. as_ptr ( ) , libc:: AT_REMOVEDIR ) } ) ?;
1740+ Ok ( ( ) )
16571741 }
16581742
16591743 fn remove_dir_all_modern ( p : & Path ) -> io:: Result < ( ) > {
0 commit comments