csplit: detect and report write errors (#9855)

* csplit: detect and report write errors

* Add Rust integration tests for csplit write error detection

* csplit: fix doc comment for finish_split
This commit is contained in:
Chris Dryden
2025-12-31 14:09:39 -05:00
committed by GitHub
parent 16c16f44ee
commit a297d26cfa
2 changed files with 56 additions and 11 deletions
+24 -11
View File
@@ -127,7 +127,7 @@ where
let ret = do_csplit(&mut split_writer, patterns_vec, &mut input_iter);
// consume the rest, unless there was an error
if ret.is_ok() {
let ret = if ret.is_ok() {
input_iter.rewind_buffer();
if let Some((_, line)) = input_iter.next() {
// There is remaining input: create a final split and copy remainder
@@ -136,14 +136,18 @@ where
for (_, line) in input_iter {
split_writer.writeln(&line?)?;
}
split_writer.finish_split();
split_writer.finish_split()
} else if all_up_to_line && options.suppress_matched {
// GNU semantics for integer patterns with --suppress-matched:
// even if no remaining input, create a final (possibly empty) split
split_writer.new_writer()?;
split_writer.finish_split();
split_writer.finish_split()
} else {
Ok(())
}
}
} else {
ret
};
// delete files on error by default
if ret.is_err() && !options.keep_files {
split_writer.delete_all_splits()?;
@@ -305,15 +309,24 @@ impl SplitWriter<'_> {
///
/// # Errors
///
/// Some [`io::Error`] if the split could not be removed in case it should be elided.
fn finish_split(&mut self) {
/// Returns an error if flushing the writer fails.
fn finish_split(&mut self) -> Result<(), CsplitError> {
if !self.dev_null {
// Flush the writer to ensure all data is written and errors are detected
if let Some(ref mut writer) = self.current_writer {
let file_name = self.options.split_name.get(self.counter - 1);
writer
.flush()
.map_err_context(|| file_name.clone())
.map_err(CsplitError::from)?;
}
if self.options.elide_empty_files && self.size == 0 {
self.counter -= 1;
} else if !self.options.quiet {
println!("{}", self.size);
}
}
Ok(())
}
/// Removes all the split files that were created.
@@ -379,7 +392,7 @@ impl SplitWriter<'_> {
}
self.writeln(&line)?;
}
self.finish_split();
self.finish_split()?;
ret
}
@@ -446,7 +459,7 @@ impl SplitWriter<'_> {
self.writeln(&line?)?;
}
None => {
self.finish_split();
self.finish_split()?;
return Err(CsplitError::LineOutOfRange(
pattern_as_str.to_string(),
));
@@ -454,7 +467,7 @@ impl SplitWriter<'_> {
}
offset -= 1;
}
self.finish_split();
self.finish_split()?;
// if we have to suppress one line after we take the next and do nothing
if next_line_suppress_matched {
@@ -495,7 +508,7 @@ impl SplitWriter<'_> {
);
}
self.finish_split();
self.finish_split()?;
if input_iter.buffer_len() < offset_usize {
return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));
}
@@ -511,7 +524,7 @@ impl SplitWriter<'_> {
}
}
self.finish_split();
self.finish_split()?;
Err(CsplitError::MatchNotFound(pattern_as_str.to_string()))
}
}
+32
View File
@@ -1551,3 +1551,35 @@ fn test_csplit_non_utf8_paths() {
ucmd.arg(&filename).arg("3").succeeds();
}
/// Test write error detection using /dev/full
#[test]
#[cfg(target_os = "linux")]
fn test_write_error_dev_full() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("/dev/full", "xx01");
ucmd.args(&["-", "2"])
.pipe_in("1\n2\n")
.fails_with_code(1)
.stderr_contains("xx01: No space left on device");
// Files cleaned up by default
assert!(!at.file_exists("xx00"));
}
/// Test write error with -k keeps files
#[test]
#[cfg(target_os = "linux")]
fn test_write_error_dev_full_keep_files() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("/dev/full", "xx01");
ucmd.args(&["-k", "-", "2"])
.pipe_in("1\n2\n")
.fails_with_code(1)
.stderr_contains("xx01: No space left on device");
assert!(at.file_exists("xx00"));
assert_eq!(at.read("xx00"), "1\n");
}