Support non-blocking pipe I/O in write() on native Windows.
authorBruno Haible <bruno@clisp.org>
Thu, 14 Apr 2011 21:42:01 +0000 (23:42 +0200)
committerBruno Haible <bruno@clisp.org>
Thu, 14 Apr 2011 21:42:01 +0000 (23:42 +0200)
* lib/write.c (rpl_write): Split a write request that failed merely
because the byte count was larger than the pipe buffer's size.
* doc/posix-functions/write.texi: Mention the problem with large byte
counts.

ChangeLog
doc/posix-functions/write.texi
lib/write.c

index 349e5fc..1657a34 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2011-04-14  Bruno Haible  <bruno@clisp.org>
 
+       Support non-blocking pipe I/O in write() on native Windows.
+       * lib/write.c (rpl_write): Split a write request that failed merely
+       because the byte count was larger than the pipe buffer's size.
+       * doc/posix-functions/write.texi: Mention the problem with large byte
+       counts.
+
+2011-04-14  Bruno Haible  <bruno@clisp.org>
+
        wchar: Ensure that wchar_t gets defined on uClibc.
        * lib/wchar.in.h: On uClibc, include <stddef.h>.
        Reported by Giuseppe Scrivano <gscrivano@gnu.org>.
index 7587fbb..604507d 100644 (file)
@@ -13,6 +13,12 @@ When writing to a non-blocking pipe whose buffer is full, this function fails
 with @code{errno} being set to @code{ENOSPC} instead of @code{EAGAIN} on some
 platforms:
 mingw.
+@item
+When writing to a non-blocking pipe on which no reader is currently waiting
+an amount of bytes that exceeds the pipe buffer's size, then -- even if the
+pipe's buffer is empty -- this function fails, instead of performing a partial
+write into the pipe buffer, on some platforms:
+mingw.
 @end itemize
 
 Portability problems fixed by Gnulib module @code{stdio}, together with module @code{sigpipe}:
index 4b40cd3..b0ffa94 100644 (file)
@@ -42,42 +42,81 @@ ssize_t
 rpl_write (int fd, const void *buf, size_t count)
 #undef write
 {
-  ssize_t ret = write (fd, buf, count);
-
-  if (ret < 0)
+  for (;;)
     {
-#  if GNULIB_NONBLOCKING
-      if (errno == ENOSPC)
+      ssize_t ret = write (fd, buf, count);
+
+      if (ret < 0)
         {
-          HANDLE h = (HANDLE) _get_osfhandle (fd);
-          if (GetFileType (h) == FILE_TYPE_PIPE)
+#  if GNULIB_NONBLOCKING
+          if (errno == ENOSPC)
             {
-              /* h is a pipe or socket.  */
-              DWORD state;
-              if (GetNamedPipeHandleState (h, &state, NULL, NULL, NULL, NULL, 0)
-                  && (state & PIPE_NOWAIT) != 0)
-                /* h is a pipe in non-blocking mode.
-                   Change errno from ENOSPC to EAGAIN.  */
-                errno = EAGAIN;
+              HANDLE h = (HANDLE) _get_osfhandle (fd);
+              if (GetFileType (h) == FILE_TYPE_PIPE)
+                {
+                  /* h is a pipe or socket.  */
+                  DWORD state;
+                  if (GetNamedPipeHandleState (h, &state, NULL, NULL, NULL,
+                                               NULL, 0)
+                      && (state & PIPE_NOWAIT) != 0)
+                    {
+                      /* h is a pipe in non-blocking mode.
+                         We can get here in four situations:
+                           1. When the pipe buffer is full.
+                           2. When count <= pipe_buf_size and the number of
+                              free bytes in the pipe buffer is < count.
+                           3. When count > pipe_buf_size and the number of free
+                              bytes in the pipe buffer is > 0, < pipe_buf_size.
+                           4. When count > pipe_buf_size and the pipe buffer is
+                              entirely empty.
+                         The cases 1 and 2 are POSIX compliant.  In cases 3 and
+                         4 POSIX specifies that write() must split the request
+                         and succeed with a partial write.  We fix case 4.
+                         We don't fix case 3 because it is not essential for
+                         programs.  */
+                      DWORD out_size; /* size of the buffer for outgoing data */
+                      DWORD in_size;  /* size of the buffer for incoming data */
+                      if (GetNamedPipeInfo (h, NULL, &out_size, &in_size, NULL))
+                        {
+                          size_t reduced_count = count;
+                          /* In theory we need only one of out_size, in_size.
+                             But I don't know which of the two.  The description
+                             is ambiguous.  */
+                          if (out_size != 0 && out_size < reduced_count)
+                            reduced_count = out_size;
+                          if (in_size != 0 && in_size < reduced_count)
+                            reduced_count = in_size;
+                          if (reduced_count < count)
+                            {
+                              /* Attempt to write only the first part.  */
+                              count = reduced_count;
+                              continue;
+                            }
+                        }
+                      /* Change errno from ENOSPC to EAGAIN.  */
+                      errno = EAGAIN;
+                    }
+                }
             }
-        }
-      else
+          else
 #  endif
-        {
-#  if GNULIB_SIGPIPE
-          if (GetLastError () == ERROR_NO_DATA
-              && GetFileType ((HANDLE) _get_osfhandle (fd)) == FILE_TYPE_PIPE)
             {
-              /* Try to raise signal SIGPIPE.  */
-              raise (SIGPIPE);
-              /* If it is currently blocked or ignored, change errno from
-                 EINVAL to EPIPE.  */
-              errno = EPIPE;
-            }
+#  if GNULIB_SIGPIPE
+              if (GetLastError () == ERROR_NO_DATA
+                  && GetFileType ((HANDLE) _get_osfhandle (fd))
+                     == FILE_TYPE_PIPE)
+                {
+                  /* Try to raise signal SIGPIPE.  */
+                  raise (SIGPIPE);
+                  /* If it is currently blocked or ignored, change errno from
+                     EINVAL to EPIPE.  */
+                  errno = EPIPE;
+                }
 #  endif
+            }
         }
+      return ret;
     }
-  return ret;
 }
 
 # endif