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


Reply via email to