1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

chmod: Correct chmod -R on dangling symlink and tests (#7618)

* Correct chmod -R on dangling symlink and tests

* Add tests of arg-level symlink to chmod

* Add tests of all symlink flag combos on chmod dangling

* Fix no traverse on dangling symlink

* Add chmod recursive tests of default symlink method

* Add default chmod -H flag tests

* Set chmod default traversal method correctly to -H

* Fix arg symlink chmod case

* Remove extra chmod -H testing

---------

Co-authored-by: Clifford Ressel <EMAIL@gmail.com>
This commit is contained in:
Clifford Ressel 2025-04-07 10:18:00 -04:00 committed by GitHub
parent 1b250fa75c
commit b7bf8c9467
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 10 deletions

View file

@ -138,7 +138,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return Err(UUsageError::new(1, "missing operand".to_string())); return Err(UUsageError::new(1, "missing operand".to_string()));
} }
let (recursive, dereference, traverse_symlinks) = configure_symlink_and_recursion(&matches)?; let (recursive, dereference, traverse_symlinks) =
configure_symlink_and_recursion(&matches, TraverseSymlinks::First)?;
let chmoder = Chmoder { let chmoder = Chmoder {
changes, changes,
@ -259,6 +260,10 @@ impl Chmoder {
// Don't try to change the mode of the symlink itself // Don't try to change the mode of the symlink itself
continue; continue;
} }
if self.recursive && self.traverse_symlinks == TraverseSymlinks::None {
continue;
}
if !self.quiet { if !self.quiet {
show!(USimpleError::new( show!(USimpleError::new(
1, 1,

View file

@ -507,6 +507,7 @@ type GidUidFilterOwnerParser = fn(&ArgMatches) -> UResult<GidUidOwnerFilter>;
/// Returns the updated `dereference` and `traverse_symlinks` values. /// Returns the updated `dereference` and `traverse_symlinks` values.
pub fn configure_symlink_and_recursion( pub fn configure_symlink_and_recursion(
matches: &ArgMatches, matches: &ArgMatches,
default_traverse_symlinks: TraverseSymlinks,
) -> Result<(bool, bool, TraverseSymlinks), Box<dyn crate::error::UError>> { ) -> Result<(bool, bool, TraverseSymlinks), Box<dyn crate::error::UError>> {
let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) {
Some(true) // Follow symlinks Some(true) // Follow symlinks
@ -516,12 +517,13 @@ pub fn configure_symlink_and_recursion(
None // Default behavior None // Default behavior
}; };
let mut traverse_symlinks = if matches.get_flag("L") { let mut traverse_symlinks = default_traverse_symlinks;
TraverseSymlinks::All if matches.get_flag("L") {
traverse_symlinks = TraverseSymlinks::All
} else if matches.get_flag("H") { } else if matches.get_flag("H") {
TraverseSymlinks::First traverse_symlinks = TraverseSymlinks::First
} else { } else if matches.get_flag("P") {
TraverseSymlinks::None traverse_symlinks = TraverseSymlinks::None
}; };
let recursive = matches.get_flag(options::RECURSIVE); let recursive = matches.get_flag(options::RECURSIVE);
@ -597,7 +599,8 @@ pub fn chown_base(
.unwrap_or_default(); .unwrap_or_default();
let preserve_root = matches.get_flag(options::preserve_root::PRESERVE); let preserve_root = matches.get_flag(options::preserve_root::PRESERVE);
let (recursive, dereference, traverse_symlinks) = configure_symlink_and_recursion(&matches)?; let (recursive, dereference, traverse_symlinks) =
configure_symlink_and_recursion(&matches, TraverseSymlinks::None)?;
let verbosity_level = if matches.get_flag(options::verbosity::CHANGES) { let verbosity_level = if matches.get_flag(options::verbosity::CHANGES) {
VerbosityLevel::Changes VerbosityLevel::Changes

View file

@ -878,7 +878,7 @@ fn test_chmod_symlink_target_no_dereference() {
} }
#[test] #[test]
fn test_chmod_symlink_to_dangling_recursive() { fn test_chmod_symlink_recursive_final_traversal_flag() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
@ -891,9 +891,14 @@ fn test_chmod_symlink_to_dangling_recursive() {
.ucmd() .ucmd()
.arg("755") .arg("755")
.arg("-R") .arg("-R")
.arg("-H")
.arg("-L")
.arg("-H")
.arg("-L")
.arg("-P")
.arg(symlink) .arg(symlink)
.fails() .succeeds()
.stderr_is("chmod: cannot operate on dangling symlink 'symlink'\n"); .no_output();
assert_eq!( assert_eq!(
at.symlink_metadata(symlink).permissions().mode(), at.symlink_metadata(symlink).permissions().mode(),
get_expected_symlink_permissions(), get_expected_symlink_permissions(),
@ -903,9 +908,73 @@ fn test_chmod_symlink_to_dangling_recursive() {
); );
} }
#[test]
fn test_chmod_symlink_to_dangling_recursive_no_traverse() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let dangling_target = "nonexistent_file";
let symlink = "symlink";
at.symlink_file(dangling_target, symlink);
scene
.ucmd()
.arg("755")
.arg("-R")
.arg("-P")
.arg(symlink)
.succeeds()
.no_output();
assert_eq!(
at.symlink_metadata(symlink).permissions().mode(),
get_expected_symlink_permissions(),
"Expected symlink permissions: {:o}, but got: {:o}",
get_expected_symlink_permissions(),
at.symlink_metadata(symlink).permissions().mode()
);
}
#[test]
fn test_chmod_dangling_symlink_recursive_combos() {
let error_scenarios = [vec!["-R"], vec!["-R", "-H"], vec!["-R", "-L"]];
for flags in error_scenarios {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let dangling_target = "nonexistent_file";
let symlink = "symlink";
at.symlink_file(dangling_target, symlink);
let mut ucmd = scene.ucmd();
for f in &flags {
ucmd.arg(f);
}
ucmd.arg("u+x")
.umask(0o022)
.arg(symlink)
.fails()
.stderr_is("chmod: cannot operate on dangling symlink 'symlink'\n");
assert_eq!(
at.symlink_metadata(symlink).permissions().mode(),
get_expected_symlink_permissions(),
"Expected symlink permissions: {:o}, but got: {:o}",
get_expected_symlink_permissions(),
at.symlink_metadata(symlink).permissions().mode()
);
}
}
#[test] #[test]
fn test_chmod_traverse_symlink_combo() { fn test_chmod_traverse_symlink_combo() {
let scenarios = [ let scenarios = [
(
vec!["-R"], // Should default to "-H"
0o100_664,
get_expected_symlink_permissions(),
),
( (
vec!["-R", "-H"], vec!["-R", "-H"],
0o100_664, 0o100_664,