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.
This commit is contained in:
Paul Eggert
2024-04-19 21:44:32 -07:00
parent 9ab1f9db94
commit fb543b6b82
3 changed files with 32 additions and 5 deletions

3
NEWS
View File

@@ -12,6 +12,9 @@ GNU coreutils NEWS -*- outline -*-
have exited with a "Function not implemented" error. have exited with a "Function not implemented" error.
[bug introduced in coreutils-8.28] [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 ** Changes in behavior
ls's -f option now simply acts like -aU, instead of also ignoring ls's -f option now simply acts like -aU, instead of also ignoring

View File

@@ -760,7 +760,8 @@ free_lbuffers:
return ok; 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. This is a stripped down version of pipe_lines.
Return true if successful. */ 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 end_pos = -1;
off_t current_pos = -1; off_t current_pos = -1;
bool copy_from_current_pos = false;
if (! presume_input_pipe && n_bytes <= OFF_T_MAX) if (! presume_input_pipe && n_bytes <= OFF_T_MAX)
{ {
if (usable_st_size (&stats)) if (usable_st_size (&stats))
end_pos = stats.st_size; {
else if ((current_pos = lseek (fd, -n_bytes, SEEK_END)) != -1) /* Use st_size only if it's so large that this is
end_pos = current_pos + n_bytes; 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); return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
if (current_pos == -1) if (current_pos == -1)
current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename); current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);

View File

@@ -35,4 +35,14 @@ printf '123456' | tail -c3 > out || fail=1
printf '456' > exp || framework_failure_ printf '456' > exp || framework_failure_
compare exp out || fail=1 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 Exit $fail