changeset 14582:3a41e32d28ab

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.
author Bruno Haible <bruno@clisp.org>
date Thu, 14 Apr 2011 23:42:01 +0200
parents a1d4fafd50b6
children 8b22057e98d2
files ChangeLog doc/posix-functions/write.texi lib/write.c
diffstat 3 files changed, 83 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+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.
--- a/doc/posix-functions/write.texi
+++ b/doc/posix-functions/write.texi
@@ -13,6 +13,12 @@
 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}:
--- a/lib/write.c
+++ b/lib/write.c
@@ -42,42 +42,81 @@
 rpl_write (int fd, const void *buf, size_t count)
 #undef write
 {
-  ssize_t ret = write (fd, buf, count);
+  for (;;)
+    {
+      ssize_t ret = write (fd, buf, count);
 
-  if (ret < 0)
-    {
-#  if GNULIB_NONBLOCKING
-      if (errno == ENOSPC)
+      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
+#  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;
+                }
+#  endif
             }
         }
-      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;
-            }
-#  endif
-        }
+      return ret;
     }
-  return ret;
 }
 
 # endif