@@ -1478,13 +1478,15 @@ mod remove_dir_impl {
1478
1478
#[ cfg( not( any( target_os = "redox" , target_os = "espidf" ) ) ) ]
1479
1479
mod remove_dir_impl {
1480
1480
use super :: { cstr, lstat, Dir , DirEntry , InnerReadDir , ReadDir } ;
1481
- use crate :: ffi:: CStr ;
1481
+ use crate :: ffi:: { CStr , CString } ;
1482
1482
use crate :: io;
1483
+ use crate :: mem;
1483
1484
use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , IntoRawFd } ;
1484
1485
use crate :: os:: unix:: prelude:: { OwnedFd , RawFd } ;
1485
1486
use crate :: path:: { Path , PathBuf } ;
1486
1487
use crate :: sync:: Arc ;
1487
1488
use crate :: sys:: { cvt, cvt_r} ;
1489
+ use alloc:: collections:: VecDeque ;
1488
1490
1489
1491
#[ cfg( not( all( target_os = "macos" , target_arch = "x86_64" ) , ) ) ]
1490
1492
use libc:: { fdopendir, openat, unlinkat} ;
@@ -1518,6 +1520,23 @@ mod remove_dir_impl {
1518
1520
#[ cfg( all( target_os = "macos" , target_arch = "x86_64" ) ) ]
1519
1521
use macos_weak:: { fdopendir, openat, unlinkat} ;
1520
1522
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
+
1521
1540
pub fn openat_nofollow_dironly ( parent_fd : Option < RawFd > , p : & CStr ) -> io:: Result < OwnedFd > {
1522
1541
let fd = cvt_r ( || unsafe {
1523
1542
openat (
@@ -1605,55 +1624,120 @@ mod remove_dir_impl {
1605
1624
}
1606
1625
}
1607
1626
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
+ }
1610
1631
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
+ }
1617
1636
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) ?;
1622
1639
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
+ }
1625
1649
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
+ }
1629
1661
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) ?) ?;
1632
1691
loop {
1633
- while let Some ( child) = current . dir . next ( ) {
1692
+ while let Some ( child) = current_readdir . readdir . next ( ) {
1634
1693
let child = child?;
1635
- if !unlink_direntry ( & child, current . fd ) ? {
1694
+ if !unlink_direntry ( & child, current_readdir . raw_fd ) ? {
1636
1695
// 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;
1640
1709
}
1641
1710
}
1642
1711
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 ,
1655
1735
}
1656
1736
}
1737
+
1738
+ // unlink root dir
1739
+ cvt ( unsafe { unlinkat ( libc:: AT_FDCWD , cstr ( root) ?. as_ptr ( ) , libc:: AT_REMOVEDIR ) } ) ?;
1740
+ Ok ( ( ) )
1657
1741
}
1658
1742
1659
1743
fn remove_dir_all_modern ( p : & Path ) -> io:: Result < ( ) > {
0 commit comments