On 2024-04-19 11:40, Ionut Nicula wrote:
The following command hangs indefinitely, as expected:
tail -c 4096 /dev/random
The following command immediately returns 4097 bytes, unexpectedly:
tail -c 4097 /dev/random
Thanks for the bug report. Although the two commands should behave
similarly, neither should loop: they should both output the requested
number of random bytes. Similarly for /dev/zero. I installed the
attached patch to do that.From fb543b6b82c1f3a20ff88f44cc3ed367bfe811b6 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Fri, 19 Apr 2024 21:44:32 -0700
Subject: [PATCH] tail: avoid infloop with -c on /dev/zero
Problem reported by Ionut Nicula in:
https://bugs.gnu.org/70477
* src/tail.c (tail_bytes): Do not loop forever on commands
like 'tail -c 4096 /dev/zero'.
* tests/tail/tail-c.sh: Test this fix.
---
NEWS | 3 +++
src/tail.c | 24 +++++++++++++++++++-----
tests/tail/tail-c.sh | 10 ++++++++++
3 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/NEWS b/NEWS
index 43ce84d7e..389f72516 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,9 @@ GNU coreutils NEWS -*- outline -*-
have exited with a "Function not implemented" error.
[bug introduced in coreutils-8.28]
+ 'tail -c 4096 /dev/zero' no longer loops forever.
+ [This bug was present in "the beginning".]
+
** Changes in behavior
ls's -f option now simply acts like -aU, instead of also ignoring
diff --git a/src/tail.c b/src/tail.c
index 52c081031..a3b46ca2d 100644
--- a/src/tail.c
+++ b/src/tail.c
@@ -760,7 +760,8 @@ free_lbuffers:
return ok;
}
-/* Print the last N_BYTES characters from the end of pipe FD.
+/* Print the last N_BYTES characters from the end of FD.
+ Work even if the input is a pipe.
This is a stripped down version of pipe_lines.
Return true if successful. */
@@ -1875,15 +1876,28 @@ tail_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes,
{
off_t end_pos = -1;
off_t current_pos = -1;
+ bool copy_from_current_pos = false;
if (! presume_input_pipe && n_bytes <= OFF_T_MAX)
{
if (usable_st_size (&stats))
- end_pos = stats.st_size;
- else if ((current_pos = lseek (fd, -n_bytes, SEEK_END)) != -1)
- end_pos = current_pos + n_bytes;
+ {
+ /* Use st_size only if it's so large that this is
+ probably not a /proc or similar file, where st_size
+ is notional. */
+ end_pos = stats.st_size;
+ off_t smallish_size = STP_BLKSIZE (&stats);
+ copy_from_current_pos = smallish_size < end_pos;
+ }
+ else
+ {
+ current_pos = lseek (fd, -n_bytes, SEEK_END);
+ copy_from_current_pos = current_pos != -1;
+ if (copy_from_current_pos)
+ end_pos = current_pos + n_bytes;
+ }
}
- if (end_pos <= (off_t) STP_BLKSIZE (&stats))
+ if (! copy_from_current_pos)
return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
if (current_pos == -1)
current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
diff --git a/tests/tail/tail-c.sh b/tests/tail/tail-c.sh
index f518e5b21..a9f2bc2d1 100755
--- a/tests/tail/tail-c.sh
+++ b/tests/tail/tail-c.sh
@@ -35,4 +35,14 @@ printf '123456' | tail -c3 > out || fail=1
printf '456' > exp || framework_failure_
compare exp out || fail=1
+# Any part of /dev/zero should be valid for tail -c.
+head -c 4096 /dev/zero >exp || fail=1
+tail -c 4096 /dev/zero >out || fail=1
+compare exp out || fail=1
+
+# Any part of /dev/urandom, if it exists, should be valid for tail -c.
+if test -r /dev/urandom; then
+ timeout --verbose 1 tail -c 4096 /dev/urandom >/dev/null || fail=1
+fi
+
Exit $fail
--
2.40.1