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