Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/uu/chmod/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ chmod-error-dangling-symlink = cannot operate on dangling symlink {$file}
chmod-error-no-such-file = cannot access {$file}: No such file or directory
chmod-error-preserve-root = it is dangerous to operate recursively on {$file}
chmod: use --no-preserve-root to override this failsafe
chmod-error-permission-denied = {$file}: Permission denied
chmod-error-permission-denied = cannot access {$file}: Permission denied
chmod-error-new-permissions = {$file}: new permissions are {$actual}, not {$expected}
chmod-error-missing-operand = missing operand
Expand Down
2 changes: 1 addition & 1 deletion src/uu/chmod/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ chmod-error-dangling-symlink = impossible d'opérer sur le lien symbolique pendo
chmod-error-no-such-file = impossible d'accéder à {$file} : Aucun fichier ou répertoire de ce type
chmod-error-preserve-root = il est dangereux d'opérer récursivement sur {$file}
chmod: utiliser --no-preserve-root pour outrepasser cette protection
chmod-error-permission-denied = {$file} : Permission refusée
chmod-error-permission-denied = impossible d'accéder à {$file} : Permission refusée
chmod-error-new-permissions = {$file} : les nouvelles permissions sont {$actual}, pas {$expected}
chmod-error-missing-operand = opérande manquant

Expand Down
97 changes: 60 additions & 37 deletions src/uu/chmod/src/chmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

use clap::{Arg, ArgAction, Command};
use std::ffi::OsString;
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use std::{fs, io};
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, set_exit_code};
Expand Down Expand Up @@ -372,52 +372,75 @@ impl Chmoder {

for filename in files {
let file = Path::new(filename);
if !file.exists() {
if file.is_symlink() {
if !self.dereference && !self.recursive {
// The file is a symlink and we should not follow it
// Don't try to change the mode of the symlink itself

match file.try_exists() {
Ok(exists) => {
if !(exists) {
if file.is_symlink() {
if !self.dereference && !self.recursive {
// The file is a symlink and we should not follow it
// Don't try to change the mode of the symlink itself
continue;
}
if self.recursive && self.traverse_symlinks == TraverseSymlinks::None {
continue;
}

if !self.quiet {
show!(ChmodError::DanglingSymlink(
filename.to_string_lossy().to_string()
));
set_exit_code(1);
}

if self.verbose {
println!(
"{}",
translate!("chmod-verbose-failed-dangling", "file" => filename.to_string_lossy().quote())
);
}
} else if !self.quiet {
show!(ChmodError::NoSuchFile(
filename.to_string_lossy().to_string()
));
}
// 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.traverse_symlinks == TraverseSymlinks::None {
} else if !self.dereference && file.is_symlink() {
// The file is a symlink and we should not follow it
// chmod 755 --no-dereference a/link
// should not change the permissions in this case
continue;
}

if self.recursive && self.preserve_root && file == Path::new("/") {
return Err(ChmodError::PreserveRoot("/".to_string()).into());
}
if self.recursive {
r = self.walk_dir_with_context(file, true);
} else {
r = self.chmod_file(file).and(r);
}
}
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
if !self.quiet {
show!(ChmodError::DanglingSymlink(
show!(ChmodError::PermissionDenied(
filename.to_string_lossy().to_string()
));
set_exit_code(1);
}

if self.verbose {
println!(
"{}",
translate!("chmod-verbose-failed-dangling", "file" => filename.to_string_lossy().quote())
);
set_exit_code(1);
}
// error must be no such file
Err(_) => {
if !self.quiet {
show!(ChmodError::NoSuchFile(
filename.to_string_lossy().to_string()
));
}
} else if !self.quiet {
show!(ChmodError::NoSuchFile(
filename.to_string_lossy().to_string()
));
set_exit_code(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;
} else if !self.dereference && file.is_symlink() {
// The file is a symlink and we should not follow it
// chmod 755 --no-dereference a/link
// should not change the permissions in this case
continue;
}
if self.recursive && self.preserve_root && file == Path::new("/") {
return Err(ChmodError::PreserveRoot("/".to_string()).into());
}
if self.recursive {
r = self.walk_dir_with_context(file, true);
} else {
r = self.chmod_file(file).and(r);
}
}
r
Expand Down
24 changes: 22 additions & 2 deletions tests/by-util/test_chmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ fn test_permission_denied() {
.arg("o=r")
.arg("d")
.fails()
.stderr_is("chmod: 'd/no-x/y': Permission denied\n");
.stderr_is("chmod: cannot access 'd/no-x/y': Permission denied\n");
}

#[test]
Expand All @@ -395,7 +395,7 @@ fn test_chmod_recursive() {
#[cfg(not(target_os = "linux"))]
let err_msg = "chmod: Permission denied\n";
#[cfg(target_os = "linux")]
let err_msg = "chmod: 'z': Permission denied\n";
let err_msg = "chmod: cannot access 'z': Permission denied\n";

// only the permissions of folder `a` and `z` are changed
// folder can't be read after read permission is removed
Expand Down Expand Up @@ -1357,3 +1357,23 @@ fn test_chmod_colored_output() {
.stderr_contains("\x1b[31merreur\x1b[0m") // Red "erreur" in French
.stderr_contains("\x1b[33m--invalid-option\x1b[0m"); // Yellow invalid option
}

#[test]
fn test_chmod_locked_dir_permission_denied() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

let locked_dir = "locked";
let file = "file";

at.mkdir(locked_dir);
at.touch(format!("{locked_dir}/{file}"));
at.set_mode(locked_dir, 0o000);

scene
.ucmd()
.arg("000")
.arg(format!("{locked_dir}/{file}"))
.fails()
.stderr_contains("chmod: cannot access 'locked/file': Permission denied");
}
Loading