77
88use clap:: { Arg , ArgAction , Command } ;
99use std:: ffi:: OsString ;
10- use std:: fs;
1110use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
12- use std:: path:: { Path , PathBuf } ;
11+ use std:: path:: Path ;
12+ use std:: { fs, io} ;
1313use thiserror:: Error ;
1414use uucore:: display:: Quotable ;
1515use uucore:: error:: { ExitCode , UError , UResult , USimpleError , UUsageError , set_exit_code} ;
@@ -27,17 +27,17 @@ use uucore::translate;
2727#[ derive( Debug , Error ) ]
2828enum ChmodError {
2929 #[ error( "{}" , translate!( "chmod-error-cannot-stat" , "file" => _0. quote( ) ) ) ]
30- CannotStat ( PathBuf ) ,
30+ CannotStat ( String ) ,
3131 #[ error( "{}" , translate!( "chmod-error-dangling-symlink" , "file" => _0. quote( ) ) ) ]
32- DanglingSymlink ( PathBuf ) ,
32+ DanglingSymlink ( String ) ,
3333 #[ error( "{}" , translate!( "chmod-error-no-such-file" , "file" => _0. quote( ) ) ) ]
34- NoSuchFile ( PathBuf ) ,
34+ NoSuchFile ( String ) ,
3535 #[ error( "{}" , translate!( "chmod-error-preserve-root" , "file" => _0. quote( ) ) ) ]
36- PreserveRoot ( PathBuf ) ,
36+ PreserveRoot ( String ) ,
3737 #[ error( "{}" , translate!( "chmod-error-permission-denied" , "file" => _0. quote( ) ) ) ]
38- PermissionDenied ( PathBuf ) ,
39- #[ error( "{}" , translate!( "chmod-error-new-permissions" , "file" => _0. maybe_quote ( ) , "actual" => _1. clone( ) , "expected" => _2. clone( ) ) ) ]
40- NewPermissions ( PathBuf , String , String ) ,
38+ PermissionDenied ( String ) ,
39+ #[ error( "{}" , translate!( "chmod-error-new-permissions" , "file" => _0. clone ( ) , "actual" => _1. clone( ) , "expected" => _2. clone( ) ) ) ]
40+ NewPermissions ( String , String , String ) ,
4141}
4242
4343impl UError for ChmodError { }
@@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
123123 Some ( fref) => match fs:: metadata ( fref) {
124124 Ok ( meta) => Some ( meta. mode ( ) & 0o7777 ) ,
125125 Err ( _) => {
126- return Err ( ChmodError :: CannotStat ( fref. into ( ) ) . into ( ) ) ;
126+ return Err ( ChmodError :: CannotStat ( fref. to_string_lossy ( ) . to_string ( ) ) . into ( ) ) ;
127127 }
128128 } ,
129129 None => None ,
@@ -372,48 +372,75 @@ impl Chmoder {
372372
373373 for filename in files {
374374 let file = Path :: new ( filename) ;
375- if !file. exists ( ) {
376- if file. is_symlink ( ) {
377- if !self . dereference && !self . recursive {
378- // The file is a symlink and we should not follow it
379- // Don't try to change the mode of the symlink itself
375+
376+ match file. try_exists ( ) {
377+ Ok ( exists) => {
378+ if !( exists) {
379+ if file. is_symlink ( ) {
380+ if !self . dereference && !self . recursive {
381+ // The file is a symlink and we should not follow it
382+ // Don't try to change the mode of the symlink itself
383+ continue ;
384+ }
385+ if self . recursive && self . traverse_symlinks == TraverseSymlinks :: None {
386+ continue ;
387+ }
388+
389+ if !self . quiet {
390+ show ! ( ChmodError :: DanglingSymlink (
391+ filename. to_string_lossy( ) . to_string( )
392+ ) ) ;
393+ set_exit_code ( 1 ) ;
394+ }
395+
396+ if self . verbose {
397+ println ! (
398+ "{}" ,
399+ translate!( "chmod-verbose-failed-dangling" , "file" => filename. to_string_lossy( ) . quote( ) )
400+ ) ;
401+ }
402+ } else if !self . quiet {
403+ show ! ( ChmodError :: NoSuchFile (
404+ filename. to_string_lossy( ) . to_string( )
405+ ) ) ;
406+ }
407+ // GNU exits with exit code 1 even if -q or --quiet are passed
408+ // So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
409+ set_exit_code ( 1 ) ;
380410 continue ;
381- }
382- if self . recursive && self . traverse_symlinks == TraverseSymlinks :: None {
411+ } else if !self . dereference && file. is_symlink ( ) {
412+ // The file is a symlink and we should not follow it
413+ // chmod 755 --no-dereference a/link
414+ // should not change the permissions in this case
383415 continue ;
384416 }
385417
418+ if self . recursive && self . preserve_root && file == Path :: new ( "/" ) {
419+ return Err ( ChmodError :: PreserveRoot ( "/" . to_string ( ) ) . into ( ) ) ;
420+ }
421+ if self . recursive {
422+ r = self . walk_dir_with_context ( file, true ) ;
423+ } else {
424+ r = self . chmod_file ( file) . and ( r) ;
425+ }
426+ }
427+ Err ( e) if e. kind ( ) == io:: ErrorKind :: PermissionDenied => {
386428 if !self . quiet {
387- show ! ( ChmodError :: DanglingSymlink ( filename. into( ) ) ) ;
388- set_exit_code ( 1 ) ;
429+ show ! ( ChmodError :: PermissionDenied (
430+ filename. to_string_lossy( ) . to_string( )
431+ ) ) ;
389432 }
390-
391- if self . verbose {
392- println ! (
393- "{}" ,
394- translate!( "chmod-verbose-failed-dangling" , "file" => filename. quote( ) )
395- ) ;
433+ set_exit_code ( 1 ) ;
434+ }
435+ // error must be no such file
436+ Err ( _) => {
437+ if !self . quiet {
438+ show ! ( ChmodError :: NoSuchFile (
439+ filename. to_string_lossy( ) . to_string( )
440+ ) ) ;
396441 }
397- } else if !self . quiet {
398- show ! ( ChmodError :: NoSuchFile ( filename. into( ) ) ) ;
442+ set_exit_code ( 1 ) ;
399443 }
400- // GNU exits with exit code 1 even if -q or --quiet are passed
401- // So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
402- set_exit_code ( 1 ) ;
403- continue ;
404- } else if !self . dereference && file. is_symlink ( ) {
405- // The file is a symlink and we should not follow it
406- // chmod 755 --no-dereference a/link
407- // should not change the permissions in this case
408- continue ;
409- }
410- if self . recursive && self . preserve_root && file == Path :: new ( "/" ) {
411- return Err ( ChmodError :: PreserveRoot ( "/" . into ( ) ) . into ( ) ) ;
412- }
413- if self . recursive {
414- r = self . walk_dir_with_context ( file, true ) . and ( r) ;
415- } else {
416- r = self . chmod_file ( file) . and ( r) ;
417444 }
418445 }
419446 r
@@ -470,7 +497,10 @@ impl Chmoder {
470497 Err ( err) => {
471498 // Handle permission denied errors with proper file path context
472499 if err. kind ( ) == std:: io:: ErrorKind :: PermissionDenied {
473- r = r. and ( Err ( ChmodError :: PermissionDenied ( file_path. into ( ) ) . into ( ) ) ) ;
500+ r = r. and ( Err ( ChmodError :: PermissionDenied (
501+ file_path. to_string_lossy ( ) . to_string ( ) ,
502+ )
503+ . into ( ) ) ) ;
474504 } else {
475505 r = r. and ( Err ( err. into ( ) ) ) ;
476506 }
@@ -497,7 +527,7 @@ impl Chmoder {
497527 // Handle permission denied with proper file path context
498528 let e = dir_meta. unwrap_err ( ) ;
499529 let error = if e. kind ( ) == std:: io:: ErrorKind :: PermissionDenied {
500- ChmodError :: PermissionDenied ( entry_path) . into ( )
530+ ChmodError :: PermissionDenied ( entry_path. to_string_lossy ( ) . to_string ( ) ) . into ( )
501531 } else {
502532 e. into ( )
503533 } ;
@@ -523,7 +553,10 @@ impl Chmoder {
523553 }
524554 Err ( err) => {
525555 let error = if err. kind ( ) == std:: io:: ErrorKind :: PermissionDenied {
526- ChmodError :: PermissionDenied ( entry_path) . into ( )
556+ ChmodError :: PermissionDenied (
557+ entry_path. to_string_lossy ( ) . to_string ( ) ,
558+ )
559+ . into ( )
527560 } else {
528561 err. into ( )
529562 } ;
@@ -589,7 +622,9 @@ impl Chmoder {
589622 new_mode
590623 ) ;
591624 }
592- return Err ( ChmodError :: PermissionDenied ( file_path. into ( ) ) . into ( ) ) ;
625+ return Err (
626+ ChmodError :: PermissionDenied ( file_path. to_string_lossy ( ) . to_string ( ) ) . into ( ) ,
627+ ) ;
593628 }
594629
595630 // Report the change using the helper method
@@ -626,9 +661,11 @@ impl Chmoder {
626661 }
627662 Ok ( ( ) ) // Skip dangling symlinks
628663 } else if err. kind ( ) == std:: io:: ErrorKind :: PermissionDenied {
629- Err ( ChmodError :: PermissionDenied ( file. into ( ) ) . into ( ) )
664+ // These two filenames would normally be conditionally
665+ // quoted, but GNU's tests expect them to always be quoted
666+ Err ( ChmodError :: PermissionDenied ( file. to_string_lossy ( ) . to_string ( ) ) . into ( ) )
630667 } else {
631- Err ( ChmodError :: CannotStat ( file. into ( ) ) . into ( ) )
668+ Err ( ChmodError :: CannotStat ( file. to_string_lossy ( ) . to_string ( ) ) . into ( ) )
632669 } ;
633670 }
634671 } ;
@@ -658,7 +695,7 @@ impl Chmoder {
658695 // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
659696 if ( new_mode & !naively_expected_new_mode) != 0 {
660697 return Err ( ChmodError :: NewPermissions (
661- file. into ( ) ,
698+ file. to_string_lossy ( ) . to_string ( ) ,
662699 display_permissions_unix ( new_mode as mode_t , false ) ,
663700 display_permissions_unix ( naively_expected_new_mode as mode_t , false ) ,
664701 )
@@ -725,4 +762,4 @@ mod tests {
725762 assert_eq ! ( c, None ) ;
726763 assert_eq ! ( a, [ "--" , "-r" , "file" ] ) ;
727764 }
728- }
765+ }
0 commit comments