diff --git a/src/uu/chcon/locales/en-US.ftl b/src/uu/chcon/locales/en-US.ftl index 0d18484b01e..99bb57df518 100644 --- a/src/uu/chcon/locales/en-US.ftl +++ b/src/uu/chcon/locales/en-US.ftl @@ -27,6 +27,7 @@ chcon-error-no-files-specified = No files are specified chcon-error-data-out-of-range = Data is out of range chcon-error-operation-failed = { $operation } failed chcon-error-operation-failed-on = { $operation } failed on { $operand } +chcon-error-write = write error: { $error } # Error messages - argument validation chcon-error-invalid-context = Invalid security context '{ $context }'. diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 5cc4a2bac23..e3548f196c8 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -23,6 +23,7 @@ use core::ffi::CStr; use std::borrow::Cow; use std::collections::HashSet; use std::ffi::{CString, OsStr, OsString}; +use std::io::Write; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::path::{Path, PathBuf}; use std::{fs, io}; @@ -519,6 +520,7 @@ fn relabel_all( // Directories reached on the way down, so a symlink loop is noticed // instead of followed forever. open_ancestors: HashSet::new(), + write_error: None, errors, }; @@ -527,6 +529,10 @@ fn relabel_all( walk.visit(&start, operand.as_os_str(), operand, behavior, true); } + if let Some(err) = walk.write_error { + walk.errors.push(Error::Write(err)); + } + walk.errors } @@ -537,6 +543,7 @@ struct Walk<'a> { context: &'a SELinuxSecurityContext<'a>, root_id: Option, open_ancestors: HashSet, + write_error: Option, errors: Vec, } @@ -645,11 +652,13 @@ impl Walk<'_> { } fn relabel(&mut self, parent: &DirFd, name: &OsStr, display: &Path) { - if self.options.verbose { - println!( + if self.options.verbose && self.write_error.is_none() { + self.write_error = writeln!( + io::stdout(), "{}", translate!("chcon-verbose-changing-context", "util_name" => "chcon", "file" => display.quote()) - ); + ) + .err(); } if let Err(err) = apply_context( diff --git a/src/uu/chcon/src/errors.rs b/src/uu/chcon/src/errors.rs index b3ea0ada3f4..13e0d9da32a 100644 --- a/src/uu/chcon/src/errors.rs +++ b/src/uu/chcon/src/errors.rs @@ -10,6 +10,7 @@ use std::fmt::Write; use std::io; use uucore::display::Quotable; +use uucore::error::strip_errno; use uucore::translate; pub(crate) type Result = std::result::Result; @@ -31,6 +32,9 @@ pub(crate) enum Error { #[error(transparent)] CommandLine(#[from] clap::Error), + #[error("{}", translate!("chcon-error-write", "error" => strip_errno(.0)))] + Write(io::Error), + #[error("{}", translate!("chcon-error-operation-failed", "operation" => operation))] SELinux { operation: String, diff --git a/tests/by-util/test_chcon.rs b/tests/by-util/test_chcon.rs index c409f010bfb..5341063b6d4 100644 --- a/tests/by-util/test_chcon.rs +++ b/tests/by-util/test_chcon.rs @@ -53,6 +53,28 @@ fn recursive_errors() { } } +#[test] +#[cfg(target_os = "linux")] +fn verbose_write_error() { + let (at, mut cmd) = at_and_ucmd!(); + at.touch("a"); + at.touch("b"); + let dev_full = std::fs::OpenOptions::new() + .write(true) + .open("/dev/full") + .unwrap(); + + let result = cmd + .args(&["--verbose", "--type=etc_t", "a", "b", "missing"]) + .set_stdout(dev_full) + .fails_with_code(1); + result + .stderr_contains("write error: No space left on device") + .stderr_contains("Accessing failed on 'missing'"); + + assert_eq!(result.stderr_str().matches("write error:").count(), 1); +} + #[test] fn valid_context() { let (dir, mut cmd) = at_and_ucmd!();