Derek Price <[EMAIL PROTECTED]> writes:

>>You have to include stdio.h first, then #undef fwrite, then
>>#define fwrite.  It can be done (Emacs did it, after all), but I don't
>>recommend it.
>>
> Isn't this the standard behavior for a GNULIB module's header file? 
> This is almost exactly what unlocked-io does, after all.

Yes, that's true.  I'm starting to unbend on that recommendation, for
gnulib code anyway.  It's not something that can be done casually, and
it certainly wouldn't have worked in the gnulib context 10 or 20 years
ago (because files could not routinely be reincluded back then), but
I think it's OK nowadays.


>>As far as I can tell POSIX doesn't guarantee that repeated fflushes
>>will eventually write all the data, with no duplicates, in this
>>situation.
>
> Maybe not, but isn't this by far the implication of EAGAIN in most other
> contexts?  Couldn't do what you asked this time, try *AGAIN*?  I can
> understand the problem with fwrite and an object size > 1 since there is
> no way for the function to return a partial item count, but once stdio
> has the data, why should fflush not track what was written properly? 

Because fflush gets an error.  Common practice is to stop writing when
you get an error.

> GNULIB:
>
>    1. All the reasons I want this in GLIBC.
>    2. Ease of importing GLIBC updates to this code into CVS.

How does the proposed blocking-io module interact with the existing
unlocked-io module in gnulib?  It seems to me that they are
incompatible, since they both attempt to #define fwrite, for example.
This is a fairly serious problem, I'd think.  Can this be fixed
(possibly by modifying unlocked-io as well), so that the two modules
are compatible?


> Aside from bugs, this is what I would expect from most fflush
> implementations.  How about we install the module and see what sort of
> problem reports we get?

That sounds pretty drastic.  How about if we try this idea out first,
before installing it?

I just checked FreeBSD libc, and it appears that fflush fails once its
underlying write fails with EAGAIN.  So it appears that this approach
won't work under FreeBSD.  That's not a good sign.

Can you write a program to detect whether fflush reliably restarts?  I
just wrote the program below to do that, derived from your code, and
it fails for me both with Debian GNU/Linux 3.1 r0a and with Solaris
10, so that's not a good sign either.

But perhaps I'm not understanding what you're trying to do.



/* The idea of this module is to help programs cope with output
   streams that have been set nonblocking.

   To use it, simply make these definitions in your other files:

   #define printf blocking_printf
   #define fprintf blocking_fprintf
   #define vprintf blocking_vprintf
   #define vfprintf blocking_vfprintf
   #undef putchar
   #define putchar blocking_putchar
   #undef putc
   #define putc blocking_putc
   #define fputc blocking_putc
   #define puts blocking_puts
   #define fputs blocking_fputs
   #define fwrite blocking_fwrite
   #define fflush blocking_fflush

   and link with this module.  */

#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>


/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will
   return it, rather than EAGAIN, for nonblocking writes.  */
#ifdef EWOULDBLOCK
# define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
#else
# define blocking_error(err) ((err) == EAGAIN)
#endif

int
blocking_fwrite (const void *buffer, size_t size, size_t count, FILE *stream)
{
  size_t count_left = count;
  size_t count_written = 0;
  int desc = fileno (stream);

  if (size == 0 || count == 0)
    return 0;

  /* Handle the case where SIZE * COUNT overflows.
     (The comparison is designed to be conservative.)  */
  if (size > 1 && (double)size * (double)count > SIZE_MAX / 4)
    {
      const char *p = (const char *) buffer;
      for (count_written = 0; count_written < count; count_written++)
        {
          if (blocking_fwrite (p, sizeof *p, size, stream) < size)
            break;
          p += size;
        }
      return count_written;
    }

  /* When SIZE is > 1, convert to the byte-wise case
     since that is the only way fwrite undertakes not to repeat
     writing part of the data.  */
  if (size > 1)
    {
      size_t total = size * count;
      unsigned int value = blocking_fwrite (buffer, sizeof 1, total,
                                            stream);
      /* It is ok, according to POSIX, if VALUE is not a multiple
         of SIZE, because fwrite in that case can write part of an object.  */
      return value / size;
    }

  while (1)
    {
      int written;
      fd_set set;

      written = fwrite (buffer, 1, count_left, stream);
      count_written += written;
      if (written == count_left)
        break;
      if (blocking_error (errno))
        break;

      /* Wait for space to be available.  */
      FD_ZERO (&set);
      FD_SET (desc, &set);
      select (desc + 1, NULL, &set, NULL, NULL);

      buffer = (char const *) buffer + written;
      count_left -= written;
    }

  return count_written;
}

int
blocking_printf (const char *format, ...)
{
  int out_count;
  char *buffer;
  int size = 1000;

  while (1)
    {
      va_list argptr;

      buffer = malloc (size);
      if (!buffer)
        {
          /* Failed malloc on some non-posix systems (e.g. mingw) fail to set
             errno.  */
          if (!errno) errno = ENOMEM;
          return 0;
        }

      /* Format it into BUFFER, as much as fits.  */

      va_start (argptr, format);
      out_count = vsnprintf (buffer, size, format, argptr);
      va_end (argptr);
      /* OUT_COUNT now is number of bytes needed, not counting final null.  */

      if (out_count + 1 <= size)
        break;

      /* If it did not fit, try again with more space.  */

      free (buffer);
      size = out_count + 1;
    }

  /* Now we have the desired output in BUFFER.  Output it.  */

  blocking_fwrite (buffer, sizeof *buffer, out_count, stdout);

  free (buffer);
  return out_count;
}

int
blocking_fprintf (FILE *stream, const char *format, ...)
{
  int out_count;
  char *buffer;
  int size = 1000;

  while (1)
    {
      va_list argptr;

      buffer = malloc (size);
      if (!buffer)
        {
          /* Failed malloc on some non-posix systems (e.g. mingw) fail to set
             errno.  */
          if (!errno) errno = ENOMEM;
          return 0;
        }

      /* Format it into BUFFER, as much as fits.  */

      va_start (argptr, format);
      out_count = vsnprintf (buffer, size, format, argptr);
      va_end (argptr);
      /* OUT_COUNT now is number of bytes needed, not counting final null.  */

      if (out_count + 1 <= size)
        break;

      /* If it did not fit, try again with more space.  */

      free (buffer);
      size = out_count + 1;
    }

  /* Now we have the desired output in BUFFER.  Output it.  */

  blocking_fwrite (buffer, sizeof *buffer, out_count, stream);

  free (buffer);
  return out_count;
}

int
blocking_vprintf (const char *format, va_list ap)
{
  int out_count;
  char *buffer;
  int size = 1000;

  while (1)
    {
      va_list argptr;

      buffer = malloc (size);
      if (!buffer)
        {
          /* Failed malloc on some non-posix systems (e.g. mingw) fail to set
             errno.  */
          if (!errno) errno = ENOMEM;
          return 0;
        }

      /* Format it into BUFFER, as much as fits.  */

      va_copy (argptr, ap);
      out_count = vsnprintf (buffer, size, format, argptr);
      va_end (argptr);
      /* OUT_COUNT now is number of bytes needed, not counting final null.  */

      if (out_count + 1 <= size)
        break;

      /* If it did not fit, try again with more space.  */

      free (buffer);
      size = out_count + 1;
    }

  /* Now we have the desired output in BUFFER.  Output it.  */

  blocking_fwrite (buffer, sizeof *buffer, out_count, stdout);

  free (buffer);
  return out_count;
}

int
blocking_vfprintf (FILE *stream, const char *format, va_list ap)
{
  int out_count;
  char *buffer;
  int size = 1000;

  while (1)
    {
      va_list argptr;

      buffer = malloc (size);
      if (!buffer)
        {
          /* Failed malloc on some non-posix systems (e.g. mingw) fail to set
             errno.  */
          if (!errno) errno = ENOMEM;
          return 0;
        }

      /* Format it into BUFFER, as much as fits.  */

      va_copy (argptr, ap);
      out_count = vsnprintf (buffer, size, format, argptr);
      va_end (argptr);
      /* OUT_COUNT now is number of bytes needed, not counting final null.  */

      if (out_count + 1 <= size)
        break;

      /* If it did not fit, try again with more space.  */

      free (buffer);
      size = out_count + 1;
    }

  /* Now we have the desired output in BUFFER.  Output it.  */

  blocking_fwrite (buffer, sizeof *buffer, out_count, stream);

  free (buffer);
  return out_count;
}

int
blocking_putchar (char c)
{
  return (blocking_fwrite (&c, sizeof c, 1, stdout) < 1 ? EOF
          : (int) (unsigned char) c);
}

/* This serves for fputs also.  */
int
blocking_putc (char c, FILE *stream)
{
  return (blocking_fwrite (&c, sizeof c, 1, stream) < 1 ? EOF
          : (int) (unsigned char) c);
}

int
blocking_puts (const char *s)
{
  size_t len = strlen (s);
  return (blocking_fwrite (s, sizeof *s, len, stdout) < len ? EOF : 0);
}

int
blocking_fputs (const char *s, FILE *stream)
{
  size_t len = strlen (s);
  return (blocking_fwrite (s, sizeof *s, len, stream) < len ? EOF : 0);
}

int
blocking_fflush (FILE *stream)
{
  int desc = fileno (stream);

  while (1)
    {
      int value;
      fd_set set;

      value = fflush (stream);
      if (value == 0 || blocking_error (errno))
        return value;

      /* Wait for space to be available.  */
      FD_ZERO (&set);
      FD_SET (desc, &set);
      select (desc + 1, NULL, &set, NULL, NULL);
    }
}

int
blocking_fclose (FILE *stream)
{
  int desc = fileno (stream);

  while (1)
    {
      int value;
      fd_set set;

      value = fclose (stream);
      if (value == 0 || blocking_error (errno))
        return value;

      /* Wait for space to be available.  */
      FD_ZERO (&set);
      FD_SET (desc, &set);
      select (desc + 1, NULL, &set, NULL, NULL);
    }
}

#ifndef BLOCKING_IO_H
# define BLOCKING_IO_H

/* Get va_list.  */
# include <stdarg.h>
# include <stdio.h>

# define printf blocking_printf
# define fprintf blocking_fprintf
# define vprintf blocking_vprintf
# define vfprintf blocking_vfprintf
# undef putchar
# define putchar blocking_putchar
# undef putc
# define putc blocking_putc
# define fputc blocking_putc
# define puts blocking_puts
# define fputs blocking_fputs
# define fwrite blocking_fwrite
# define fflush blocking_fflush
# define fclose blocking_fclose

int blocking_printf (const char *format, ...);
int blocking_fprintf (FILE *stream, const char *format, ...);
int blocking_vprintf (const char *format, va_list ap);
int blocking_vfprintf (FILE *stream, const char *format, va_list ap);
int blocking_putchar (char c);
int blocking_putc (char c, FILE *stream);
int blocking_puts (const char *s);
int blocking_fputs (const char *s, FILE *stream);
int blocking_fwrite (const void *buffer, size_t size, size_t count,
                     FILE *stream);
int blocking_fflush (FILE *stream);
int blocking_fclose (FILE *stream);
#endif /* BLOCKING_IO_H */


#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will
   return it, rather than EAGAIN, for nonblocking writes.  */
#ifdef EWOULDBLOCK
# define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
#else
# define blocking_error(err) ((err) == EAGAIN)
#endif

char buf[1024 * 1024];
char obuf[sizeof buf];

int
main (void)
{
  int fd[2];
  pid_t child;
  if (pipe (fd) < 0)
    abort ();
  child = fork ();
  if (child < 0)
    abort ();

  if (child == 0)
    {
      /* Child.  */
      FILE *out;
      int oldflags = fcntl (fd[1], F_GETFL) & O_ACCMODE;
      size_t written = 0;
      close (fd[0]);
      if (fcntl (fd[1], F_SETFL, oldflags | O_NONBLOCK) == -1)
        abort ();
      out = fdopen (fd[1], "w");
      if (! out)
        abort ();
      setvbuf (out, obuf, _IOFBF, sizeof obuf);
      while (written < sizeof buf)
        {
          size_t bytes = fwrite (buf + written, 1, sizeof buf - written, out);
          if (bytes < sizeof buf - written && !blocking_error (errno))
            abort ();
          written += bytes;
        }
      while (fflush (out) != 0)
        if (! blocking_error (errno))
          abort ();
      _exit (0);
    }
  else
    {
      /* Parent.  */
      FILE *in;
      size_t s;
      size_t total = 0;
      close (fd[1]);
      in = fdopen (fd[0], "r");
      if (! in)
        abort ();
      while ((s = fread (buf, 1, sizeof buf, in)))
        total += s;
      if (total != sizeof buf)
        {
          printf ("got only %lu bytes, expected %lu bytes\n",
                  (unsigned long int) total,
                  (unsigned long int) sizeof buf);
          return 1;
        }
      return 0;
    }
}


_______________________________________________
bug-gnulib mailing list
bug-gnulib@gnu.org
http://lists.gnu.org/mailman/listinfo/bug-gnulib

Reply via email to