@@ -1672,55 +1672,90 @@ mod remove_dir_impl {
1672
1672
}
1673
1673
}
1674
1674
1675
- fn remove_dir_all_recursive ( parent_fd : Option < RawFd > , p : & Path ) -> io:: Result < ( ) > {
1676
- let pcstr = cstr ( p) ?;
1677
-
1678
- // entry is expected to be a directory, open as such
1679
- let fd = openat_nofollow_dironly ( parent_fd, & pcstr) ?;
1680
-
1681
- // open the directory passing ownership of the fd
1682
- let ( dir, fd) = fdreaddir ( fd) ?;
1683
- for child in dir {
1684
- let child = child?;
1685
- match is_dir ( & child) {
1686
- Some ( true ) => {
1687
- remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1688
- }
1689
- Some ( false ) => {
1690
- cvt ( unsafe { unlinkat ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) ?;
1691
- }
1692
- None => match cvt ( unsafe { unlinkat ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) {
1675
+ fn unlink_direntry ( ent : & DirEntry , parent_fd : RawFd ) -> io:: Result < bool > {
1676
+ match is_dir ( & ent) {
1677
+ Some ( true ) => Ok ( false ) ,
1678
+ Some ( false ) => {
1679
+ cvt ( unsafe { unlinkat ( parent_fd, ent. name_cstr ( ) . as_ptr ( ) , 0 ) } ) ?;
1680
+ Ok ( true )
1681
+ }
1682
+ None => {
1683
+ match cvt ( unsafe { unlinkat ( parent_fd, ent. name_cstr ( ) . as_ptr ( ) , 0 ) } ) {
1693
1684
// type unknown - try to unlink
1694
1685
Err ( err)
1695
1686
if err. raw_os_error ( ) == Some ( libc:: EISDIR )
1696
1687
|| err. raw_os_error ( ) == Some ( libc:: EPERM ) =>
1697
1688
{
1698
- // if the file is a directory unlink fails with EISDIR on Linux and EPERM everyhwere else
1699
- remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1700
- }
1701
- result => {
1702
- result?;
1689
+ // if the file is a directory unlink fails with EISDIR on Linux
1690
+ // and EPERM everyhwere else
1691
+ Ok ( false )
1703
1692
}
1704
- } ,
1693
+ result => result. map ( |_| true ) ,
1694
+ }
1705
1695
}
1706
1696
}
1697
+ }
1707
1698
1708
- // unlink the directory after removing its contents
1709
- cvt ( unsafe {
1710
- unlinkat ( parent_fd. unwrap_or ( libc:: AT_FDCWD ) , pcstr. as_ptr ( ) , libc:: AT_REMOVEDIR )
1711
- } ) ?;
1712
- Ok ( ( ) )
1699
+ fn remove_dir_all_loop ( p : & Path ) -> io:: Result < ( ) > {
1700
+ use crate :: ffi:: CString ;
1701
+
1702
+ struct State {
1703
+ dir : ReadDir ,
1704
+ fd : RawFd ,
1705
+ parent_fd : Option < RawFd > ,
1706
+ pcstr : CString ,
1707
+ }
1708
+
1709
+ impl State {
1710
+ fn new ( parent_fd : Option < RawFd > , pcstr : CString ) -> io:: Result < Self > {
1711
+ // entry is expected to be a directory, open as such
1712
+ let fd = openat_nofollow_dironly ( parent_fd, & pcstr) ?;
1713
+
1714
+ // open the directory passing ownership of the fd
1715
+ let ( dir, fd) = fdreaddir ( fd) ?;
1716
+
1717
+ Ok ( Self { dir, fd, parent_fd, pcstr } )
1718
+ }
1719
+ }
1720
+
1721
+ let mut parents = Vec :: < State > :: new ( ) ;
1722
+ let mut current = State :: new ( None , cstr ( p) ?) ?;
1723
+ loop {
1724
+ while let Some ( child) = current. dir . next ( ) {
1725
+ let child = child?;
1726
+ if !unlink_direntry ( & child, current. fd ) ? {
1727
+ // Descend into this child directory
1728
+ let parent = current;
1729
+ current = State :: new ( Some ( parent. fd ) , child. name_cstr ( ) . into ( ) ) ?;
1730
+ parents. push ( parent) ;
1731
+ }
1732
+ }
1733
+
1734
+ // unlink the directory after removing its contents
1735
+ cvt ( unsafe {
1736
+ unlinkat (
1737
+ current. parent_fd . unwrap_or ( libc:: AT_FDCWD ) ,
1738
+ current. pcstr . as_ptr ( ) ,
1739
+ libc:: AT_REMOVEDIR ,
1740
+ )
1741
+ } ) ?;
1742
+
1743
+ match parents. pop ( ) {
1744
+ Some ( parent) => current = parent,
1745
+ None => return Ok ( ( ) ) ,
1746
+ }
1747
+ }
1713
1748
}
1714
1749
1715
1750
pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1716
- // We cannot just call remove_dir_all_recursive () here because that would not delete a passed
1717
- // symlink. No need to worry about races, because remove_dir_all_recursive () does not recurse
1751
+ // We cannot just call remove_dir_all_loop () here because that would not delete a passed
1752
+ // symlink. No need to worry about races, because remove_dir_all_loop () does not descend
1718
1753
// into symlinks.
1719
1754
let attr = lstat ( p) ?;
1720
1755
if attr. file_type ( ) . is_symlink ( ) {
1721
1756
crate :: fs:: remove_file ( p)
1722
1757
} else {
1723
- remove_dir_all_recursive ( None , p)
1758
+ remove_dir_all_loop ( p)
1724
1759
}
1725
1760
}
1726
1761
}
0 commit comments