Subject: bug#8154: du: issue with `--files0-from=DIR'

From: Jim Meyering <[email-masked]>
Date: Wed, 02 Mar 2011 16:09:04 +0100

Eric Blake wrote:

> [adding bug-gnulib]
>
> On 03/02/2011 06:28 AM, Stefan Vargyas wrote:
>> Dear maintainers,
>>
>> While building and running coreutils v8.9, I came across the
>> following issue of 'du':
>>
>>   $ mkdir /tmp/foo
>>   $ du --files0-from=/tmp/foo
>>   du: `/tmp/foo': read error: Is a directory
>>   ...
>>
>> The program enters an infinite loop

Thanks!  That affects the latest (coreutils-8.10), too.

> Thanks for the report.  This is indeed a bug.
>
> I wonder if the better fix would be to modify the gnulib argv-iter
> module to make argv_iter_init_stream to fail if fileno(fp) is a
> directory, since not all platforms reliably fail with EISDIR when doing
> read() on a directory (some, like BSD, successfully return EOF, and some
> older systems even read raw directory contents).

That's just what I've done.
Here's a tentative patch, both for du.c and argv-iter.c in gnulib.
A probably-identical change to the du.c part will be required for wc.c.
I'll add tests and update NEWS, too.

diff --git a/src/du.c b/src/du.c
index 671cac7..e205cd5 100644
--- a/src/du.c
+++ b/src/du.c
@@ -889,6 +889,8 @@ main (int argc, char **argv)
                quote (files_from));

       ai = argv_iter_init_stream (stdin);
+      if (ai == NULL && errno == EISDIR)
+        error (EXIT_FAILURE, errno, _("invalid file: %s"), quote (files_from));

       /* It's not easy here to count the arguments, so assume the
          worst.  */
diff --git a/lib/argv-iter.c b/lib/argv-iter.c
index 340e588..f0445f1 100644
--- a/lib/argv-iter.c
+++ b/lib/argv-iter.c
@@ -21,6 +21,8 @@

 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <errno.h>

 struct argv_iterator
 {
@@ -49,11 +51,21 @@ argv_iter_init_argv (char **argv)
 }

 /* Initialize to read from the stream, FP.
-   The input is expected to contain a list of NUL-delimited tokens.  */
+   The input is expected to contain a list of NUL-delimited tokens.
+   If FP refers to a directory, set errno to EISDIR and return NULL.  */
 struct argv_iterator *
 argv_iter_init_stream (FILE *fp)
 {
-  struct argv_iterator *ai = malloc (sizeof *ai);
+  struct argv_iterator *ai;
+  struct stat st;
+
+  if (fstat (fileno (fp), &st) == 0 && S_ISDIR (st.st_mode))
+    {
+      errno = EISDIR;
+      return NULL;
+    }
+
+  ai = malloc (sizeof *ai);
   if (!ai)
     return NULL;
   ai->fp = fp;