Skip to content

Commit 859202c

Browse files
committed
git rebase with uutils#9793
1 parent 911bc15 commit 859202c

2 files changed

Lines changed: 109 additions & 52 deletions

File tree

src/uu/chmod/src/chmod.rs

Lines changed: 89 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
use clap::{Arg, ArgAction, Command};
99
use std::ffi::OsString;
10-
use std::fs;
1110
use std::os::unix::fs::{MetadataExt, PermissionsExt};
12-
use std::path::{Path, PathBuf};
11+
use std::path::Path;
12+
use std::{fs, io};
1313
use thiserror::Error;
1414
use uucore::display::Quotable;
1515
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, set_exit_code};
@@ -27,17 +27,17 @@ use uucore::translate;
2727
#[derive(Debug, Error)]
2828
enum 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

4343
impl 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+
}

tests/by-util/test_chmod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,3 +1389,23 @@ fn test_chmod_colored_output() {
13891389
.stderr_contains("\x1b[31merreur\x1b[0m") // Red "erreur" in French
13901390
.stderr_contains("\x1b[33m--invalid-option\x1b[0m"); // Yellow invalid option
13911391
}
1392+
1393+
#[test]
1394+
fn test_chmod_locked_dir_permission_denied() {
1395+
let scene = TestScenario::new(util_name!());
1396+
let at = &scene.fixtures;
1397+
1398+
let locked_dir = "locked";
1399+
let file = "file";
1400+
1401+
at.mkdir(locked_dir);
1402+
at.touch(format!("{locked_dir}/{file}"));
1403+
at.set_mode(locked_dir, 0o000);
1404+
1405+
scene
1406+
.ucmd()
1407+
.arg("000")
1408+
.arg(format!("{locked_dir}/{file}"))
1409+
.fails()
1410+
.stderr_contains("chmod: cannot access 'locked/file': Permission denied");
1411+
}

0 commit comments

Comments
 (0)