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 <egg...@cs.ucla.edu> 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