[Libvir] take 2 [Re: write(2) may write less than the total requested

Jim Meyering jim at meyering.net
Wed Feb 20 15:42:12 UTC 2008


Daniel Veillard <veillard at redhat.com> wrote:

> On Wed, Feb 20, 2008 at 02:53:49PM +0100, Jim Meyering wrote:
>> Use safewrite in place of write, in many cases.
>> And add "make syntax-check" rules to ensure no new uses sneak in.
>>
>> There are many uses of write like this:
>>
>>     if (write (fd, xml, towrite) != towrite)
>>         return -1;
>>
>> The problem is that the syscall can succeed, yet write less than
>> the requested number of bytes, so the caller should retry
>> rather than simply failing.
>
>   agreed using safewrite is a good idea, +1
>
>> This patch changes most of them to use util.c's safewrite wrapper,
>> which encapsulates the process.  Also, there were a few cases in
>> which the retry loop was open-coded, and I replaced those, too.
>
>> However, there remain invalid uses of write in both virsh.c and
>> console.c.  I would have replaced those, too, but util.c isn't
>> linked with them, so it would have made this change more involved.
>> xend_internal.c has one use, but it's ok, and not easily replaced.
>
>   maybe for case outside of the library adding a copy of safewrite
> in virsh.c would be a good way to do this while keeping the code
> consistent. The duplication of that small function whould not
> be a real problem IMHO

Thanks for the quick review.
That would work well.

It's needed also for qemud.c, which already includes util.h
and uses functions like __virStrToLong_i, yet linking libvirtd
would currently fail due to the uses of safewrite.

If I do as you suggest and move the safewrite definition into util.h,
and make it static inline, then both problems go away.

Here's a new version of the patch, adjusted accordingly:

---------------------------------------------------------------
Use safewrite in place of write, in many cases.

And add "make syntax-check" rules to ensure no new uses sneak in.

There are many uses of write like this:

    if (write (fd, xml, towrite) != towrite)
        return -1;

The problem is that the syscall can succeed, yet write less than
the requested number of bytes, so the caller should retry
rather than simply failing.

This patch changes most of them to use util.c's safewrite wrapper,
which encapsulates the process.  Also, there were a few cases in
which the retry loop was open-coded, and I replaced those, too.
However, there remain invalid uses of write in both virsh.c and
console.c.  I would have replaced those, too, but util.c isn't
linked with them, so it would have made this change more involved.
xend_internal.c has one use, but it's ok, and not easily replaced.

Note: the change to libvirt_proxy.c removes a diagnostic that
would have warned about an EINTR-interrupted (and retried) write,
but that seems ok.

* Makefile.maint (sc_avoid_write): New rule, to avoid recurrence.
* .x-sc_avoid_write: New file.  Record two legitimate exemptions.
* qemud/qemud.c (sig_handler, qemudClientWriteBuf): Use safewrite, not write.
* src/conf.c (__virConfWriteFile): Likewise.
* src/qemu_conf.c (qemudSaveConfig, qemudSaveNetworkConfig): Likewise.
* src/qemu_driver.c (qemudWaitForMonitor, qemudStartVMDaemon)
(qemudVMData, PROC_IP_FORWARD): Likewise.
* proxy/libvirt_proxy.c: Include "util.h".
(proxyWriteClientSocket): Use safewrite.
* src/test.c (testDomainSave, testDomainCoreDump): Likewise.
* src/proxy_internal.c (virProxyWriteClientSocket): Likewise.
* src/virsh.c (vshOutputLogFile): Likewise.
* src/console.c (vshRunConsole): Likewise.
* src/util.c (safewrite): Move function definition to ...
* src/util.h (safewrite): ..here, and make it "static inline".
* proxy/Makefile.am: Split a long line, and add a newline at EOF.

Signed-off-by: Jim Meyering <meyering at redhat.com>
---
 .x-sc_avoid_write     |    2 ++
 Makefile.maint        |   11 +++++++++++
 proxy/Makefile.am     |    5 +++--
 proxy/libvirt_proxy.c |   16 +++++-----------
 qemud/qemud.c         |   10 ++++------
 src/conf.c            |    2 +-
 src/console.c         |    6 ++++--
 src/proxy_internal.c  |   14 ++++----------
 src/qemu_conf.c       |    4 ++--
 src/qemu_driver.c     |   19 +++++++------------
 src/test.c            |    9 +++++----
 src/util.c            |   22 +---------------------
 src/util.h            |   21 ++++++++++++++++++++-
 src/virsh.c           |    2 +-
 14 files changed, 70 insertions(+), 73 deletions(-)
 create mode 100644 .x-sc_avoid_write

diff --git a/.x-sc_avoid_write b/.x-sc_avoid_write
new file mode 100644
index 0000000..72a196d
--- /dev/null
+++ b/.x-sc_avoid_write
@@ -0,0 +1,2 @@
+^src/util\.c$
+^src/xend_internal\.c$
diff --git a/Makefile.maint b/Makefile.maint
index 4b54baf..afddddb 100644
--- a/Makefile.maint
+++ b/Makefile.maint
@@ -46,6 +46,17 @@ sc_avoid_if_before_free:
 	  { echo '$(ME): found useless "if" before "free" above' 1>&2;	\
 	    exit 1; } || :

+# Avoid uses of write(2).  Either switch to streams (fwrite), or use
+# the safewrite wrapper.
+sc_avoid_write:
+	@if $(CVS_LIST_EXCEPT) | grep '\.c$$' > /dev/null; then		\
+	  grep '\<write *(' $$($(CVS_LIST_EXCEPT) | grep '\.c$$') &&	\
+	    { echo "$(ME): the above files use write;"			\
+	      " consider using the safewrite wrapper instead"		\
+		  1>&2; exit 1; } || :;					\
+	else :;								\
+	fi
+
 sc_cast_of_argument_to_free:
 	@grep -nE '\<free \(\(' $$($(CVS_LIST_EXCEPT)) &&		\
 	  { echo '$(ME): don'\''t cast free argument' 1>&2;		\
diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index ff9a3eb..006e503 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -12,11 +12,12 @@ libexec_PROGRAMS = libvirt_proxy
 libvirt_proxy_SOURCES = libvirt_proxy.c @top_srcdir@/src/xend_internal.c \
 	    @top_srcdir@/src/xen_internal.c @top_srcdir@/src/virterror.c \
 	    @top_srcdir@/src/sexpr.c @top_srcdir@/src/xml.c \
-            @top_srcdir@/src/xs_internal.c @top_srcdir@/src/buf.c @top_srcdir@/src/uuid.c
+            @top_srcdir@/src/xs_internal.c @top_srcdir@/src/buf.c \
+	    @top_srcdir@/src/uuid.c
 libvirt_proxy_LDFLAGS = $(WARN_CFLAGS)
 libvirt_proxy_DEPENDENCIES =
 libvirt_proxy_LDADD =

 install-exec-hook:
 	chmod u+s $(DESTDIR)$(libexecdir)/libvirt_proxy
-endif
\ No newline at end of file
+endif
diff --git a/proxy/libvirt_proxy.c b/proxy/libvirt_proxy.c
index d96d3db..a22ba6c 100644
--- a/proxy/libvirt_proxy.c
+++ b/proxy/libvirt_proxy.c
@@ -2,7 +2,7 @@
  * proxy_svr.c: root suid proxy server for Xen access to APIs with no
  *              side effects from unauthenticated clients.
  *
- * Copyright (C) 2006, 2007 Red Hat, Inc.
+ * Copyright (C) 2006, 2007, 2008 Red Hat, Inc.
  *
  * See COPYING.LIB for the License of this software
  *
@@ -26,6 +26,7 @@
 #include "internal.h"

 #include "proxy_internal.h"
+#include "util.h"
 #include "xen_internal.h"
 #include "xend_internal.h"
 #include "xs_internal.h"
@@ -317,19 +318,12 @@ proxyWriteClientSocket(int nr, virProxyPacketPtr req) {
 	return(-1);
     }

-retry:
-    ret = write(pollInfos[nr].fd, (char *) req, req->len);
+    ret = safewrite(pollInfos[nr].fd, (char *) req, req->len);
     if (ret < 0) {
-        if (errno == EINTR) {
-	    if (debug > 0)
-	        fprintf(stderr, "write socket %d to client %d interrupted\n",
-		        pollInfos[nr].fd, nr);
-	    goto retry;
-	}
         fprintf(stderr, "write %d bytes to socket %d from client %d failed\n",
 	        req->len, pollInfos[nr].fd, nr);
-	proxyCloseClientSocket(nr);
-	return(-1);
+        proxyCloseClientSocket(nr);
+        return(-1);
     }
     if (ret == 0) {
 	if (debug)
diff --git a/qemud/qemud.c b/qemud/qemud.c
index 3a5e44c..269e9fe 100644
--- a/qemud/qemud.c
+++ b/qemud/qemud.c
@@ -118,7 +118,7 @@ static void sig_handler(int sig) {
         return;

     origerrno = errno;
-    r = write(sigwrite, &sigc, 1);
+    r = safewrite(sigwrite, &sigc, 1);
     if (r == -1) {
         sig_errors++;
         sig_lasterrno = errno;
@@ -1360,11 +1360,9 @@ static int qemudClientWriteBuf(struct qemud_server *server,
                                const char *data, int len) {
     int ret;
     if (!client->tlssession) {
-        if ((ret = write(client->fd, data, len)) == -1) {
-            if (errno != EAGAIN) {
-                qemudLog (QEMUD_ERR, _("write: %s"), strerror (errno));
-                qemudDispatchClientFailure(server, client);
-            }
+        if ((ret = safewrite(client->fd, data, len)) == -1) {
+            qemudLog (QEMUD_ERR, _("write: %s"), strerror (errno));
+            qemudDispatchClientFailure(server, client);
             return -1;
         }
     } else {
diff --git a/src/conf.c b/src/conf.c
index e0ecdea..53ea993 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -904,7 +904,7 @@ __virConfWriteFile(const char *filename, virConfPtr conf)
 	goto error;
     }

-    ret = write(fd, buf->content, buf->use);
+    ret = safewrite(fd, buf->content, buf->use);
     close(fd);
     if (ret != (int) buf->use) {
         virConfError(NULL, VIR_ERR_WRITE_FAILED, _("failed to save content"), 0);
diff --git a/src/console.c b/src/console.c
index 02a9c7f..1c6cba0 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1,7 +1,7 @@
 /*
  * console.c: A dumb serial console client
  *
- * Copyright (C) 2007 Red Hat, Inc.
+ * Copyright (C) 2007, 2008 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -37,6 +37,7 @@

 #include "console.h"
 #include "internal.h"
+#include "util.h"

 /* ie  Ctrl-]  as per telnet */
 #define CTRL_CLOSE_BRACKET '\35'
@@ -161,7 +162,8 @@ int vshRunConsole(const char *tty) {

                 while (sent < got) {
                     int done;
-                    if ((done = write(destfd, buf + sent, got - sent)) <= 0) {
+                    if ((done = safewrite(destfd, buf + sent, got - sent))
+                        <= 0) {
                         fprintf(stderr, _("failure writing output: %s\n"),
                                 strerror(errno));
                         goto cleanup;
diff --git a/src/proxy_internal.c b/src/proxy_internal.c
index bc94763..c3e50c6 100644
--- a/src/proxy_internal.c
+++ b/src/proxy_internal.c
@@ -1,7 +1,7 @@
 /*
  * proxy_client.c: client side of the communication with the libvirt proxy.
  *
- * Copyright (C) 2006 Red Hat, Inc.
+ * Copyright (C) 2006, 2008 Red Hat, Inc.
  *
  * See COPYING.LIB for the License of this software
  *
@@ -26,6 +26,7 @@
 #include "internal.h"
 #include "driver.h"
 #include "proxy_internal.h"
+#include "util.h"
 #include "xen_unified.h"

 #define STANDALONE
@@ -345,17 +346,10 @@ virProxyWriteClientSocket(int fd, const char *data, int len) {
     if ((fd < 0) || (data == NULL) || (len < 0))
         return(-1);

-retry:
-    ret = write(fd, data, len);
+    ret = safewrite(fd, data, len);
     if (ret < 0) {
-        if (errno == EINTR) {
-	    if (debug > 0)
-	        fprintf(stderr, "write socket %d, %d bytes interrupted\n",
-		        fd, len);
-	    goto retry;
-	}
         fprintf(stderr, _("Failed to write to socket %d\n"), fd);
-	return(-1);
+        return(-1);
     }
     if (debug)
 	fprintf(stderr, "wrote %d bytes to socket %d\n",
diff --git a/src/qemu_conf.c b/src/qemu_conf.c
index e39c7bc..ff6a92e 100644
--- a/src/qemu_conf.c
+++ b/src/qemu_conf.c
@@ -1866,7 +1866,7 @@ static int qemudSaveConfig(virConnectPtr conn,
     }

     towrite = strlen(xml);
-    if (write(fd, xml, towrite) != towrite) {
+    if (safewrite(fd, xml, towrite) < 0) {
         qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                          "cannot write config file %s: %s",
                          vm->configFile, strerror(errno));
@@ -2089,7 +2089,7 @@ static int qemudSaveNetworkConfig(virConnectPtr conn,
     }

     towrite = strlen(xml);
-    if (write(fd, xml, towrite) != towrite) {
+    if (safewrite(fd, xml, towrite) < 0) {
         qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                          "cannot write config file %s: %s",
                          network->configFile, strerror(errno));
diff --git a/src/qemu_driver.c b/src/qemu_driver.c
index 15cd52c..85d042f 100644
--- a/src/qemu_driver.c
+++ b/src/qemu_driver.c
@@ -539,11 +539,9 @@ static int qemudWaitForMonitor(virConnectPtr conn,
                                      "console");

     buf[sizeof(buf)-1] = '\0';
- retry:
-    if (write(vm->logfile, buf, strlen(buf)) < 0) {
+
+    if (safewrite(vm->logfile, buf, strlen(buf)) < 0) {
         /* Log, but ignore failures to write logfile for VM */
-        if (errno == EINTR)
-            goto retry;
         qemudLog(QEMUD_WARN, _("Unable to log VM console data: %s"),
                  strerror(errno));
     }
@@ -656,15 +654,15 @@ static int qemudStartVMDaemon(virConnectPtr conn,

     tmp = argv;
     while (*tmp) {
-        if (write(vm->logfile, *tmp, strlen(*tmp)) < 0)
+        if (safewrite(vm->logfile, *tmp, strlen(*tmp)) < 0)
             qemudLog(QEMUD_WARN, _("Unable to write argv to logfile %d: %s"),
                      errno, strerror(errno));
-        if (write(vm->logfile, " ", 1) < 0)
+        if (safewrite(vm->logfile, " ", 1) < 0)
             qemudLog(QEMUD_WARN, _("Unable to write argv to logfile %d: %s"),
                      errno, strerror(errno));
         tmp++;
     }
-    if (write(vm->logfile, "\n", 1) < 0)
+    if (safewrite(vm->logfile, "\n", 1) < 0)
         qemudLog(QEMUD_WARN, _("Unable to write argv to logfile %d: %s"),
                  errno, strerror(errno));

@@ -733,11 +731,8 @@ static int qemudVMData(struct qemud_driver *driver ATTRIBUTE_UNUSED,
         }
         buf[ret] = '\0';

-    retry:
-        if (write(vm->logfile, buf, ret) < 0) {
+        if (safewrite(vm->logfile, buf, ret) < 0) {
             /* Log, but ignore failures to write logfile for VM */
-            if (errno == EINTR)
-                goto retry;
             qemudLog(QEMUD_WARN, _("Unable to log VM console data: %s"),
                      strerror(errno));
         }
@@ -1113,7 +1108,7 @@ qemudEnableIpForwarding(void)
     if ((fd = open(PROC_IP_FORWARD, O_WRONLY|O_TRUNC)) == -1)
         return 0;

-    if (write(fd, "1\n", 2) < 0)
+    if (safewrite(fd, "1\n", 2) < 0)
         ret = 0;

     close (fd);
diff --git a/src/test.c b/src/test.c
index 003d6b7..346774b 100644
--- a/src/test.c
+++ b/src/test.c
@@ -42,6 +42,7 @@
 #include "test.h"
 #include "xml.h"
 #include "buf.h"
+#include "util.h"
 #include "uuid.h"

 /* Flags that determine the action to take on a shutdown or crash of a domain
@@ -1293,19 +1294,19 @@ static int testDomainSave(virDomainPtr domain,
         return (-1);
     }
     len = strlen(xml);
-    if (write(fd, TEST_SAVE_MAGIC, sizeof(TEST_SAVE_MAGIC)) != sizeof(TEST_SAVE_MAGIC)) {
+    if (safewrite(fd, TEST_SAVE_MAGIC, sizeof(TEST_SAVE_MAGIC)) < 0) {
         testError(domain->conn, domain, NULL, VIR_ERR_INTERNAL_ERROR,
                   "cannot write header");
         close(fd);
         return (-1);
     }
-    if (write(fd, (char*)&len, sizeof(len)) != sizeof(len)) {
+    if (safewrite(fd, (char*)&len, sizeof(len)) < 0) {
         testError(domain->conn, domain, NULL, VIR_ERR_INTERNAL_ERROR,
                   "cannot write metadata length");
         close(fd);
         return (-1);
     }
-    if (write(fd, xml, len) != len) {
+    if (safewrite(fd, xml, len) < 0) {
         testError(domain->conn, domain, NULL, VIR_ERR_INTERNAL_ERROR,
                   "cannot write metadata");
         free(xml);
@@ -1398,7 +1399,7 @@ static int testDomainCoreDump(virDomainPtr domain,
                   "cannot save domain core");
         return (-1);
     }
-    if (write(fd, TEST_SAVE_MAGIC, sizeof(TEST_SAVE_MAGIC)) != sizeof(TEST_SAVE_MAGIC)) {
+    if (safewrite(fd, TEST_SAVE_MAGIC, sizeof(TEST_SAVE_MAGIC)) < 0) {
         testError(domain->conn, domain, NULL, VIR_ERR_INTERNAL_ERROR,
                   "cannot write header");
         close(fd);
diff --git a/src/util.c b/src/util.c
index f082984..c813a4c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,7 +1,7 @@
 /*
  * utils.c: common, generic utility functions
  *
- * Copyright (C) 2006, 2007 Red Hat, Inc.
+ * Copyright (C) 2006, 2007, 2008 Red Hat, Inc.
  * Copyright (C) 2006 Daniel P. Berrange
  * Copyright (C) 2006, 2007 Binary Karma
  * Copyright (C) 2006 Shuveb Hussain
@@ -295,26 +295,6 @@ int saferead(int fd, void *buf, size_t count)
 	return nread;
 }

-/* Like write(), but restarts after EINTR */
-ssize_t safewrite(int fd, const void *buf, size_t count)
-{
-	size_t nwritten = 0;
-	while (count > 0) {
-		int r = write(fd, buf, count);
-
-		if (r < 0 && errno == EINTR)
-			continue;
-		if (r < 0)
-			return r;
-		if (r == 0)
-			return nwritten;
-		buf = (unsigned char *)buf + r;
-		count -= r;
-		nwritten += r;
-	}
-	return nwritten;
-}
-

 int __virFileReadAll(const char *path,
                      int maxlen,
diff --git a/src/util.h b/src/util.h
index da1f5b0..218ab1b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -31,7 +31,26 @@ int virExecNonBlock(virConnectPtr conn, char **argv, int *retpid, int infd, int
 int virRun(virConnectPtr conn, char **argv, int *status);

 int saferead(int fd, void *buf, size_t count);
-ssize_t safewrite(int fd, const void *buf, size_t count);
+
+/* Like write(), but restarts after EINTR */
+static inline ssize_t safewrite(int fd, const void *buf, size_t count)
+{
+	size_t nwritten = 0;
+	while (count > 0) {
+		int r = write(fd, buf, count);
+
+		if (r < 0 && errno == EINTR)
+			continue;
+		if (r < 0)
+			return r;
+		if (r == 0)
+			return nwritten;
+		buf = (unsigned char *)buf + r;
+		count -= r;
+		nwritten += r;
+	}
+	return nwritten;
+}

 int __virFileReadAll(const char *path,
 		     int maxlen,
diff --git a/src/virsh.c b/src/virsh.c
index 730f251..b67e63c 100644
--- a/src/virsh.c
+++ b/src/virsh.c
@@ -4582,7 +4582,7 @@ vshOutputLogFile(vshControl *ctl, int log_level, const char *msg_format, va_list
         snprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf), "\n");

     /* write log */
-    if (write(ctl->log_fd, msg_buf, strlen(msg_buf)) == -1) {
+    if (safewrite(ctl->log_fd, msg_buf, strlen(msg_buf)) < 0) {
         vshCloseLogFile(ctl);
         vshError(ctl, FALSE, "%s", _("failed to write the log file"));
     }
--
1.5.4.2.134.g82883




More information about the libvir-list mailing list