From 6f3c1591134028d3dd7648b2edb6a0114a437cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Sat, 27 Jun 2026 23:00:24 +0300 Subject: [PATCH] csplit: include the file name in output-file errors csplit leaked the raw OS error when it could not create a split file, e.g. `csplit: IO error: Permission denied (os error 13)`. GNU reports the file name and a plain message: `csplit: xx00: Permission denied`. The file-creation error now carries the file name, and the `IoError` display drops the `IO error:` prefix and the raw `(os error N)` code so the other output paths read the same way. Closes #13118 --- src/uu/csplit/src/csplit.rs | 8 +++++--- src/uu/csplit/src/csplit_error.rs | 20 ++++++++++++++++++-- tests/by-util/test_csplit.rs | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 3a4b43e881e..b73b32176e6 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -268,10 +268,12 @@ impl SplitWriter<'_> { /// /// # Errors /// - /// The creation of the split file may fail with some [`io::Error`]. - fn new_writer(&mut self) -> io::Result<()> { + /// Returns an error if creating the split file fails. + fn new_writer(&mut self) -> Result<(), CsplitError> { let file_name = self.options.split_name.get(self.counter); - let file = File::create(file_name)?; + let file = File::create(&file_name) + .map_err_context(|| file_name.clone()) + .map_err(CsplitError::from)?; self.current_writer = Some(BufWriter::new(file)); self.counter += 1; self.size = 0; diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index d73400bd7d1..497c5c5b63b 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -6,13 +6,13 @@ use std::io; use thiserror::Error; use uucore::display::Quotable; -use uucore::error::UError; +use uucore::error::{UError, strip_errno}; use uucore::translate; /// Errors thrown by the csplit command #[derive(Debug, Error)] pub enum CsplitError { - #[error("IO error: {}", _0)] + #[error("{}", strip_errno(_0))] IoError(#[from] io::Error), #[error("{}", translate!("csplit-error-line-out-of-range", "pattern" => _0.quote()))] LineOutOfRange(String), @@ -54,3 +54,19 @@ impl UError for CsplitError { } } } + +#[cfg(test)] +mod tests { + use super::CsplitError; + + #[cfg(unix)] + #[test] + fn io_error_display_is_clean() { + // GNU does not print "IO error:" nor the raw "(os error N)" suffix. + let err = CsplitError::IoError(std::io::Error::from_raw_os_error(13)); + let msg = err.to_string(); + assert_eq!(msg, "Permission denied"); + assert!(!msg.contains("IO error:")); + assert!(!msg.contains("os error")); + } +} diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 385e2945c29..0e15fb47011 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1582,3 +1582,22 @@ fn test_write_error_dev_full_keep_files() { assert!(at.file_exists("xx00")); assert_eq!(at.read("xx00"), "1\n"); } + +/// Test that a failed split-file creation reports the filename. +#[test] +#[cfg(unix)] +fn test_create_error_reports_filename() { + // Root can open a mode-000 file for writing, so File::create would not fail. + if rustix::process::geteuid().is_root() { + return; + } + let (at, mut ucmd) = at_and_ucmd!(); + at.write("input", "a\nb\nc\n"); + // Pre-create the first split with no write permission so File::create fails. + at.touch("xx00"); + at.set_mode("xx00", 0o000); + + ucmd.args(&["input", "2"]) + .fails() + .stderr_is("csplit: xx00: Permission denied\n"); +}