diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index a2dc8e06b..374f22874 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -12,12 +12,12 @@ use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; use uucore::display::Quotable; -use uucore::error::{ExitCode, UResult, USimpleError, UUsageError}; +use uucore::error::{set_exit_code, ExitCode, UResult, USimpleError, UUsageError}; use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; -use uucore::{format_usage, show_error}; +use uucore::{format_usage, show, show_error}; const ABOUT: &str = "Change the mode of each FILE to MODE.\n\ With --reference, change the mode of each FILE to that of RFILE."; @@ -195,21 +195,24 @@ impl Chmoder { filename.quote() ); if !self.quiet { - return Err(USimpleError::new( + show!(USimpleError::new( 1, format!("cannot operate on dangling symlink {}", filename.quote()), )); } } else if !self.quiet { - return Err(USimpleError::new( + show!(USimpleError::new( 1, format!( "cannot access {}: No such file or directory", filename.quote() - ), + ) )); } - return Err(ExitCode::new(1)); + // GNU exits with exit code 1 even if -q or --quiet are passed + // So we set the exit code, because it hasn't been set yet if `self.quiet` is true. + set_exit_code(1); + continue; } if self.recursive && self.preserve_root && filename == "/" { return Err(USimpleError::new( diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 5dfd1a714..feb756632 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -574,3 +574,71 @@ fn test_mode_after_dash_dash() { ucmd, ); } + +#[test] +fn test_chmod_file_after_non_existing_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(TEST_FILE); + at.touch("file2"); + set_permissions(at.plus(TEST_FILE), Permissions::from_mode(0o664)).unwrap(); + set_permissions(at.plus("file2"), Permissions::from_mode(0o664)).unwrap(); + scene + .ucmd() + .arg("u+x") + .arg("does-not-exist") + .arg(TEST_FILE) + .fails() + .stderr_contains("chmod: cannot access 'does-not-exist': No such file or directory") + .code_is(1); + + assert_eq!(at.metadata(TEST_FILE).permissions().mode(), 0o100764); + + scene + .ucmd() + .arg("u+x") + .arg("--q") + .arg("does-not-exist") + .arg("file2") + .fails() + .no_stderr() + .code_is(1); + assert_eq!(at.metadata("file2").permissions().mode(), 0o100764); +} + +#[test] +fn test_chmod_file_symlink_after_non_existing_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let existing = "file"; + let test_existing_symlink = "file_symlink"; + + let non_existing = "test_chmod_symlink_non_existing_file"; + let test_dangling_symlink = "test_chmod_symlink_non_existing_file_symlink"; + let expected_stdout = &format!( + "failed to change mode of '{test_dangling_symlink}' from 0000 (---------) to 0000 (---------)" + ); + let expected_stderr = &format!("cannot operate on dangling symlink '{test_dangling_symlink}'"); + + at.touch(existing); + set_permissions(at.plus(existing), Permissions::from_mode(0o664)).unwrap(); + at.symlink_file(non_existing, test_dangling_symlink); + at.symlink_file(existing, test_existing_symlink); + + // this cannot succeed since the symbolic link dangles + // but the metadata for the existing target should change + scene + .ucmd() + .arg("u+x") + .arg("-v") + .arg(test_dangling_symlink) + .arg(test_existing_symlink) + .fails() + .code_is(1) + .stdout_contains(expected_stdout) + .stderr_contains(expected_stderr); + assert_eq!( + at.metadata(test_existing_symlink).permissions().mode(), + 0o100764 + ); +}