There are a few invocations of 'tail', that I don't see any real use
for, that can run until your disk space is exhausted.
The first one is very simple:
$ echo a > a
$ timeout -v 1m tail -f a >> a
timeout: sending signal TERM to command ‘tail’
$ du --human-readable a
19M a
The second one isn't that much more complicated. You just need to
figure out the buffer sizes such that appending the bytes will cause
subsequent reads never to reach EOF:
$ yes a | head -n 4096 | tr -d '\n' > a
$ timeout -v 10 tail -c +1 a >> a
$ yes a | head -n 4097 | tr -d '\n' > a
$ timeout -v 10 tail -c +1 a >> a
timeout: sending signal TERM to command ‘tail’
$ du --human-readable a
4.0G a
I think it would be better to behave like 'cat' here and catch
invocations like this.
However, I left this as an RFC without tests or a NEWS entry to get
opinions since this likely violates POSIX. For 'cat' it says [1]:
However, I left this as an RFC without tests or a NEWS entry to
get opinions since this likely violates POSIX.
There isn't any mention similar to this for 'tail'. I interpret that
as meaning that it should append to the file, even if it simply fills
the disk. But perhaps my interpretation is wrong.
[1] https://pubs.opengroup.org/onlinepubs/9799919799/utilities/cat.html
-- 8< --
* src/tail.c (tail_file): Emit an error when appending to the input
file.
(main): Call fstat on standard output and pass it to tail_file.
---
src/tail.c | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/src/tail.c b/src/tail.c
index aa612e5e4..eb708e9dd 100644
--- a/src/tail.c
+++ b/src/tail.c
@@ -1996,7 +1996,8 @@ tail (char const *filename, int fd, struct stat const
*st, count_t n_units)
Return true if successful. */
static bool
-tail_file (struct File_spec *f, count_t n_files, count_t n_units)
+tail_file (struct File_spec *f, count_t n_files, count_t n_units,
+ struct stat const *ostat_bufp)
{
int fd;
bool ok;
@@ -2044,7 +2045,27 @@ tail_file (struct File_spec *f, count_t n_files, count_t
n_units)
f->errnum = errno;
error (0, f->errnum, _("cannot fstat %s"), quoteaf (f->prettyname));
}
- else
+ else if (! (S_ISFIFO (stats.st_mode) || S_ISSOCK (stats.st_mode)
+ || S_TYPEISSHM (&stats) || S_TYPEISTMO (&stats))
+ && ! (S_TYPEISSHM (ostat_bufp) || S_TYPEISTMO (ostat_bufp))
+ && PSAME_INODE (&stats, ostat_bufp))
+ {
+ off_t in_pos = lseek (fd, 0, SEEK_CUR);
+ if (0 <= in_pos)
+ {
+ int out_flags = fcntl (STDOUT_FILENO, F_GETFL);
+ int whence = (0 <= out_flags && out_flags & O_APPEND
+ ? SEEK_END : SEEK_CUR);
+ if (in_pos < lseek (STDOUT_FILENO, 0, whence))
+ {
+ error (0, 0, _("%s: input file is output file"),
+ quotef (f->prettyname));
+ ok = false;
+ }
+ }
+ }
+
+ if (ok)
{
read_pos = tail (f->prettyname, fd, &stats, n_units);
ok = -1 <= read_pos;
@@ -2450,8 +2471,12 @@ main (int argc, char **argv)
xset_binary_mode (STDOUT_FILENO, O_BINARY);
+ struct stat ostat_buf;
+ if (fstat (STDOUT_FILENO, &ostat_buf) < 0)
+ error (EXIT_FAILURE, errno, _("standard output"));
+
for (int i = 0; i < n_files; i++)
- ok &= tail_file (&F[i], n_files, n_units);
+ ok &= tail_file (&F[i], n_files, n_units, &ostat_buf);
if (forever && ignore_fifo_and_pipe (F, n_files))
{
--
2.55.0