@@ -17,7 +17,7 @@ use std::os::unix::ffi::OsStrExt;
1717#[ cfg( unix) ]
1818use std:: os:: unix:: fs:: PermissionsExt ;
1919use std:: path:: MAIN_SEPARATOR ;
20- use std:: path:: { Path , PathBuf } ;
20+ use std:: path:: Path ;
2121use thiserror:: Error ;
2222use uucore:: display:: Quotable ;
2323use uucore:: error:: { FromIo , UError , UResult } ;
@@ -56,7 +56,7 @@ fn verbose_removed_file(path: &Path, options: &Options) {
5656 if options. verbose {
5757 println ! (
5858 "{}" ,
59- translate!( "rm-verbose-removed" , "file" => normalize ( path) . quote( ) )
59+ translate!( "rm-verbose-removed" , "file" => uucore :: fs :: normalize_path ( path) . quote( ) )
6060 ) ;
6161 }
6262}
@@ -66,7 +66,7 @@ fn verbose_removed_directory(path: &Path, options: &Options) {
6666 if options. verbose {
6767 println ! (
6868 "{}" ,
69- translate!( "rm-verbose-removed-directory" , "file" => normalize ( path) . quote( ) )
69+ translate!( "rm-verbose-removed-directory" , "file" => uucore :: fs :: normalize_path ( path) . quote( ) )
7070 ) ;
7171 }
7272}
@@ -229,6 +229,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
229229 } )
230230 } ;
231231
232+ let preserve_root = !matches. get_flag ( OPT_NO_PRESERVE_ROOT ) ;
233+ let recursive = matches. get_flag ( OPT_RECURSIVE ) ;
234+
232235 let options = Options {
233236 force : force_flag,
234237 interactive : {
@@ -245,8 +248,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
245248 }
246249 } ,
247250 one_fs : matches. get_flag ( OPT_ONE_FILE_SYSTEM ) ,
248- preserve_root : !matches . get_flag ( OPT_NO_PRESERVE_ROOT ) ,
249- recursive : matches . get_flag ( OPT_RECURSIVE ) ,
251+ preserve_root,
252+ recursive,
250253 dir : matches. get_flag ( OPT_DIR ) ,
251254 verbose : matches. get_flag ( OPT_VERBOSE ) ,
252255 progress : matches. get_flag ( OPT_PROGRESS ) ,
@@ -487,6 +490,19 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool {
487490 for filename in files {
488491 let file = Path :: new ( filename) ;
489492
493+ // Check if the path (potentially with trailing slash) resolves to root
494+ // This needs to happen before symlink_metadata to catch cases like "rootlink/"
495+ // where rootlink is a symlink to root.
496+ if uucore:: fs:: path_ends_with_terminator ( file)
497+ && options. recursive
498+ && options. preserve_root
499+ && is_root_path ( file)
500+ {
501+ show_preserve_root_error ( file) ;
502+ had_err = true ;
503+ continue ;
504+ }
505+
490506 had_err = match file. symlink_metadata ( ) {
491507 Ok ( metadata) => {
492508 // Create progress bar on first successful file metadata read
@@ -673,6 +689,39 @@ fn remove_dir_recursive(
673689 }
674690}
675691
692+ /// Check if a path resolves to the root directory.
693+ /// Returns true if the path is root, false otherwise.
694+ fn is_root_path ( path : & Path ) -> bool {
695+ // Check simple case: literal "/" path
696+ if path. has_root ( ) && path. parent ( ) . is_none ( ) {
697+ return true ;
698+ }
699+
700+ // Check if path resolves to "/" after following symlinks
701+ if let Ok ( canonical) = path. canonicalize ( ) {
702+ canonical. has_root ( ) && canonical. parent ( ) . is_none ( )
703+ } else {
704+ false
705+ }
706+ }
707+
708+ /// Show error message for attempting to remove root.
709+ fn show_preserve_root_error ( path : & Path ) {
710+ let path_looks_like_root = path. has_root ( ) && path. parent ( ) . is_none ( ) ;
711+
712+ if path_looks_like_root {
713+ // Path is literally "/"
714+ show_error ! ( "{}" , RmError :: DangerousRecursiveOperation ) ;
715+ } else {
716+ // Path resolves to root but isn't literally "/" (e.g., symlink to /)
717+ show_error ! (
718+ "it is dangerous to operate recursively on '{}' (same as '/')" ,
719+ path. display( )
720+ ) ;
721+ }
722+ show_error ! ( "{}" , RmError :: UseNoPreserveRoot ) ;
723+ }
724+
676725fn handle_dir ( path : & Path , options : & Options , progress_bar : Option < & ProgressBar > ) -> bool {
677726 let mut had_err = false ;
678727
@@ -685,14 +734,13 @@ fn handle_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar>
685734 return true ;
686735 }
687736
688- let is_root = path . has_root ( ) && path. parent ( ) . is_none ( ) ;
737+ let is_root = is_root_path ( path) ;
689738 if options. recursive && ( !is_root || !options. preserve_root ) {
690739 had_err = remove_dir_recursive ( path, options, progress_bar) ;
691740 } else if options. dir && ( !is_root || !options. preserve_root ) {
692741 had_err = remove_dir ( path, options, progress_bar) . bitor ( had_err) ;
693742 } else if options. recursive {
694- show_error ! ( "{}" , RmError :: DangerousRecursiveOperation ) ;
695- show_error ! ( "{}" , RmError :: UseNoPreserveRoot ) ;
743+ show_preserve_root_error ( path) ;
696744 had_err = true ;
697745 } else {
698746 show_error ! (
@@ -940,14 +988,6 @@ fn prompt_descend(path: &Path) -> bool {
940988 prompt_yes ! ( "descend into directory {}?" , path. quote( ) )
941989}
942990
943- fn normalize ( path : & Path ) -> PathBuf {
944- // copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90
945- // both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT
946- // for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208
947- // TODO: replace this once that lands
948- uucore:: fs:: normalize_path ( path)
949- }
950-
951991#[ cfg( not( windows) ) ]
952992fn is_symlink_dir ( _metadata : & Metadata ) -> bool {
953993 false
0 commit comments