From c11887b4b34e4c8b0ef87ab84e3c2c6bdd5631c6 Mon Sep 17 00:00:00 2001 From: fixfon <12512757+fixfon@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:38:26 +0300 Subject: [PATCH 1/2] chmod: keep operand order with --reference With --reference, clap parses the first FILE operand as MODE and uumain() appended it after the other operands, so it was processed last. Insert it at the front instead. Closes #15666 --- src/uu/chmod/src/chmod.rs | 5 +++-- tests/by-util/test_chmod.rs | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 98abdfae2e..226b5080e9 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -177,9 +177,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default(); let cmode = if fmode.is_some() { // "--reference" and MODE are mutually exclusive - // if "--reference" was used MODE needs to be interpreted as another FILE + // if "--reference" was used MODE needs to be interpreted as another FILE, + // the first one, so it must go in front to keep the command-line order // it wasn't possible to implement this behavior directly with clap - files.push(OsString::from(cmode)); + files.insert(0, OsString::from(cmode)); None } else { Some(cmode) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 9e8aa22480..ba84769cda 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -411,6 +411,51 @@ fn test_chmod_reference_file() { run_single_test(&tests[0], &at, ucmd); } +#[test] +fn test_chmod_reference_keeps_operand_order() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("ref"); + at.set_mode("ref", 0o644); + for name in ["a", "b", "c"] { + at.touch(name); + at.set_mode(name, 0o600); + } + + ucmd.args(&["-v", "--reference=ref", "a", "b", "c"]) + .succeeds() + .stdout_is( + "mode of 'a' changed from 0600 (rw-------) to 0644 (rw-r--r--)\n\ + mode of 'b' changed from 0600 (rw-------) to 0644 (rw-r--r--)\n\ + mode of 'c' changed from 0600 (rw-------) to 0644 (rw-r--r--)\n", + ); +} + +#[test] +fn test_chmod_reference_first_operand_before_its_parent_dir() { + use rustix::process::geteuid; + + // The reference mode drops the search bit of `dir`, so `dir/file` is only + // reachable if it is handled first. root ignores directory permissions. + if geteuid().is_root() { + return; + } + + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("ref"); + at.set_mode("ref", 0o644); + at.mkdir("dir"); + at.touch("dir/file"); + at.set_mode("dir/file", 0o600); + at.set_mode("dir", 0o755); + + ucmd.args(&["--reference=ref", "dir/file", "dir"]) + .succeeds(); + + assert_eq!(at.metadata("dir").permissions().mode() & 0o7777, 0o644); + at.set_mode("dir", 0o755); + assert_eq!(at.metadata("dir/file").permissions().mode() & 0o7777, 0o644); +} + #[test] fn test_permission_denied() { let scene = TestScenario::new(util_name!()); From f8aba5d448ee6e71849912fd6b72bd7a01f2fb5d Mon Sep 17 00:00:00 2001 From: fixfon <12512757+fixfon@users.noreply.github.com> Date: Sat, 24 Oct 2026 17:45:04 +0300 Subject: [PATCH 2/2] chmod: simplify --reference operand comment --- src/uu/chmod/src/chmod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 226b5080e9..a2e12d726b 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -178,7 +178,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let cmode = if fmode.is_some() { // "--reference" and MODE are mutually exclusive // if "--reference" was used MODE needs to be interpreted as another FILE, - // the first one, so it must go in front to keep the command-line order + // it is the first FILE, keep the command-line order // it wasn't possible to implement this behavior directly with clap files.insert(0, OsString::from(cmode)); None