fd: Improve error handling of write_nonblock().
authorAndre Noll <maan@systemlinux.org>
Sun, 11 Dec 2011 18:48:56 +0000 (19:48 +0100)
committerAndre Noll <maan@systemlinux.org>
Fri, 20 Jan 2012 21:57:06 +0000 (22:57 +0100)
This function had two shortcomings: First, a call to write might
fail with errno set to EINTR in case the write call was interrupted
before any data was written. This is not fatal and one should just
retry the write in this case.

Secondly, POSIX allows to return either EAGAIN or EWOULDBLOCK if the
write would block but we only check for EAGAIN. This is no problem on
Linux since both constants refer to the same value on Linux. However,
POSIX does not require them to be equal, so we have to check for both.

This patch corrects both issues.

fd.c

diff --git a/fd.c b/fd.c
index 6f487c4..830b15d 100644 (file)
--- a/fd.c
+++ b/fd.c
@@ -62,33 +62,49 @@ __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
 }
 
 /**
- * Write a buffer to a non-blocking file descriptor.
+ * Write a buffer to a file descriptor, re-writing on short writes.
  *
  * \param fd The file descriptor.
- * \param buf the buffer to write.
- * \param len the number of bytes of \a buf.
- *
- * EAGAIN is not considered an error condition.  For example CCID3 has a
- * sending wait queue which fills up and is emptied asynchronously. The EAGAIN
- * case means that there is currently no space in the wait queue, but this can
- * change at any moment.
- *
- * \return Negative on errors, number of bytes written else.
+ * \param buf The buffer to write.
+ * \param len The number of bytes to write.
+ *
+ * EAGAIN/EWOULDBLOCK is not considered a fatal error condition. For example
+ * DCCP CCID3 has a sending wait queue which fills up and is emptied
+ * asynchronously. The EAGAIN case means that there is currently no space in
+ * the wait queue, but this can change at any moment.
+ *
+ * \return Negative on fatal errors, number of bytes written else. For blocking
+ * file descriptors this function returns either \a len or the error code of
+ * the fatal error that caused the last write call to fail. For nonblocking
+ * file descriptors there is a third possibility: A positive return value < \a
+ * len indicates that some bytes have been written but the next write would
+ * block.
  */
 int write_nonblock(int fd, const char *buf, size_t len)
 {
        size_t written = 0;
-       int ret = 0;
 
        while (written < len) {
-               size_t num = len - written;
-
-               ret = write(fd, buf + written, num);
-               if (ret < 0 && errno == EAGAIN)
+               ssize_t ret = write(fd, buf + written, len - written);
+               if (ret >= 0) {
+                       written += ret;
+                       continue;
+               }
+               if (errno == EINTR)
+                       /*
+                        * The write() call was interrupted by a signal before
+                        * any data was written. Try again.
+                        */
+                       continue;
+               if (errno == EAGAIN || errno == EWOULDBLOCK)
+                       /*
+                        * We don't consider this an error. Note that POSIX
+                        * allows either error to be returned, and does not
+                        * require these constants to have the same value.
+                        */
                        return written;
-               if (ret < 0)
-                       return -ERRNO_TO_PARA_ERROR(errno);
-               written += ret;
+               /* fatal error */
+               return -ERRNO_TO_PARA_ERROR(errno);
        }
        return written;
 }