@@ -395,6 +395,7 @@ fn is_readable_metadata(metadata: &Metadata) -> bool {
395395
396396/// Whether the given file or directory is readable.
397397#[ cfg( unix) ]
398+ #[ cfg( not( target_os = "linux" ) ) ]
398399fn is_readable ( path : & Path ) -> bool {
399400 match fs:: metadata ( path) {
400401 Err ( _) => false ,
@@ -436,11 +437,29 @@ fn safe_remove_dir_recursive(path: &Path, options: &Options) -> bool {
436437 let dir_fd = match DirFd :: open ( path) {
437438 Ok ( fd) => fd,
438439 Err ( e) => {
439- show_error ! (
440- "{}" ,
441- e. map_err_context( || translate!( "rm-error-cannot-remove" , "file" => path. quote( ) ) )
442- ) ;
443- return true ;
440+ // If we can't open the directory for safe traversal, try removing it as empty directory
441+ // This handles the case where it's an empty directory with no read permissions
442+ match fs:: remove_dir ( path) {
443+ Ok ( _) => {
444+ if options. verbose {
445+ println ! (
446+ "{}" ,
447+ translate!( "rm-verbose-removed-directory" , "file" => normalize( path) . quote( ) )
448+ ) ;
449+ }
450+ return false ;
451+ }
452+ Err ( _) => {
453+ // If we can't remove it as empty dir either, report the original open error
454+ show_error ! (
455+ "{}" ,
456+ e. map_err_context(
457+ || translate!( "rm-error-cannot-remove" , "file" => path. quote( ) )
458+ )
459+ ) ;
460+ return true ;
461+ }
462+ }
444463 }
445464 } ;
446465
@@ -457,28 +476,35 @@ fn safe_remove_dir_recursive(path: &Path, options: &Options) -> bool {
457476
458477 // Use regular fs::remove_dir for the root since we can't unlinkat ourselves
459478 match fs:: remove_dir ( path) {
460- Ok ( _) => false ,
461- Err ( e) => {
479+ Ok ( _) => {
480+ if options. verbose {
481+ println ! (
482+ "{}" ,
483+ translate!( "rm-verbose-removed-directory" , "file" => normalize( path) . quote( ) )
484+ ) ;
485+ }
486+ false
487+ }
488+ Err ( e) if !error => {
462489 let e = e. map_err_context (
463490 || translate ! ( "rm-error-cannot-remove" , "file" => path. quote( ) ) ,
464491 ) ;
465492 show_error ! ( "{e}" ) ;
466493 true
467494 }
495+ Err ( _) => {
496+ // If there has already been at least one error when
497+ // trying to remove the children, then there is no need to
498+ // show another error message as we return from each level
499+ // of the recursion.
500+ error
501+ }
468502 }
469503 }
470504}
471505
472506#[ cfg( target_os = "linux" ) ]
473507fn safe_remove_dir_recursive_impl ( path : & Path , dir_fd : & DirFd , options : & Options ) -> bool {
474- // Check if we should descend into this directory
475- if options. interactive == InteractiveMode :: Always
476- && !is_dir_empty ( path)
477- && !prompt_descend ( path)
478- {
479- return false ;
480- }
481-
482508 // Read directory entries using safe traversal
483509 let entries = match dir_fd. read_dir ( ) {
484510 Ok ( entries) => entries,
@@ -518,35 +544,9 @@ fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options
518544 let is_dir = ( entry_stat. st_mode & libc:: S_IFMT ) == libc:: S_IFDIR ;
519545
520546 if is_dir {
521- // Recursively remove directory
522- let subdir_fd = match dir_fd. open_subdir ( & entry_name) {
523- Ok ( fd) => fd,
524- Err ( e) => {
525- let e = e. map_err_context (
526- || translate ! ( "rm-error-cannot-remove" , "file" => entry_path. quote( ) ) ,
527- ) ;
528- show_error ! ( "{e}" ) ;
529- error = true ;
530- continue ;
531- }
532- } ;
533-
534- let child_error = safe_remove_dir_recursive_impl ( & entry_path, & subdir_fd, options) ;
547+ // Recursively remove subdirectory - handle in the style of the non-Linux version
548+ let child_error = remove_dir_recursive ( & entry_path, options) ;
535549 error = error || child_error;
536-
537- // Try to remove the directory (even if there were some child errors)
538- // Ask user permission if needed
539- if options. interactive == InteractiveMode :: Always && !prompt_dir ( & entry_path, options) {
540- continue ;
541- }
542-
543- if let Err ( e) = dir_fd. unlink_at ( & entry_name, true ) {
544- let e = e. map_err_context (
545- || translate ! ( "rm-error-cannot-remove" , "file" => entry_path. quote( ) ) ,
546- ) ;
547- show_error ! ( "{e}" ) ;
548- error = true ;
549- }
550550 } else {
551551 // Remove file - check if user wants to remove it first
552552 if prompt_file ( & entry_path, options) {
@@ -556,6 +556,11 @@ fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options
556556 ) ;
557557 show_error ! ( "{e}" ) ;
558558 error = true ;
559+ } else if options. verbose {
560+ println ! (
561+ "{}" ,
562+ translate!( "rm-verbose-removed" , "file" => normalize( & entry_path) . quote( ) )
563+ ) ;
559564 }
560565 }
561566 }
@@ -590,17 +595,13 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
590595 return false ;
591596 }
592597
593- // Use secure traversal on Linux for long paths
598+ // Use secure traversal on Linux for all recursive directory removals
594599 #[ cfg( target_os = "linux" ) ]
595600 {
596- if let Some ( s) = path. to_str ( ) {
597- if s. len ( ) > 1000 {
598- return safe_remove_dir_recursive ( path, options) ;
599- }
600- }
601+ safe_remove_dir_recursive ( path, options)
601602 }
602603
603- // Fallback for non-Linux or shorter paths
604+ // Fallback for non-Linux or use fs::remove_dir_all for very long paths
604605 #[ cfg( not( target_os = "linux" ) ) ]
605606 {
606607 if let Some ( s) = path. to_str ( ) {
@@ -617,62 +618,63 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
617618 }
618619 }
619620 }
620- }
621621
622- // Recursive case: this is a directory.
623- let mut error = false ;
624- match fs:: read_dir ( path) {
625- Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: PermissionDenied => {
626- // This is not considered an error.
627- }
628- Err ( _) => error = true ,
629- Ok ( iter) => {
630- for entry in iter {
631- match entry {
632- Err ( _) => error = true ,
633- Ok ( entry) => {
634- let child_error = remove_dir_recursive ( & entry. path ( ) , options) ;
635- error = error || child_error;
622+ // Recursive case: this is a directory.
623+ let mut error = false ;
624+ match fs:: read_dir ( path) {
625+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: PermissionDenied => {
626+ // This is not considered an error.
627+ }
628+ Err ( _) => error = true ,
629+ Ok ( iter) => {
630+ for entry in iter {
631+ match entry {
632+ Err ( _) => error = true ,
633+ Ok ( entry) => {
634+ let child_error = remove_dir_recursive ( & entry. path ( ) , options) ;
635+ error = error || child_error;
636+ }
636637 }
637638 }
638639 }
639640 }
640- }
641641
642- // Ask the user whether to remove the current directory.
643- if options. interactive == InteractiveMode :: Always && !prompt_dir ( path, options) {
644- return false ;
645- }
646-
647- // Try removing the directory itself.
648- match fs:: remove_dir ( path) {
649- Err ( _) if !error && !is_readable ( path) => {
650- // For compatibility with GNU test case
651- // `tests/rm/unread2.sh`, show "Permission denied" in this
652- // case instead of "Directory not empty".
653- show_error ! ( "cannot remove {}: Permission denied" , path. quote( ) ) ;
654- error = true ;
655- }
656- Err ( e) if !error => {
657- let e =
658- e. map_err_context ( || translate ! ( "rm-error-cannot-remove" , "file" => path. quote( ) ) ) ;
659- show_error ! ( "{e}" ) ;
660- error = true ;
642+ // Ask the user whether to remove the current directory.
643+ if options. interactive == InteractiveMode :: Always && !prompt_dir ( path, options) {
644+ return false ;
661645 }
662- Err ( _) => {
663- // If there has already been at least one error when
664- // trying to remove the children, then there is no need to
665- // show another error message as we return from each level
666- // of the recursion.
646+
647+ // Try removing the directory itself.
648+ match fs:: remove_dir ( path) {
649+ Err ( _) if !error && !is_readable ( path) => {
650+ // For compatibility with GNU test case
651+ // `tests/rm/unread2.sh`, show "Permission denied" in this
652+ // case instead of "Directory not empty".
653+ show_error ! ( "cannot remove {}: Permission denied" , path. quote( ) ) ;
654+ error = true ;
655+ }
656+ Err ( e) if !error => {
657+ let e = e. map_err_context (
658+ || translate ! ( "rm-error-cannot-remove" , "file" => path. quote( ) ) ,
659+ ) ;
660+ show_error ! ( "{e}" ) ;
661+ error = true ;
662+ }
663+ Err ( _) => {
664+ // If there has already been at least one error when
665+ // trying to remove the children, then there is no need to
666+ // show another error message as we return from each level
667+ // of the recursion.
668+ }
669+ Ok ( _) if options. verbose => println ! (
670+ "{}" ,
671+ translate!( "rm-verbose-removed-directory" , "file" => normalize( path) . quote( ) )
672+ ) ,
673+ Ok ( _) => { }
667674 }
668- Ok ( _) if options. verbose => println ! (
669- "{}" ,
670- translate!( "rm-verbose-removed-directory" , "file" => normalize( path) . quote( ) )
671- ) ,
672- Ok ( _) => { }
673- }
674675
675- error
676+ error
677+ }
676678}
677679
678680fn handle_dir ( path : & Path , options : & Options ) -> bool {
0 commit comments