From 6f848041c6b07c8b4799a1f6bc66d9246542204f Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Fri, 13 Mar 2026 08:50:10 +0530 Subject: [PATCH 1/4] fold: fix crash when reading from pseudo-devices squashme: wraparound struct for fold squashme: fix crash when reading from pseudo-devices Fixes #11291 --- src/uu/fold/src/fold.rs | 80 ++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 76ef483bf31..8c028b05613 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 - { - break; + // 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); } - if line == [NL] { - output.write_all(&[NL])?; - line.clear(); - continue; + // The width exceeds the line read so far if we have + // reached EOF. + if width > line.len() { + output.write_all(&line)?; + break; } - 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; - } + let chunk = &line[..width]; + let newline_end = chunk.iter().position(|c| NL.eq(c)).map(|v| v + 1); - i += slice.len(); + let space_end = chunk + .iter() + .rposition(|c| spaces && c.is_ascii_whitespace() && !CR.eq(c)) + .map(|v| v + 1); - let at_eol = i >= len; + let end = newline_end.or(space_end).unwrap_or(width); + let slice = &line[..end]; - if at_eol { - output.write_all(slice)?; - } else { - output.write_all(slice)?; - output.write_all(&[NL])?; - } + 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(); + line.drain(..end); } - Ok(()) } From 2b4fbd94ac0cd866b7e014b55340aeee2ab33020 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:48:53 +0530 Subject: [PATCH 2/4] tests/fold: add tests to read from pseudo device streams --- tests/by-util/test_fold.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index c371ba4ca58..3d58a3af15f 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -889,6 +889,18 @@ fn test_bytewise_carriage_return_is_not_word_boundary() { .succeeds() .stdout_is("fizz\rb\nuzz\rfi\nzzbuzz"); // spell-checker:disable-line } + +#[test] +fn test_bytewise_read_from_pseudo_device() { + let mut child = new_ucmd!().arg("-b").arg("/dev/zero").run_no_wait(); + + let timeout = std::time::Duration::from_millis(100); + std::thread::sleep(timeout); + child.close_stdin(); + assert!(child.stdout_all().contains("\x00\x0a")); + assert!(child.stderr_all().is_empty()); +} + #[test] fn test_obsolete_syntax() { new_ucmd!() From 06a473ff21be56110083b4143035c02ab9c7a9f5 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:11:13 +0530 Subject: [PATCH 3/4] tests/fold: kill child process before making asserts --- tests/by-util/test_fold.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index 3d58a3af15f..fec4243a912 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -894,11 +894,14 @@ fn test_bytewise_carriage_return_is_not_word_boundary() { fn test_bytewise_read_from_pseudo_device() { let mut child = new_ucmd!().arg("-b").arg("/dev/zero").run_no_wait(); - let timeout = std::time::Duration::from_millis(100); - std::thread::sleep(timeout); - child.close_stdin(); - assert!(child.stdout_all().contains("\x00\x0a")); - assert!(child.stderr_all().is_empty()); + child.make_assertion_with_delay(100).is_alive(); + + child + .kill() + .make_assertion() + .with_all_output() + .stdout_contains_bytes(b"\x00\x0a") + .no_stderr(); } #[test] From d59d98e9d958dea491b0150e77fa905ce1a4e200 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:10:23 +0530 Subject: [PATCH 4/4] tests/fold: test fold on pseudo devices only for bsds and linux --- tests/by-util/test_fold.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index fec4243a912..2d863ffa43f 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -890,6 +890,7 @@ fn test_bytewise_carriage_return_is_not_word_boundary() { .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();