This change is performance neutral as tested with:
$ yes | head -n10M > sl.in
$ yes $(yes éééááé | head -n9 | paste -s -d,) | head -n1M > mb.in
$ hyperfine src/cut{,.orig}' -c1 sl.in >/dev/null'
$ hyperfine src/cut{,.orig}' -f1 -w mb.in >/dev/null'
* src/cut.c (skip_buffered_line): A new refactored helper.
* src/cut.c (scan_mb_line_end): Use help, and also call mbbuf_avail()
to process remaining bytes before topup.
(cut_characters_mode): Likewise. Note the topup is done implicitly
in the following mbbuf_get_char().
* NEWS: Mention the bug fix.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-VRMLC6DR.
---
NEWS | 5 +++++
src/cut.c | 30 ++++++++++++++++++++----------
2 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/NEWS b/NEWS
index ffe134e76..c64f7b481 100644
--- a/NEWS
+++ b/NEWS
@@ -49,6 +49,11 @@ GNU coreutils NEWS -*-
outline -*-
the \u0085 (next line) character as a separator on non-GLIBC platforms.
[bug introduced in coreutils-9.11]
+ 'cut -w', and 'cut -d' in some multi-byte locales, no longer delay output
+ of an already read line until more input arrives, when fewer than 4 bytes
+ follow the last selected field.
+ [bug introduced in coreutils-9.12]
+
'chown -v' and 'chgrp -v' no longer emit uninitialized user and group IDs
when
operating on dangling symbolic links.
[bug introduced in coreutils-5.3.0]
diff --git a/src/cut.c b/src/cut.c
index 8e7ed3fac..b438f661f 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -442,6 +442,18 @@ search_bytes (char const *buf, int c, size_t n_bytes)
return 2 < n_bytes ? (char *) memchr (buf + 2, c, n_bytes - 2) : NULL;
}
+/* Consume buffered bytes through the next line delimiter, if present.
+ Return whether a delimiter was consumed. Do not refill the buffer. */
+
+static inline bool
+skip_buffered_line (mbbuf_t *mbbuf, idx_t available)
+{
+ char *p = mbbuf->buffer + mbbuf->offset;
+ char *end = search_bytes (p, line_delim, available);
+ mbbuf_advance (mbbuf, end ? end - p + 1 : available);
+ return end != NULL;
+}
+
static inline void
write_line_delim (void)
{
@@ -591,14 +603,13 @@ scan_mb_line_end (mbbuf_t *mbbuf, struct mbfield_parser
*parser,
while (true)
{
- idx_t available = mbbuf_topup (mbbuf);
+ idx_t available = mbbuf_avail (mbbuf);
+ if (available == 0)
+ available = mbbuf_topup (mbbuf);
if (available == 0)
return FIELD_EOF;
- char *buffer = mbbuf->buffer + mbbuf->offset;
- char *line_end = search_bytes (buffer, line_delim, available);
- idx_t n = line_end ? line_end - buffer + 1 : available;
- mbbuf_advance (mbbuf, n);
+ bool line_end = skip_buffered_line (mbbuf, available);
*have_pending_line = true;
if (line_end)
@@ -929,11 +940,10 @@ cut_characters_mode (FILE *stream, bool byte_mode)
/* Skip unselected line suffixes without decoding each character. */
if (idx && field_selection_exhausted (idx))
{
- idx_t available = mbbuf_topup (&mbbuf);
- char *p = mbbuf.buffer + mbbuf.offset;
- char *end = search_bytes (p, line_delim, available);
- mbbuf_advance (&mbbuf, end ? end - p : available);
- if (!end && available)
+ idx_t available = mbbuf_avail (&mbbuf);
+ if (skip_buffered_line (&mbbuf, available))
+ reset_item_line (&idx, &print_delimiter);
+ if (available)
continue;
}
--
2.56.0