Ideally, this would be two commits. One for the missing NEW_FILE_FLAG
check, and the other for the off-by-one. Given that the fixes are
small, I figured it would be more beneficial to keep each OSS scanner
issue related to one commit. I'm not 100% on it, though.
-- 8< --
* src/split.c (bytes_split): Don't lseek when the offset is already at
the correct position from reading. Don't add 1 to TO_WRITE.
* tests/split/filter.sh: Add two test cases.
* NEWS: Mention the bug fixes.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-D5DVHK7A.
---
NEWS | 9 +++++++++
src/split.c | 9 +++++++--
tests/split/filter.sh | 20 ++++++++++++++++++++
3 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/NEWS b/NEWS
index 382ac0c41..7e0e92ed8 100644
--- a/NEWS
+++ b/NEWS
@@ -225,6 +225,15 @@ GNU coreutils NEWS -*-
outline -*-
accepting input or the entire input is processed.
[bug introduced with --filter option in coreutils-8.13]
+ 'split --bytes N --filter' no longer skips a whole chunk of input when a
+ filter exits without reading all of a chunk ending at an internal buffer
+ boundary.
+ [bug introduced in coreutils-8.28]
+
+ 'split -n N' no longer shortens subsequent chunks by one byte after a filter
+ exits before it finishes reading all of its input.
+ [bug introduced in coreutils-9.2]
+
'stty' again rejects empty speed arguments.
Previously such arguments were accepted as value 0.
[bug introduced in coreutils-9.8]
diff --git a/src/split.c b/src/split.c
index ea410dc9f..936f8d5ee 100644
--- a/src/split.c
+++ b/src/split.c
@@ -742,10 +742,15 @@ bytes_split (intmax_t n_bytes, intmax_t rem_bytes,
}
else
{
- if (! filter_ok
+ /* If the filter for the current chunk has stopped reading,
+ skip the rest of that chunk. If the failed write is at
+ the end of a chunk, NEW_FILE_FLAG will be true. In that
+ case the lseek is not needed and TO_WRITE refers to the
+ next chunk. */
+ if (! filter_ok && ! new_file_flag
&& 0 <= lseek (STDIN_FILENO, to_write, SEEK_CUR))
{
- to_write = n_bytes + (opened + 1 < rem_bytes);
+ to_write = n_bytes + (opened < rem_bytes);
new_file_flag = true;
}
diff --git a/tests/split/filter.sh b/tests/split/filter.sh
index 90ee3a5f2..a38d98720 100755
--- a/tests/split/filter.sh
+++ b/tests/split/filter.sh
@@ -84,6 +84,26 @@ for mode in '' -u; do
compare exp-xaa xaa || fail=1
compare exp-xab xab || fail=1
done
+rm -f x?? || framework_failure_
+
+# Make sure chunks aren't skipped if a filter exits early at the end
+# of an input buffer.
+for c in a b c d e f g; do
+ yes $c | tr -d '\n' | head -c 256k || framework_failure_
+done >in
+split --bytes 256k ---io-blksize=256k --filter='head -c1 >$FILE' in || fail=1
+# coreutils-8.28 to coreutils-9.12 would produce "aceg" here.
+test "$(cat x??)" = abcdefg || fail=1
+rm -f x?? || framework_failure_
+
+# Make sure that --number chunk sizes aren't affected by a filter exiting
early.
+{ yes a | tr -d '\n' | head -c 300001 &&
+ yes b | tr -d '\n' | head -c 300001 &&
+ yes c | tr -d '\n' | head -c 300000; } >in || framework_failure_
+split --number 3 ---io-blksize=256k --filter='head -c1 >$FILE' in || fail=1
+# coreutils-9.2 to coreutils-9.12 would produce "abb" here.
+test "$(cat x??)" = abc || fail=1
+rm -f x?? || framework_failure_
# Ensure that "endless" input _is_ processed for unbounded number of filters
for buf in 1000 1000000; do
--
2.56.0