@@ -1482,140 +1482,60 @@ mod remove_dir_impl {
1482
1482
pub use crate :: sys_common:: fs:: remove_dir_all;
1483
1483
}
1484
1484
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" ) ) ) ]
1487
1487
mod remove_dir_impl {
1488
- use super :: { cstr, lstat, Dir , InnerReadDir , ReadDir } ;
1488
+ use super :: { cstr, lstat, Dir , DirEntry , InnerReadDir , ReadDir } ;
1489
1489
use crate :: ffi:: CStr ;
1490
1490
use crate :: io;
1491
1491
use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , IntoRawFd } ;
1492
1492
use crate :: os:: unix:: prelude:: { OwnedFd , RawFd } ;
1493
1493
use crate :: path:: { Path , PathBuf } ;
1494
1494
use crate :: sync:: Arc ;
1495
- use crate :: sys:: weak:: weak;
1496
1495
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
- }
1510
1496
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} ;
1534
1501
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 } ;
1536
1506
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
+ }
1539
1511
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 ( )
1566
1514
}
1567
1515
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
+ }
1578
1522
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
+ } )
1588
1529
}
1589
- }
1590
1530
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
+ } )
1599
1537
}
1600
1538
}
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} ;
1619
1539
1620
1540
pub fn openat_nofollow_dironly ( parent_fd : Option < RawFd > , p : & CStr ) -> io:: Result < OwnedFd > {
1621
1541
let fd = cvt_r ( || unsafe {
@@ -1683,8 +1603,21 @@ mod remove_dir_impl {
1683
1603
fn remove_dir_all_recursive ( parent_fd : Option < RawFd > , p : & Path ) -> io:: Result < ( ) > {
1684
1604
let pcstr = cstr ( p) ?;
1685
1605
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
+ } ;
1688
1621
1689
1622
// open the directory passing ownership of the fd
1690
1623
let ( dir, fd) = fdreaddir ( fd) ?;
@@ -1697,19 +1630,13 @@ mod remove_dir_impl {
1697
1630
Some ( false ) => {
1698
1631
cvt ( unsafe { unlinkat ( fd, child. name_cstr ( ) . as_ptr ( ) , 0 ) } ) ?;
1699
1632
}
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
+ }
1713
1640
}
1714
1641
}
1715
1642
@@ -1720,7 +1647,7 @@ mod remove_dir_impl {
1720
1647
Ok ( ( ) )
1721
1648
}
1722
1649
1723
- pub fn remove_dir_all ( p : & Path ) -> io:: Result < ( ) > {
1650
+ fn remove_dir_all_modern ( p : & Path ) -> io:: Result < ( ) > {
1724
1651
// We cannot just call remove_dir_all_recursive() here because that would not delete a passed
1725
1652
// symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
1726
1653
// into symlinks.
@@ -1731,4 +1658,20 @@ mod remove_dir_impl {
1731
1658
remove_dir_all_recursive ( None , p)
1732
1659
}
1733
1660
}
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
+ }
1734
1677
}
0 commit comments