On 10/11/2026 09:50, Bruno Haible via GNU coreutils General Discussion wrote:
In today's CI, the tail/append-to-input test fails on Alpine Linux.
It failed on Alpine due to its different buffering,
but would also fail on any system as the file sizes were increased.
Given that the behavior is inconsistent based on buffering, file size,
and if tailing the end of a file, which is already bounded,
I'll probably add the attached to bound the output when skipping
from the start also (only when the output and input are the same).

cheers,
Padraig.
From 929b2e01239d210ab7940acb4240104fbeba50bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Tue, 10 Nov 2026 19:24:56 +0000
Subject: [PATCH] tail: ensure bounded operation when appending to input

`tail -n2 in >> in` already operated in a bounded manner,
so this makes tailing from the start with `tail -n+2 in >> in`
bounded also, operating only on the original file contents
and not increasing the size of 'in' forever.

Note this bounded operation does not apply with -f.

* src/tail.c (remaining_input): A new helper to return
the limit of the original file when output is the same file.
(tail_bytes): Use the new limit from remaining_input().
(tail_lines): Likewise.
* tests/tail/append-to-input.sh: Adjust accordingly.
Note this test was failing on Alpine due to its
different buffering behavior.  We're now consistent
over all file sizes and buffering behaviour in this case.
* NEWS: Mention the improvement.
Test failure reported by Bruno Haible.
---
 NEWS                          |  5 +++++
 src/tail.c                    | 21 +++++++++++++++++++--
 tests/tail/append-to-input.sh |  4 ++--
 3 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index c7d8dc392..3361336ef 100644
--- a/NEWS
+++ b/NEWS
@@ -345,6 +345,11 @@ GNU coreutils NEWS                                    -*- outline -*-
   'tail -F' is more responsive to changes when following many file names,
   when using inotify.
 
+  'tail -n+...' and 'tail -c+...', i.e., when tailing based from the start
+  of a file, now limit output to the original file contents if appending
+  output to the same file.  This is consistent with the existing bounded
+  operation when tailing based from the end of a file.
+
   'wc -m' counts ASCII up to 14x faster, and UTF-8 up to 7x faster.
 
   Commands that support hardware acceleration like 'cksum' and 'wc',
diff --git a/src/tail.c b/src/tail.c
index 32d4d26e0..c67c2546a 100644
--- a/src/tail.c
+++ b/src/tail.c
@@ -1906,6 +1906,23 @@ tail_forever_inotify (int wd, struct File_spec *f, int n_files,
 }
 #endif
 
+/* When copying a regular file to itself, stop at its original end recorded
+   in ST rather than reading our own output.  Otherwise, copy until EOF.  */
+static count_t
+remaining_input (int fd, struct stat const *st)
+{
+  struct stat out;
+  if (S_ISREG (st->st_mode)
+      && fstat (STDOUT_FILENO, &out) == 0
+      && SAME_INODE (*st, out))
+    {
+      off_t pos = lseek (fd, 0, SEEK_CUR);
+      if (0 <= pos)
+        return pos < st->st_size ? st->st_size - pos : 0;
+    }
+  return COPY_TO_EOF;
+}
+
 /* Output the last bytes of the file PRETTYNAME open for reading
    in FD and with status ST.  Output the last N_BYTES bytes.
    Return (-1 - errno) on failure, otherwise the resulting file offset
@@ -1928,7 +1945,7 @@ tail_bytes (char const *prettyname, int fd, struct stat const *st,
           if (t < 0)
             return t;
         }
-      n_bytes = COPY_TO_EOF;
+      n_bytes = remaining_input (fd, st);
     }
   else
     {
@@ -2011,7 +2028,7 @@ tail_lines (char const *prettyname, int fd, struct stat const *st,
       int t = start_lines (prettyname, fd, n_lines);
       if (t < 0)
         return t;
-      dump_remainder (false, prettyname, fd, COPY_TO_EOF);
+      dump_remainder (false, prettyname, fd, remaining_input (fd, st));
       return -1;
     }
   else
diff --git a/tests/tail/append-to-input.sh b/tests/tail/append-to-input.sh
index ea7fa7fbb..cc0453ee5 100755
--- a/tests/tail/append-to-input.sh
+++ b/tests/tail/append-to-input.sh
@@ -22,8 +22,8 @@ print_ver_ tail seq
 # Cap the file size so a runaway tail cannot fill the file system.
 (ulimit -f 10000) 2>/dev/null || skip_ 'unable to limit file size'
 
-# A file larger than a buffer, read from the end.
-for opt in -n1 -c4; do
+# A file larger than a buffer, so self-appending cannot rely on buffering.
+for opt in -n1 -c4 -n+2 -c+3; do
   seq 100000 > in || framework_failure_
   cp in exp || framework_failure_
   tail $opt in >> exp || framework_failure_
-- 
2.56.0

Reply via email to