From e6a6cb6399ad7f12302d6bd0aefd26f8133e86a8 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:55:52 +0530 Subject: [PATCH] fold: fix crash when reading from pseudo-devices (#11314) * fold: fix crash when reading from pseudo-devices squashme: wraparound struct for fold squashme: fix crash when reading from pseudo-devices Fixes #11291 * tests/fold: add tests to read from pseudo device streams * tests/fold: kill child process before making asserts * tests/fold: test fold on pseudo devices only for bsds and linux --- src/uu/fold/src/fold.rs | 86 +++++++++++++++----------------------- tests/by-util/test_fold.rs | 16 +++++++ 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index e7b168857..f07c2b393 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -194,64 +194,46 @@ fn fold_file_bytewise( let mut line = Vec::new(); loop { - if file - .read_until(NL, &mut line) - .map_err_context(|| translate!("fold-error-readline"))? - == 0 - { + // Ensures that our line always has enough bytes to either + // come across a newline, a whitespace or just wrap around. + while width > line.len() { + let buf = file + .fill_buf() + .map_err_context(|| translate!("fold-error-readline"))?; + if buf.is_empty() { + break; + } + line.extend_from_slice(buf); + let len = buf.len(); + file.consume(len); + } + + // The width exceeds the line read so far if we have + // reached EOF. + if width > line.len() { + output.write_all(&line)?; break; } - if line == [NL] { + let chunk = &line[..width]; + let newline_end = chunk.iter().position(|c| NL.eq(c)).map(|v| v + 1); + + let space_end = chunk + .iter() + .rposition(|c| spaces && c.is_ascii_whitespace() && !CR.eq(c)) + .map(|v| v + 1); + + let end = newline_end.or(space_end).unwrap_or(width); + let slice = &line[..end]; + + output.write_all(slice)?; + let slice_ends_without_newline = line[end - 1] != NL; + let no_newline_follows_slice = line.get(end).is_some_and(|c| *c != NL); + if slice_ends_without_newline && no_newline_follows_slice { output.write_all(&[NL])?; - line.clear(); - continue; } - - let len = line.len(); - let mut i = 0; - - while i < len { - let width = if len - i >= width { width } else { len - i }; - let slice = { - let slice = &line[i..i + width]; - if spaces && i + width < len { - match slice - .iter() - .enumerate() - .rev() - .find(|(_, c)| c.is_ascii_whitespace() && **c != CR) - { - Some((m, _)) => &slice[..=m], - None => slice, - } - } else { - slice - } - }; - - // Don't duplicate trailing newlines: if the slice is "\n", the - // previous iteration folded just before the end of the line and - // has already printed this newline. - if slice == [NL] { - break; - } - - i += slice.len(); - - let at_eol = i >= len; - - if at_eol { - output.write_all(slice)?; - } else { - output.write_all(slice)?; - output.write_all(&[NL])?; - } - } - - line.clear(); + line.drain(..end); } - Ok(()) } diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index c371ba4ca..2d863ffa4 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -889,6 +889,22 @@ fn test_bytewise_carriage_return_is_not_word_boundary() { .succeeds() .stdout_is("fizz\rb\nuzz\rfi\nzzbuzz"); // spell-checker:disable-line } + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[test] +fn test_bytewise_read_from_pseudo_device() { + let mut child = new_ucmd!().arg("-b").arg("/dev/zero").run_no_wait(); + + child.make_assertion_with_delay(100).is_alive(); + + child + .kill() + .make_assertion() + .with_all_output() + .stdout_contains_bytes(b"\x00\x0a") + .no_stderr(); +} + #[test] fn test_obsolete_syntax() { new_ucmd!()