* src/pr.c: Continue processing if the line is truncated instead of
returning early. Return after handling headers, line numbers, etc., but
before printing the truncated clump.
* tests/pr/inf-loop.sh: New file.
* tests/local.mk (all_tests): Add the new test case.
* NEWS: Mention the bug fix.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-TMZPQ2AH.
---
NEWS | 5 +++++
src/pr.c | 15 ++++++++++-----
tests/local.mk | 1 +
tests/pr/inf-loop.sh | 37 +++++++++++++++++++++++++++++++++++++
4 files changed, 53 insertions(+), 5 deletions(-)
create mode 100755 tests/pr/inf-loop.sh
diff --git a/NEWS b/NEWS
index 728529c31..5e9360c23 100644
--- a/NEWS
+++ b/NEWS
@@ -142,6 +142,11 @@ GNU coreutils NEWS -*-
outline -*-
than INT_MAX bytes of column data for a single page.
[This bug was present in "the beginning".]
+ 'pr' no longer loops infinitely when the first character of each line in a
row
+ is wider than a column. E.g., previously, "yes $'\t' | head -n 56 | pr -W
7'"
+ would loop infinitely instead of printing an empty page.
+ [This bug was present in "the beginning".]
+
'realpath' without -s, or 'readlink' with -e,-f,-m no longer
loop infinitely when resolving a symbolic link which
resolves to a path beginning with the symbolic link itself.
diff --git a/src/pr.c b/src/pr.c
index e0140e567..561e51696 100644
--- a/src/pr.c
+++ b/src/pr.c
@@ -2477,11 +2477,13 @@ read_line (COLUMN *p)
chars = char_to_clump (c);
}
- if (truncate_lines && input_position > chars_per_column)
- {
- input_position = last_input_position;
- return false;
- }
+ /* If the first clump does not fit in the column, account for it but do
+ not print it. We must still handle headers, line numbers, etc., so
+ continue instead of returning. */
+ bool const first_clump_fits = ! (truncate_lines
+ && chars_per_column < input_position);
+ if (! first_clump_fits)
+ input_position = last_input_position;
if (p->char_func != store_char)
{
@@ -2525,6 +2527,9 @@ read_line (COLUMN *p)
if (c == '\n')
return true;
+ if (! first_clump_fits)
+ return false;
+
print_clump (p, chars, clump_buff);
while (true)
diff --git a/tests/local.mk b/tests/local.mk
index 33d830cd6..b03ea397e 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -307,6 +307,7 @@ all_tests = \
tests/mktemp/write-error.sh \
tests/misc/arch.sh \
tests/pr/bounded-memory.sh \
+ tests/pr/inf-loop.sh \
tests/pr/options.sh \
tests/pr/pr-tests.pl \
tests/pwd/argument.sh \
diff --git a/tests/pr/inf-loop.sh b/tests/pr/inf-loop.sh
new file mode 100755
index 000000000..77fb3ae2c
--- /dev/null
+++ b/tests/pr/inf-loop.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Test a case that would make 'pr' infinite loop in coreutils-9.12 and earlier.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ pr
+
+# Test that we don't have an infinite loop when the input line length is longer
+# than the page width.
+yes "$(printf '\t')" | head -n 100 >inp &&
+tr -d '\t' <inp >exp || framework_failure_
+pr -t -W 7 inp >out 2>err || fail=1
+compare exp out || fail=1
+compare /dev/null err || fail=1
+
+# Make sure we don't lose line numbers.
+truncate -s 0 exp || framework_failure_
+for i in $(seq 100); do printf '%5d\t\n' "$i" >>exp || framework_failure_; done
+pr -n -t -W 7 inp >out 2>err || fail=1
+compare exp out || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
--
2.56.0