77
88use clap:: builder:: { PossibleValue , ValueParser } ;
99use clap:: { Arg , ArgAction , Command , parser:: ValueSource } ;
10+ use indicatif:: { ProgressBar , ProgressStyle } ;
1011use std:: ffi:: { OsStr , OsString } ;
1112use std:: fs:: { self , Metadata } ;
12- use std:: io:: { IsTerminal , stdin} ;
13+ use std:: io:: { self , IsTerminal , stdin} ;
1314use std:: ops:: BitOr ;
1415#[ cfg( unix) ]
1516use std:: os:: unix:: ffi:: OsStrExt ;
@@ -108,6 +109,8 @@ pub struct Options {
108109 pub dir : bool ,
109110 /// `-v`, `--verbose`
110111 pub verbose : bool ,
112+ /// `-g`, `--progress`
113+ pub progress : bool ,
111114 #[ doc( hidden) ]
112115 /// `---presume-input-tty`
113116 /// Always use `None`; GNU flag for testing use only
@@ -124,6 +127,7 @@ impl Default for Options {
124127 recursive : false ,
125128 dir : false ,
126129 verbose : false ,
130+ progress : false ,
127131 __presume_input_tty : None ,
128132 }
129133 }
@@ -139,6 +143,7 @@ static OPT_PROMPT_ALWAYS: &str = "prompt-always";
139143static OPT_PROMPT_ONCE : & str = "prompt-once" ;
140144static OPT_RECURSIVE : & str = "recursive" ;
141145static OPT_VERBOSE : & str = "verbose" ;
146+ static OPT_PROGRESS : & str = "progress" ;
142147static PRESUME_INPUT_TTY : & str = "-presume-input-tty" ;
143148
144149static ARG_FILES : & str = "files" ;
@@ -191,6 +196,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
191196 recursive : matches. get_flag ( OPT_RECURSIVE ) ,
192197 dir : matches. get_flag ( OPT_DIR ) ,
193198 verbose : matches. get_flag ( OPT_VERBOSE ) ,
199+ progress : matches. get_flag ( OPT_PROGRESS ) ,
194200 __presume_input_tty : if matches. get_flag ( PRESUME_INPUT_TTY ) {
195201 Some ( true )
196202 } else {
@@ -309,6 +315,13 @@ pub fn uu_app() -> Command {
309315 . help ( translate ! ( "rm-help-verbose" ) )
310316 . action ( ArgAction :: SetTrue ) ,
311317 )
318+ . arg (
319+ Arg :: new ( OPT_PROGRESS )
320+ . short ( 'g' )
321+ . long ( OPT_PROGRESS )
322+ . help ( translate ! ( "rm-help-progress" ) )
323+ . action ( ArgAction :: SetTrue ) ,
324+ )
312325 // From the GNU source code:
313326 // This is solely for testing.
314327 // Do not document.
@@ -333,6 +346,63 @@ pub fn uu_app() -> Command {
333346 )
334347}
335348
349+ /// Count the total number of files and directories to be deleted.
350+ /// This function recursively counts all files and directories that will be processed.
351+ /// Files are not deduplicated when appearing in multiple sources. If `recursive` is set to `false`, the
352+ /// directories in `paths` will be ignored.
353+ fn count_files ( paths : & [ & OsStr ] , recursive : bool ) -> io:: Result < u64 > {
354+ let mut total = 0 ;
355+ for p in paths {
356+ let path = Path :: new ( p) ;
357+ match fs:: symlink_metadata ( path) {
358+ Ok ( md) => {
359+ if md. is_dir ( ) && !is_symlink_dir ( & md) {
360+ if recursive {
361+ total += count_files_in_directory ( path) ?;
362+ }
363+ } else {
364+ total += 1 ; // Count this file/symlink
365+ }
366+ }
367+ Err ( _) => {
368+ // If we can't access the file, skip it for counting
369+ // This matches the behavior where -f suppresses errors for missing files
370+ }
371+ }
372+ }
373+ Ok ( total)
374+ }
375+
376+ /// A helper for `count_files` specialized for directories.
377+ fn count_files_in_directory ( p : & Path ) -> io:: Result < u64 > {
378+ let mut total = 1 ; // Count the directory itself
379+
380+ match fs:: read_dir ( p) {
381+ Ok ( entries) => {
382+ for entry in entries. flatten ( ) {
383+ let path = entry. path ( ) ;
384+ match entry. file_type ( ) {
385+ Ok ( file_type) => {
386+ if file_type. is_dir ( ) {
387+ total += count_files_in_directory ( & path) ?;
388+ } else {
389+ total += 1 ; // Count this file
390+ }
391+ }
392+ Err ( _) => {
393+ // Skip files we can't access
394+ }
395+ }
396+ }
397+ }
398+ Err ( _) => {
399+ // Skip directories we can't read
400+ }
401+ }
402+
403+ Ok ( total)
404+ }
405+
336406// TODO: implement one-file-system (this may get partially implemented in walkdir)
337407/// Remove (or unlink) the given files
338408///
@@ -343,17 +413,35 @@ pub fn uu_app() -> Command {
343413pub fn remove ( files : & [ & OsStr ] , options : & Options ) -> bool {
344414 let mut had_err = false ;
345415
416+ // Create progress bar if requested
417+ let progress_bar = if options. progress {
418+ count_files ( files, options. recursive )
419+ . ok ( )
420+ . map ( |total_files| {
421+ ProgressBar :: new ( total_files)
422+ . with_style (
423+ ProgressStyle :: with_template (
424+ "{msg}: [{elapsed_precise}] {wide_bar} {pos:>7}/{len:7} files" ,
425+ )
426+ . unwrap ( ) ,
427+ )
428+ . with_message ( translate ! ( "rm-progress-removing" ) )
429+ } )
430+ } else {
431+ None
432+ } ;
433+
346434 for filename in files {
347435 let file = Path :: new ( filename) ;
348436
349437 had_err = match file. symlink_metadata ( ) {
350438 Ok ( metadata) => {
351439 if metadata. is_dir ( ) {
352- handle_dir ( file, options)
440+ handle_dir ( file, options, progress_bar . as_ref ( ) )
353441 } else if is_symlink_dir ( & metadata) {
354- remove_dir ( file, options)
442+ remove_dir ( file, options, progress_bar . as_ref ( ) )
355443 } else {
356- remove_file ( file, options)
444+ remove_file ( file, options, progress_bar . as_ref ( ) )
357445 }
358446 }
359447
@@ -376,6 +464,10 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool {
376464 . bitor ( had_err) ;
377465 }
378466
467+ if let Some ( pb) = progress_bar {
468+ pb. finish ( ) ;
469+ }
470+
379471 had_err
380472}
381473
@@ -482,7 +574,7 @@ fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options
482574 // Read directory entries using safe traversal
483575 let entries = match dir_fd. read_dir ( ) {
484576 Ok ( entries) => entries,
485- Err ( e) if e. kind ( ) == std :: io:: ErrorKind :: PermissionDenied => {
577+ Err ( e) if e. kind ( ) == io:: ErrorKind :: PermissionDenied => {
486578 // This is not considered an error - just like the original
487579 return false ;
488580 }
@@ -570,15 +662,19 @@ fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options
570662/// directory, remove all of its entries recursively and then remove the
571663/// directory itself. In case of an error, print the error message to
572664/// `stderr` and return `true`. If there were no errors, return `false`.
573- fn remove_dir_recursive ( path : & Path , options : & Options ) -> bool {
665+ fn remove_dir_recursive (
666+ path : & Path ,
667+ options : & Options ,
668+ progress_bar : Option < & ProgressBar > ,
669+ ) -> bool {
574670 // Base case 1: this is a file or a symbolic link.
575671 //
576672 // The symbolic link case is important because it could be a link to
577673 // a directory and we don't want to recurse. In particular, this
578674 // avoids an infinite recursion in the case of a link to the current
579675 // directory, like `ln -s . link`.
580676 if !path. is_dir ( ) || path. is_symlink ( ) {
581- return remove_file ( path, options) ;
677+ return remove_file ( path, options, progress_bar ) ;
582678 }
583679
584680 // Base case 2: this is a non-empty directory, but the user
@@ -622,7 +718,7 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
622718 // Recursive case: this is a directory.
623719 let mut error = false ;
624720 match fs:: read_dir ( path) {
625- Err ( e) if e. kind ( ) == std :: io:: ErrorKind :: PermissionDenied => {
721+ Err ( e) if e. kind ( ) == io:: ErrorKind :: PermissionDenied => {
626722 // This is not considered an error.
627723 }
628724 Err ( _) => error = true ,
@@ -631,7 +727,8 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
631727 match entry {
632728 Err ( _) => error = true ,
633729 Ok ( entry) => {
634- let child_error = remove_dir_recursive ( & entry. path ( ) , options) ;
730+ let child_error =
731+ remove_dir_recursive ( & entry. path ( ) , options, progress_bar) ;
635732 error = error || child_error;
636733 }
637734 }
@@ -675,7 +772,7 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
675772 error
676773}
677774
678- fn handle_dir ( path : & Path , options : & Options ) -> bool {
775+ fn handle_dir ( path : & Path , options : & Options , progress_bar : Option < & ProgressBar > ) -> bool {
679776 let mut had_err = false ;
680777
681778 let path = clean_trailing_slashes ( path) ;
@@ -689,9 +786,9 @@ fn handle_dir(path: &Path, options: &Options) -> bool {
689786
690787 let is_root = path. has_root ( ) && path. parent ( ) . is_none ( ) ;
691788 if options. recursive && ( !is_root || !options. preserve_root ) {
692- had_err = remove_dir_recursive ( path, options) ;
789+ had_err = remove_dir_recursive ( path, options, progress_bar ) ;
693790 } else if options. dir && ( !is_root || !options. preserve_root ) {
694- had_err = remove_dir ( path, options) . bitor ( had_err) ;
791+ had_err = remove_dir ( path, options, progress_bar ) . bitor ( had_err) ;
695792 } else if options. recursive {
696793 show_error ! ( "{}" , RmError :: DangerousRecursiveOperation ) ;
697794 show_error ! ( "{}" , RmError :: UseNoPreserveRoot ) ;
@@ -710,7 +807,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool {
710807/// Remove the given directory, asking the user for permission if necessary.
711808///
712809/// Returns true if it has encountered an error.
713- fn remove_dir ( path : & Path , options : & Options ) -> bool {
810+ fn remove_dir ( path : & Path , options : & Options , progress_bar : Option < & ProgressBar > ) -> bool {
714811 // Ask the user for permission.
715812 if !prompt_dir ( path, options) {
716813 return false ;
@@ -726,6 +823,11 @@ fn remove_dir(path: &Path, options: &Options) -> bool {
726823 }
727824
728825 // Try to remove the directory.
826+ // Update progress bar for directory removal
827+ if let Some ( pb) = progress_bar {
828+ pb. inc ( 1 ) ;
829+ }
830+
729831 match fs:: remove_dir ( path) {
730832 Ok ( _) => {
731833 if options. verbose {
@@ -745,8 +847,13 @@ fn remove_dir(path: &Path, options: &Options) -> bool {
745847 }
746848}
747849
748- fn remove_file ( path : & Path , options : & Options ) -> bool {
850+ fn remove_file ( path : & Path , options : & Options , progress_bar : Option < & ProgressBar > ) -> bool {
749851 if prompt_file ( path, options) {
852+ // Update progress bar before removing the file
853+ if let Some ( pb) = progress_bar {
854+ pb. inc ( 1 ) ;
855+ }
856+
750857 match fs:: remove_file ( path) {
751858 Ok ( _) => {
752859 if options. verbose {
@@ -757,7 +864,7 @@ fn remove_file(path: &Path, options: &Options) -> bool {
757864 }
758865 }
759866 Err ( e) => {
760- if e. kind ( ) == std :: io:: ErrorKind :: PermissionDenied {
867+ if e. kind ( ) == io:: ErrorKind :: PermissionDenied {
761868 // GNU compatibility (rm/fail-eacces.sh)
762869 show_error ! (
763870 "{}" ,
0 commit comments