From d572850fca0d608721ec4b9660ec854e15b45c64 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Tue, 6 Oct 2026 01:24:54 +0200 Subject: [PATCH 1/2] sed: write the newline a file's last line lacks before the next file's output, as GNU sed does --- src/sed/fast_io.rs | 20 ++++++ src/sed/in_place.rs | 2 +- src/sed/named_writer.rs | 40 +++++++----- src/sed/processor.rs | 17 ++--- tests/by-util/test_sed.rs | 63 ++++++++++++++++++- .../fixtures/sed/output/multiple_input_files | 3 +- 6 files changed, 114 insertions(+), 31 deletions(-) diff --git a/src/sed/fast_io.rs b/src/sed/fast_io.rs index 1fe73c87..95c50063 100644 --- a/src/sed/fast_io.rs +++ b/src/sed/fast_io.rs @@ -652,6 +652,14 @@ impl OutputBuffer { } } + /// Continue the output of `previous`, which wrote to the same stream, + /// including a newline it deferred. + #[must_use] + pub fn continuing(mut self, previous: &Self) -> Self { + self.pending_newline = previous.pending_newline; + self + } + /// Schedule the specified String or &str for eventual output pub fn write_str>(&mut self, s: S) -> io::Result<()> { let mut s = s.into(); @@ -692,7 +700,19 @@ impl OutputBuffer { // As in GNU sed, write the deferred newline even when the file is // empty or cannot be read. self.write_raw(b"")?; + self.copy_file_contents(path) + } + + /// Copy the specified file to the output, leaving any deferred newline + /// for the next output, as GNU sed does for `0r`. + pub fn copy_file_before_pending_newline(&mut self, path: &PathBuf) -> io::Result<()> { + // Flush mmap writes, if any. + #[cfg(unix)] + self.flush_mmap(WriteRange::Complete)?; + self.copy_file_contents(path) + } + fn copy_file_contents(&mut self, path: &PathBuf) -> io::Result<()> { let Ok(file) = File::open(path) else { // Per POSIX, if the file can't be read treat it as empty. return Ok(()); diff --git a/src/sed/in_place.rs b/src/sed/in_place.rs index 50c1faf8..272ac414 100644 --- a/src/sed/in_place.rs +++ b/src/sed/in_place.rs @@ -67,7 +67,7 @@ impl InPlace { /// to the context settings. fn begin_resolved(&mut self, file_name: &Path) -> UResult<&mut OutputBuffer> { if !self.in_place { - self.output = OutputBuffer::new(Box::new(stdout())); + self.output = OutputBuffer::new(Box::new(stdout())).continuing(&self.output); return Ok(&mut self.output); } diff --git a/src/sed/named_writer.rs b/src/sed/named_writer.rs index 18cf1e5c..ce73146d 100644 --- a/src/sed/named_writer.rs +++ b/src/sed/named_writer.rs @@ -30,6 +30,7 @@ pub struct NamedWriter { pub path: PathBuf, writer: BufWriter, location: ScriptLocation, + pending_newline: bool, // The last line written lacked its newline } impl NamedWriter { @@ -49,6 +50,7 @@ impl NamedWriter { path, writer: BufWriter::new(file), location, + pending_newline: false, })); FLUSH_LIST.with(|list| list.borrow_mut().push(Rc::clone(&writer))); @@ -61,23 +63,29 @@ impl NamedWriter { } /// Write bytes to the file, possibly with a newline, returning errors. + /// A missing newline is written before any further line. pub fn write_line_bytes(&mut self, line: &[u8], newline: bool) -> UResult<()> { - self.writer - .write_all(line) - .and_then(|()| { - if newline { - self.writer.write_all(b"\n") - } else { - Ok(()) - } - }) - .map_err(|e| { - runtime_error::<()>( - &self.location, - format!("writing to file {}: {e}", self.path.quote()), - ) - .unwrap_err() - }) + let pending = std::mem::replace(&mut self.pending_newline, !newline); + (if pending { + self.writer.write_all(b"\n") + } else { + Ok(()) + }) + .and_then(|()| self.writer.write_all(line)) + .and_then(|()| { + if newline { + self.writer.write_all(b"\n") + } else { + Ok(()) + } + }) + .map_err(|e| { + runtime_error::<()>( + &self.location, + format!("writing to file {}: {e}", self.path.quote()), + ) + .unwrap_err() + }) } /// Flush the writer, returning a descriptive error. diff --git a/src/sed/processor.rs b/src/sed/processor.rs index 9cc98c1f..b9c9d979 100644 --- a/src/sed/processor.rs +++ b/src/sed/processor.rs @@ -632,7 +632,7 @@ fn process_address_0( && cmd.addr2.is_none() { let path = extract_variant!(cmd, Path); - output.copy_file(path)?; + output.copy_file_before_pending_newline(path)?; } cmd.next.clone() @@ -902,16 +902,11 @@ fn process_file( // Append only the first line of the pattern space. let writer = extract_variant!(command, NamedWriter); let pattern_bytes = pattern.as_bytes(); - let (first_line, found_newline) = - match pattern_bytes.iter().position(|&b| b == b'\n') { - // A slice including the newline - Some(pos) => (&pattern_bytes[..=pos], true), - None => (pattern_bytes, false), - }; - writer.borrow_mut().write_line_bytes( - first_line, - !found_newline && pattern.is_newline_terminated(), - )?; + let (first_line, newline) = match memchr(b'\n', pattern_bytes) { + Some(pos) => (&pattern_bytes[..pos], true), + None => (pattern_bytes, pattern.is_newline_terminated()), + }; + writer.borrow_mut().write_line_bytes(first_line, newline)?; } 'x' => { // Exchange the contents of the pattern and hold spaces. diff --git a/tests/by-util/test_sed.rs b/tests/by-util/test_sed.rs index 83d96035..03580976 100644 --- a/tests/by-util/test_sed.rs +++ b/tests/by-util/test_sed.rs @@ -2020,12 +2020,12 @@ fn write_first_line_newline() -> std::io::Result<()> { new_ucmd!() .args(&["-n", "-e", &cmd]) - .pipe_in("abc\ndef\n") + .pipe_in("abc\ndef\nghi\njkl\n") .succeeds(); let mut actual = String::new(); temp.reopen()?.read_to_string(&mut actual)?; - assert_eq!(actual, "abc\n"); + assert_eq!(actual, "abc\nghi\n"); Ok(()) } @@ -2618,6 +2618,8 @@ fn in_place_edit_separate() -> std::io::Result<()> { "b1+b2\n", ), (&["-n", "-i", "-e", "N;p"], "a1\n", b, "", "b1\nb2\n"), + // A newline missing at the end of one file is not written to the next. + (&["-i", "-e", "s/a/A/"], "a", "b\n", "A", "b\n"), ]; for (args, first, second, expected1, expected2) in cases { @@ -3408,3 +3410,60 @@ fn test_execute_command_output_is_copied_unchanged() { .succeeds() .stdout_is("a\n"); } + +/// A newline that a file's last line lacks is written before the next +/// file's output, both to standard output and to `w` and `W` files. +#[test] +fn test_missing_newline_is_written_between_files() -> std::io::Result<()> { + type Case = (&'static [&'static str], &'static str, [&'static str; 3]); + let cases: &[Case] = &[ + (&[], "w", ["e\nf", "g\nh", "e\nf\ng\nh"]), + (&[], "W", ["e\nf", "g\nh", "e\nf\ng\nh"]), + (&["-s"], "w", ["e\nf", "g\nh", "e\nf\ng\nh"]), + (&[], "2Q;w", ["x", "y", "x"]), + ]; + for (flags, script, [first, second, expected]) in cases { + let (dir, one, two) = two_inputs(first, second)?; + let out = dir.path().join("out"); + new_ucmd!() + .args(flags) + .arg(format!("{script} {}", out.display())) + .arg(&one) + .arg(&two) + .succeeds() + .stdout_is(*expected); + assert_eq!(fs::read_to_string(&out)?, *expected, "{flags:?} {script}"); + } + Ok(()) +} + +/// `0r` at the start of the next file leaves the missing newline for that +/// file's first line, as in GNU sed. +#[test] +fn test_zero_address_read_keeps_missing_newline_for_next_line() -> std::io::Result<()> { + let (dir, one, two) = two_inputs("a", "b\n")?; + let text = dir.path().join("text"); + fs::write(&text, "X\nY")?; + new_ucmd!() + .args(&["-s", &format!("0r {}", text.display())]) + .arg(&one) + .arg(&two) + .succeeds() + .stdout_is("X\nYaX\nY\nb\n"); + Ok(()) +} + +/// A `w` file gets the newline that an emptied last line lacks before the +/// next file's line, as in GNU sed. +#[test] +fn test_write_empty_line_without_newline() -> std::io::Result<()> { + let (dir, one, two) = two_inputs("a", "b")?; + let out = dir.path().join("out"); + new_ucmd!() + .args(&["-n", &format!("s/.*//w {}", out.display())]) + .arg(&one) + .arg(&two) + .succeeds(); + assert_eq!(fs::read_to_string(&out)?, "\n"); + Ok(()) +} diff --git a/tests/fixtures/sed/output/multiple_input_files b/tests/fixtures/sed/output/multiple_input_files index 2529dcc0..27afad30 100644 --- a/tests/fixtures/sed/output/multiple_input_files +++ b/tests/fixtures/sed/output/multiple_input_files @@ -14,7 +14,8 @@ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ -Hello................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ +Hello +................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ From 2a6a4c73c11c9d2ccb0352288a0bcb1d01435971 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Sat, 10 Oct 2026 21:22:00 +0200 Subject: [PATCH 2/2] sed: test deferred newline write errors --- src/sed/named_writer.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/sed/named_writer.rs b/src/sed/named_writer.rs index ce73146d..494e5a07 100644 --- a/src/sed/named_writer.rs +++ b/src/sed/named_writer.rs @@ -146,4 +146,31 @@ mod tests { assert_eq!(fs::read(path).unwrap(), b"a\xE9"); } + + #[test] + fn test_write_line_bytes_reports_deferred_newline_error() { + let file = NamedTempFile::new().unwrap(); + let path = file.path().to_path_buf(); + let mut writer = NamedWriter { + path: path.clone(), + // Unbuffered writes to a read-only handle fail immediately. + writer: BufWriter::with_capacity(0, File::open(&path).unwrap()), + location: ScriptLocation { + input_name: Rc::from("script.sed"), + line_number: 3, + column_number: 7, + ..ScriptLocation::default() + }, + pending_newline: false, + }; + + writer.write_line_bytes(b"", false).unwrap(); + // Only the deferred newline has bytes to write. + let error = writer.write_line_bytes(b"", false).unwrap_err(); + assert_eq!(error.code(), 2); + let message = error.to_string(); + assert!(message.starts_with("script.sed:3:7: error: ")); + assert!(message.contains("writing to file")); + assert!(message.contains(path.to_str().unwrap())); + } }