[libvirt] [PATCHv3] command: improve behavior on no output

Eric Blake eblake at redhat.com
Tue Dec 7 03:07:34 UTC 2010


Guarantee that outbuf/errbuf are allocated on success, even if to the
empty string.  Caller always has to free the result, and empty output
check requires checking if *outbuf=='\0'.  Makes the API easier to use
safely.  Failure is best effort allocation (some paths, like
out-of-memory, cannot allocate a buffer, but most do), so caller must
free buffer on failure; but this now guarantees that VIR_FREE(buf)
will be safe.

* docs/internals/command.html.in: Update documentation.
* src/util/command.c (virCommandSetOutputBuffer)
(virCommandSetErrorBuffer, virCommandProcessIO) Guarantee empty
string on no output.
* tests/commandtest.c (test17): New test.
---

Everyone liked the safety of v2 over the speed of v1; and in looking
at it more, I found even more ways to make it safer.  Hence this v3.

 docs/internals/command.html.in |   12 ++++++--
 src/util/command.c             |   53 ++++++++++++++++++++++++++++--------
 tests/commandtest.c            |   58 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 15 deletions(-)

diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in
index 259e68e..95d2b81 100644
--- a/docs/internals/command.html.in
+++ b/docs/internals/command.html.in
@@ -365,9 +365,15 @@
 </pre>

     <p>
-      Once the command has finished executing, these buffers
-      will contain the output. It is the callers responsibility
-      to free these buffers.
+      Once the command has finished executing, these buffers will
+      contain the output.  Allocation is guaranteed if virCommandRun
+      or virCommandWait succeed (if there was no output, then the
+      buffer will contain an allocated empty string); if the command
+      failed, then the buffers usually contain a best-effort
+      allocation of collected information (however, on an
+      out-of-memory condition, the buffer may still be NULL).  The
+      caller is responsible for freeing registered buffers, since the
+      buffers are designed to persist beyond virCommandFree.
     </p>

     <h3><a name="directory">Setting working directory</a></h3>
diff --git a/src/util/command.c b/src/util/command.c
index d1d8f6d..da2572d 100644
--- a/src/util/command.c
+++ b/src/util/command.c
@@ -533,11 +533,15 @@ virCommandSetInputBuffer(virCommandPtr cmd, const char *inbuf)


 /*
- * Capture the child's stdout to a string buffer
+ * Capture the child's stdout to a string buffer.  *outbuf is
+ * guaranteed to be allocated after successful virCommandRun or
+ * virCommandWait, and is best-effort allocated after failed
+ * virCommandRun; caller is responsible for freeing *outbuf.
  */
 void
 virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
 {
+    *outbuf = NULL;
     if (!cmd || cmd->has_error)
         return;

@@ -553,11 +557,15 @@ virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)


 /*
- * Capture the child's stderr to a string buffer
+ * Capture the child's stderr to a string buffer.  *errbuf is
+ * guaranteed to be allocated after successful virCommandRun or
+ * virCommandWait, and is best-effort allocated after failed
+ * virCommandRun; caller is responsible for freeing *errbuf.
  */
 void
 virCommandSetErrorBuffer(virCommandPtr cmd, char **errbuf)
 {
+    *errbuf = NULL;
     if (!cmd || cmd->has_error)
         return;

@@ -747,6 +755,7 @@ virCommandProcessIO(virCommandPtr cmd)
     int infd = -1, outfd = -1, errfd = -1;
     size_t inlen = 0, outlen = 0, errlen = 0;
     size_t inoff = 0;
+    int ret = 0;

     /* With an input buffer, feed data to child
      * via pipe */
@@ -755,12 +764,27 @@ virCommandProcessIO(virCommandPtr cmd)
         infd = cmd->inpipe;
     }

-    /* With out/err buffer, the outfd/errfd
-     * have been filled with an FD for us */
-    if (cmd->outbuf)
+    /* With out/err buffer, the outfd/errfd have been filled with an
+     * FD for us.  Guarantee an allocated string with partial results
+     * even if we encounter a later failure, as well as freeing any
+     * results accumulated over a prior run of the same command.  */
+    if (cmd->outbuf) {
         outfd = cmd->outfd;
-    if (cmd->errbuf)
+        if (VIR_REALLOC_N(*cmd->outbuf, 1) < 0) {
+            virReportOOMError();
+            ret = -1;
+        }
+    }
+    if (cmd->errbuf) {
         errfd = cmd->errfd;
+        if (VIR_REALLOC_N(*cmd->errbuf, 1) < 0) {
+            virReportOOMError();
+            ret = -1;
+        }
+    }
+    if (ret == -1)
+        goto cleanup;
+    ret = -1;

     for (;;) {
         int i;
@@ -791,7 +815,7 @@ virCommandProcessIO(virCommandPtr cmd)
                 continue;
             virReportSystemError(errno, "%s",
                                  _("unable to poll on child"));
-            return -1;
+            goto cleanup;
         }

         for (i = 0; i < nfds ; i++) {
@@ -815,7 +839,7 @@ virCommandProcessIO(virCommandPtr cmd)
                         errno != EAGAIN) {
                         virReportSystemError(errno, "%s",
                                              _("unable to write to child input"));
-                        return -1;
+                        goto cleanup;
                     }
                 } else if (done == 0) {
                     if (fds[i].fd == outfd)
@@ -825,11 +849,10 @@ virCommandProcessIO(virCommandPtr cmd)
                 } else {
                     if (VIR_REALLOC_N(*buf, *len + done + 1) < 0) {
                         virReportOOMError();
-                        return -1;
+                        goto cleanup;
                     }
                     memcpy(*buf + *len, data, done);
                     *len += done;
-                    (*buf)[*len] = '\0';
                 }
             } else {
                 int done;
@@ -841,7 +864,7 @@ virCommandProcessIO(virCommandPtr cmd)
                         errno != EAGAIN) {
                         virReportSystemError(errno, "%s",
                                              _("unable to write to child input"));
-                        return -1;
+                        goto cleanup;
                     }
                 } else {
                     inoff += done;
@@ -856,7 +879,13 @@ virCommandProcessIO(virCommandPtr cmd)
         }
     }

-    return 0;
+    ret = 0;
+cleanup:
+    if (*cmd->outbuf)
+        (*cmd->outbuf)[outlen] = '\0';
+    if (*cmd->errbuf)
+        (*cmd->errbuf)[errlen] = '\0';
+    return ret;
 }


diff --git a/tests/commandtest.c b/tests/commandtest.c
index b7261e9..20e56bc 100644
--- a/tests/commandtest.c
+++ b/tests/commandtest.c
@@ -595,6 +595,63 @@ cleanup:
     return ret;
 }

+/*
+ * Test string handling when no output is present.
+ */
+static int test17(const void *unused ATTRIBUTE_UNUSED)
+{
+    virCommandPtr cmd = virCommandNew("/bin/true");
+    int ret = -1;
+    char *outbuf;
+    char *errbuf;
+
+    virCommandSetOutputBuffer(cmd, &outbuf);
+    if (outbuf != NULL) {
+        puts("buffer not sanitized at registration");
+        goto cleanup;
+    }
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        goto cleanup;
+    }
+
+    if (!outbuf || *outbuf) {
+        puts("output string not allocated");
+        goto cleanup;
+    }
+    VIR_FREE(outbuf);
+    if ((outbuf = strdup("should not be leaked")) == NULL) {
+        puts("test framework failure");
+        goto cleanup;
+    }
+
+    virCommandSetErrorBuffer(cmd, &errbuf);
+    if (errbuf != NULL) {
+        puts("buffer not sanitized at registration");
+        goto cleanup;
+    }
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        goto cleanup;
+    }
+
+    if (!outbuf || *outbuf || !errbuf || *errbuf) {
+        puts("output strings not allocated");
+        goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    virCommandFree(cmd);
+    VIR_FREE(outbuf);
+    VIR_FREE(errbuf);
+    return ret;
+}
+
 static int
 mymain(int argc, char **argv)
 {
@@ -658,6 +715,7 @@ mymain(int argc, char **argv)
     DO_TEST(test14);
     DO_TEST(test15);
     DO_TEST(test16);
+    DO_TEST(test17);

     return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
 }
-- 
1.7.3.2




More information about the libvir-list mailing list