bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Bug-gnulib] invalid use of errno after ferror


From: Bruno Haible
Subject: [Bug-gnulib] invalid use of errno after ferror
Date: Mon, 15 Sep 2003 21:20:54 +0200
User-agent: KMail/1.5

Hi,

In lib/mountlist.c I can see a reference to 'errno' after an ferror() call.
According to POSIX:2001 (see
http://www.opengroup.org/onlinepubs/007904975/functions/errno.html and
http://www.opengroup.org/onlinepubs/007904975/functions/ferror.html)
this is invalid. Grepping for ferror, I found the following files contain
suspicious uses of errno after ferror().

coreutils-5.0/lib/mountlist.c
coreutils-5.0/src/cksum.c
coreutils-5.0/src/comm.c
coreutils-5.0/src/csplit.c
coreutils-5.0/src/cut.c
coreutils-5.0/src/expand.c
coreutils-5.0/src/fold.c
coreutils-5.0/src/nl.c
coreutils-5.0/src/od.c
coreutils-5.0/src/paste.c
coreutils-5.0/src/pr.c
coreutils-5.0/src/sort.c
coreutils-5.0/src/sum.c
coreutils-5.0/src/tac.c
coreutils-5.0/src/tee.c
coreutils-5.0/src/unexpand.c
coreutils-5.0/src/uniq.c
coreutils-5.0/src/yes.c
diffutils-2.8.4/src/sdiff.c
grep-2.5a/src/grep.c
... and a few in GNU gettext too.

Remember that even malloc() can set errno (e.g. with glibc, when an mmap()
system call is attempted and it fails but less memory is still available).

In GNU gettext I was using the idiom

  /* Make sure nothing went wrong.  */
  if (fflush (fp) || ferror (fp))
    error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
           filename);

which obviously needs to be corrected to

  /* Make sure nothing went wrong.  */
  if (fwriteerror (fp))
    error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
           filename);

I'm adding the fwriteerror module to gnulib.

Also, in lib/exclude.c one assumes that ferror() doesn't modify errno;
this assumption is not valid in theory (strictly POSIX), but probably
still valid in practice (since ferror() is just a bit test in most stdio
implementations).

Bruno

=============================== fwriteerror.h ===============================
/* Detect write error on a stream.
   Copyright (C) 2003 Free Software Foundation, Inc.
   Written by Bruno Haible <address@hidden>, 2003.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* There are two approaches for detecting a write error on a stream opened
   for writing:

     (a) Test the return value of every fwrite() or fprintf() call, and react
         immediately.
     (b) Just before fclose(), test the error indicator in the stream and
         the return value of the final fflush() or fclose() call.

   The benefit of (a) is that non file related errors (such that ENOMEM during
   fprintf) and temporary error conditions can be diagnosed accurately.

   A theoretical benefit of (a) is also that, on POSIX systems, in the case of
   an ENOSPC error, errno is set and can be used by error() to provide a more
   accurate error message. But in practice, this benefit is not big because
   users can easily figure out by themselves why a file cannot be written to,
   and furthermore the function fwriteerror() can provide errno as well.

   The big drawback of (a) is extensive error checking code: Every function
   which does stream output must return an error indicator.

   This file provides support for (b).  */

#include <stdio.h>

/* Write out the not yet written buffered contents of the stream FP, and then
   test whether some error occurred on the stream FP.  FP must be a stream
   opened for writing.
   Return 0 if no error occurred.  In this case it can be assumed that
   fclose (fp) will succeed.
   Return -1 and set errno if there was an error.  The errno value will be 0
   if the cause of the error cannot be determined.
 */
extern int fwriteerror (FILE *fp);

=============================== fwriteerror.c ===============================
/* Detect write error on a stream.
   Copyright (C) 2003 Free Software Foundation, Inc.
   Written by Bruno Haible <address@hidden>, 2003.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif

/* Specification.  */
#include "fwriteerror.h"

#include <errno.h>

int
fwriteerror (FILE *fp)
{
  /* Need to
     1. test the error indicator of the stream,
     2. flush the buffers (what fclose() would do), testing for error again.
     We can equally well swap these steps; this leads to smaller code.  */

  /* Clear errno, so that on non-POSIX systems the caller doesn't see a
     wrong value of errno when we return -1.  */
  errno = 0;

  if (fflush (fp))
    return -1; /* errno is set here */

  if (ferror (fp))
    {
      /* The stream had an error earlier, but its errno was lost.  If the
         error was not temporary, we can get the same errno by writing and
         flushing one more byte.  We can do so because at this point the
         stream's contents is garbage anyway.  */
      if (fputc ('\0', fp) == EOF)
        return -1; /* errno is set here */
      if (fflush (fp))
        return -1; /* errno is set here */
      /* Give up on errno.  */
      errno = 0;
      return -1;
    }

  return 0;
}


#if TEST

/* Name of a file on which writing fails.  On systems without /dev/full,
   you can choose a filename on a full filesystem.  */
#define UNWRITABLE_FILE "/dev/full"

int
main ()
{
  static int sizes[] =
    {
       511,  512,  513,
      1023, 1024, 1025,
      2047, 2048, 2049,
      4095, 4096, 4097,
      8191, 8192, 8193
    };
  static char dummy[8193];
  unsigned int i, j;

  for (i = 0; i < sizeof (sizes) / sizeof (sizes[0]); i++)
    {
      size_t size = sizes[i];

      for (j = 0; j < 2; j++)
        {
          /* Run a test depending on i and j:
             Write size bytes and then calls fflush if j==1.  */
          FILE *stream = fopen (UNWRITABLE_FILE, "w");

          if (stream == NULL)
            {
              fprintf (stderr, "Test %u:%u: could not open file\n", i, j);
              continue;
            }

          fwrite (dummy, 347, 1, stream);
          fwrite (dummy, size - 347, 1, stream);
          if (j)
            fflush (stream);

          if (fwriteerror (stream) == -1)
            {
              if (errno != ENOSPC)
                fprintf (stderr, "Test %u:%u: fwriteerror ok, errno = %d\n",
                         i, j, errno);
            }
          else
            fprintf (stderr, "Test %u:%u: fwriteerror found no error!\n",
                     i, j);

          if (fclose (stream))
            fprintf (stderr, "Test %u:%u: fclose failed, errno = %d\n",
                     i, j, errno);
        }
    }

  return 0;
}

#endif





reply via email to

[Prev in Thread] Current Thread [Next in Thread]