tail -f < /etc/passwd

Version:

coreutils-7.5 (fixed in 7.6)

How to reproduce?

coreutils-7.5/src/tail -f < /etc/passwd

Symptom:

Early termination.

tail -f < /etc/passwd

will produce the following output:

$ ./tail -f < /etc/passwd

./tail: cannot watch `-': No such file or directory

and quit, instead of sleep and wait.

Root cause:

Forgot to check for a corner case: it should not call ‘tail_forever_inotify’ with file set to “-”. Within ‘tail_forever_inotify’, it calls inotify_add_watch system call with “-”, and logged the error return.

During the above buggy execution, the flow is as following:

main

  file = “-” // This is to indicate that the input is from stdin

  -> tail_file (“-”...) // Things are fine...

  // Did not change anything to ‘file’

  -> tail_forever_inotify (“-”...)

1222:      -> inotify_add_watch(“-” ...) error return

1227:      -> error (0, errno, _("cannot watch %s"), quote (f[i].name));

tail_forever_intify (“-”...) {

  ... ...

  /* inotify_add_watch is a system call.

     see http://linux.die.net/man/2/inotify_add_watch */

  f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask);

  /* error return from the inotify_add_watch.. */

  if (f[i].wd < 0) {

       if (errno != f[i].errnum)

          /* The log message was printed here. */

          error (0, errno, _("cannot watch %s"), quote (f[i].name));

             continue;

     }

}

So before it calls tail_forever_inotify, it didn’t change anything to the file “-”.

It should not call ‘inotify_add_watch’ with “-” at all!

The following code is in the fixed version, where it ended up not calling ‘tail_forever_inotify’.

+  size_t n_viable = 0;

+  for (i = 0; i < n_files; i++) {

+     bool is_a_fifo_or_pipe =

+       (STREQ (F[i].name, "-")

+        && !F[i].ignore

+        && 0 <= F[i].fd

+        && (S_ISFIFO (F[i].mode)

+            || (HAVE_FIFO_PIPES != 1 && isapipe (F[i].fd))));

+     if (is_a_fifo_or_pipe)

+       F[i].ignore = true;

+     else

+       ++n_viable;

+   }

-  if (forever)

+  if (forever && n_viable)

   {

#if HAVE_INOTIFY

     /* If the user specifies stdin via a command line argument of "-",

        or implicitly by providing no arguments, we won't use inotify.

        Technically, on systems with a working /dev/stdin, we *could*,

        but would it be worth it?  Verifying that it's a real device

        and hooked up to stdin is not trivial, while reverting to

        non-inotify-based tail_forever is easy and portable.  */

     bool stdin_cmdline_arg = false;

     for (i = 0; i < n_files; i++)

       if (!F[i].ignore && STREQ (F[i].name, "-"))

         stdin_cmdline_arg = true;

     if (!disable_inotify && !stdin_cmdline_arg)

       {

         int wd = inotify_init ();

         if (wd < 0)

           error (0, errno, _("inotify cannot be used, reverting to polling"));

         else

           {

             /* Flush any output from tail_file, now, since

                tail_forever_inotify flushes only after writing,

                not before reading.  */

             if (fflush (stdout) != 0)

               error (EXIT_FAILURE, errno, _("write error"));

             tail_forever_inotify (wd, F, n_files, sleep_interval);

             /* The only way the above returns is upon failure.  */

             exit (EXIT_FAILURE);

           }

       }

#endif

Is there Error Message?

Yes.

Can developers/Errlog anticipate error and put a magic error message?

No. Otherwise they can simply fix the bug!