@@ -1482,140 +1482,60 @@ mod remove_dir_impl {
14821482 pub use crate :: sys_common:: fs:: remove_dir_all;
14831483}
14841484
1485- // Dynamically choose implementation Macos x86-64: modern for 10.10+, fallback for older versions
1486- #[ cfg( all ( target_os = "macos " , target_arch = "x86_64" ) ) ]
1485+ // Modern implementation using openat(), unlinkat() and fdopendir()
1486+ #[ cfg( not ( any ( target_os = "redox " , target_os = "espidf" ) ) ) ]
14871487mod remove_dir_impl {
1488- use super :: { cstr, lstat, Dir , InnerReadDir , ReadDir } ;
1488+ use super :: { cstr, lstat, Dir , DirEntry , InnerReadDir , ReadDir } ;
14891489 use crate :: ffi:: CStr ;
14901490 use crate :: io;
14911491 use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , IntoRawFd } ;
14921492 use crate :: os:: unix:: prelude:: { OwnedFd , RawFd } ;
14931493 use crate :: path:: { Path , PathBuf } ;
14941494 use crate :: sync:: Arc ;
1495- use crate :: sys:: weak:: weak;
14961495 use crate :: sys:: { cvt, cvt_r} ;
1497- use libc:: { c_char, c_int, DIR } ;
1498-
1499- pub fn openat_nofollow_dironly ( parent_fd : Option < RawFd > , p : & CStr ) -> io:: Result < OwnedFd > {
1500- weak ! ( fn openat( c_int, * const c_char, c_int) -> c_int) ;
1501- let fd = cvt_r ( || unsafe {
1502- openat. get ( ) . unwrap ( ) (
1503- parent_fd. unwrap_or ( libc:: AT_FDCWD ) ,
1504- p. as_ptr ( ) ,
1505- libc:: O_CLOEXEC | libc:: O_RDONLY | libc:: O_NOFOLLOW | libc:: O_DIRECTORY ,
1506- )
1507- } ) ?;
1508- Ok ( unsafe { OwnedFd :: from_raw_fd ( fd) } )
1509- }
15101496
1511- fn fdreaddir ( dir_fd : OwnedFd ) -> io:: Result < ( ReadDir , RawFd ) > {
1512- weak ! ( fn fdopendir( c_int) -> * mut DIR , "fdopendir$INODE64" ) ;
1513- let ptr = unsafe { fdopendir. get ( ) . unwrap ( ) ( dir_fd. as_raw_fd ( ) ) } ;
1514- if ptr. is_null ( ) {
1515- return Err ( io:: Error :: last_os_error ( ) ) ;
1516- }
1517- let dirp = Dir ( ptr) ;
1518- // file descriptor is automatically closed by libc::closedir() now, so give up ownership
1519- let new_parent_fd = dir_fd. into_raw_fd ( ) ;
1520- // a valid root is not needed because we do not call any functions involving the full path
1521- // of the DirEntrys.
1522- let dummy_root = PathBuf :: new ( ) ;
1523- Ok ( (
1524- ReadDir {
1525- inner : Arc :: new ( InnerReadDir { dirp, root : dummy_root } ) ,
1526- end_of_stream : false ,
1527- } ,
1528- new_parent_fd,
1529- ) )
1530- }
1531-
1532- fn remove_dir_all_recursive ( parent_fd : Option < RawFd > , p : & Path ) -> io:: Result < ( ) > {
1533- weak ! ( fn unlinkat( c_int, * const c_char, c_int) -> c_int) ;
1497+ #[ cfg( not( all( target_os = "macos" , target_arch = "x86_64" ) , ) ) ]
1498+ use libc:: { fdopendir, openat, unlinkat} ;
1499+ #[ cfg( all( target_os = "macos" , target_arch = "x86_64" ) ) ]
1500+ use macos_weak:: { fdopendir, openat, unlinkat} ;
15341501
1535- let pcstr = cstr ( p) ?;
1502+ #[ cfg( all( target_os = "macos" , target_arch = "x86_64" ) ) ]
1503+ mod macos_weak {
1504+ use crate :: sys:: weak:: weak;
1505+ use libc:: { c_char, c_int, DIR } ;
15361506
1537- // entry is expected to be a directory, open as such
1538- let fd = openat_nofollow_dironly ( parent_fd, & pcstr) ?;
1507+ fn get_openat_fn ( ) -> Option < unsafe extern "C" fn ( c_int , * const c_char , c_int ) -> c_int > {
1508+ weak ! ( fn openat( c_int, * const c_char, c_int) -> c_int) ;
1509+ openat. get ( )
1510+ }
15391511
1540- // open the directory passing ownership of the fd
1541- let ( dir, fd) = fdreaddir ( fd) ?;
1542- for child in dir {
1543- let child = child?;
1544- match child. entry . d_type {
1545- libc:: DT_DIR => {
1546- remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1547- }
1548- libc:: DT_UNKNOWN => {
1549- match cvt ( unsafe { unlinkat. get ( ) . unwrap ( ) ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } )
1550- {
1551- // type unknown - try to unlink
1552- Err ( err) if err. raw_os_error ( ) == Some ( libc:: EPERM ) => {
1553- // if the file is a directory unlink fails with EPERM
1554- remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1555- }
1556- result => {
1557- result?;
1558- }
1559- }
1560- }
1561- _ => {
1562- // not a directory -> unlink
1563- cvt ( unsafe { unlinkat. get ( ) . unwrap ( ) ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) ?;
1564- }
1565- }
1512+ pub fn has_openat ( ) -> bool {
1513+ get_openat_fn ( ) . is_some ( )
15661514 }
15671515
1568- // unlink the directory after removing its contents
1569- cvt ( unsafe {
1570- unlinkat. get ( ) . unwrap ( ) (
1571- parent_fd. unwrap_or ( libc:: AT_FDCWD ) ,
1572- pcstr. as_ptr ( ) ,
1573- libc:: AT_REMOVEDIR ,
1574- )
1575- } ) ?;
1576- Ok ( ( ) )
1577- }
1516+ pub unsafe fn openat ( dirfd : c_int , pathname : * const c_char , flags : c_int ) -> c_int {
1517+ get_openat_fn ( ) . map ( |openat| openat ( dirfd, pathname, flags) ) . unwrap_or_else ( || {
1518+ crate :: sys:: unix:: os:: set_errno ( libc:: ENOSYS ) ;
1519+ -1
1520+ } )
1521+ }
15781522
1579- fn remove_dir_all_modern ( p : & Path ) -> io:: Result < ( ) > {
1580- // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
1581- // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
1582- // into symlinks.
1583- let attr = lstat ( p) ?;
1584- if attr. file_type ( ) . is_symlink ( ) {
1585- crate :: fs:: remove_file ( p)
1586- } else {
1587- remove_dir_all_recursive ( None , p)
1523+ pub unsafe fn fdopendir ( fd : c_int ) -> * mut DIR {
1524+ weak ! ( fn fdopendir( c_int) -> * mut DIR , "fdopendir$INODE64" ) ;
1525+ fdopendir. get ( ) . map ( |fdopendir| fdopendir ( fd) ) . unwrap_or_else ( || {
1526+ crate :: sys:: unix:: os:: set_errno ( libc:: ENOSYS ) ;
1527+ crate :: ptr:: null_mut ( )
1528+ } )
15881529 }
1589- }
15901530
1591- pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1592- weak ! ( fn openat( c_int, * const c_char, c_int) -> c_int) ;
1593- if openat. get ( ) . is_some ( ) {
1594- // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
1595- remove_dir_all_modern ( p)
1596- } else {
1597- // fall back to classic implementation
1598- crate :: sys_common:: fs:: remove_dir_all ( p)
1531+ pub unsafe fn unlinkat ( dirfd : c_int , pathname : * const c_char , flags : c_int ) -> c_int {
1532+ weak ! ( fn unlinkat( c_int, * const c_char, c_int) -> c_int) ;
1533+ unlinkat. get ( ) . map ( |unlinkat| unlinkat ( dirfd, pathname, flags) ) . unwrap_or_else ( || {
1534+ crate :: sys:: unix:: os:: set_errno ( libc:: ENOSYS ) ;
1535+ -1
1536+ } )
15991537 }
16001538 }
1601- }
1602-
1603- // Modern implementation using openat(), unlinkat() and fdopendir()
1604- #[ cfg( not( any(
1605- all( target_os = "macos" , target_arch = "x86_64" ) ,
1606- target_os = "redox" ,
1607- target_os = "espidf"
1608- ) ) ) ]
1609- mod remove_dir_impl {
1610- use super :: { cstr, lstat, Dir , DirEntry , InnerReadDir , ReadDir } ;
1611- use crate :: ffi:: CStr ;
1612- use crate :: io;
1613- use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , IntoRawFd } ;
1614- use crate :: os:: unix:: prelude:: { OwnedFd , RawFd } ;
1615- use crate :: path:: { Path , PathBuf } ;
1616- use crate :: sync:: Arc ;
1617- use crate :: sys:: { cvt, cvt_r} ;
1618- use libc:: { fdopendir, openat, unlinkat} ;
16191539
16201540 pub fn openat_nofollow_dironly ( parent_fd : Option < RawFd > , p : & CStr ) -> io:: Result < OwnedFd > {
16211541 let fd = cvt_r ( || unsafe {
@@ -1683,8 +1603,21 @@ mod remove_dir_impl {
16831603 fn remove_dir_all_recursive ( parent_fd : Option < RawFd > , p : & Path ) -> io:: Result < ( ) > {
16841604 let pcstr = cstr ( p) ?;
16851605
1686- // entry is expected to be a directory, open as such
1687- let fd = openat_nofollow_dironly ( parent_fd, & pcstr) ?;
1606+ // try opening as directory
1607+ let fd = match openat_nofollow_dironly ( parent_fd, & pcstr) {
1608+ Err ( err) if err. raw_os_error ( ) == Some ( libc:: ENOTDIR ) => {
1609+ // not a directory - don't traverse further
1610+ return match parent_fd {
1611+ // unlink...
1612+ Some ( parent_fd) => {
1613+ cvt ( unsafe { unlinkat ( parent_fd, pcstr. as_ptr ( ) , 0 ) } ) . map ( drop)
1614+ }
1615+ // ...unless this was supposed to be the deletion root directory
1616+ None => Err ( err) ,
1617+ } ;
1618+ }
1619+ result => result?,
1620+ } ;
16881621
16891622 // open the directory passing ownership of the fd
16901623 let ( dir, fd) = fdreaddir ( fd) ?;
@@ -1697,19 +1630,13 @@ mod remove_dir_impl {
16971630 Some ( false ) => {
16981631 cvt ( unsafe { unlinkat ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) ?;
16991632 }
1700- None => match cvt ( unsafe { unlinkat ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) {
1701- // type unknown - try to unlink
1702- Err ( err)
1703- if err. raw_os_error ( ) == Some ( libc:: EISDIR )
1704- || err. raw_os_error ( ) == Some ( libc:: EPERM ) =>
1705- {
1706- // if the file is a directory unlink fails with EISDIR on Linux and EPERM everyhwere else
1707- remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1708- }
1709- result => {
1710- result?;
1711- }
1712- } ,
1633+ None => {
1634+ // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
1635+ // if the process has the appropriate privileges. This however can causing orphaned
1636+ // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
1637+ // into it first instead of trying to unlink() it.
1638+ remove_dir_all_recursive ( Some ( fd) , Path :: new ( & child. file_name ( ) ) ) ?;
1639+ }
17131640 }
17141641 }
17151642
@@ -1720,7 +1647,7 @@ mod remove_dir_impl {
17201647 Ok ( ( ) )
17211648 }
17221649
1723- pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1650+ fn remove_dir_all_modern ( p : & Path ) -> io:: Result < ( ) > {
17241651 // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
17251652 // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
17261653 // into symlinks.
@@ -1731,4 +1658,20 @@ mod remove_dir_impl {
17311658 remove_dir_all_recursive ( None , p)
17321659 }
17331660 }
1661+
1662+ #[ cfg( not( all( target_os = "macos" , target_arch = "x86_64" ) ) ) ]
1663+ pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1664+ remove_dir_all_modern ( p)
1665+ }
1666+
1667+ #[ cfg( all( target_os = "macos" , target_arch = "x86_64" ) ) ]
1668+ pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1669+ if macos_weak:: has_openat ( ) {
1670+ // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
1671+ remove_dir_all_modern ( p)
1672+ } else {
1673+ // fall back to classic implementation
1674+ crate :: sys_common:: fs:: remove_dir_all ( p)
1675+ }
1676+ }
17341677}
0 commit comments