[libvirt] [PATCH 4/8] Introduce new APIs for spawning processes

Daniel P. Berrange berrange at redhat.com
Mon Nov 22 18:09:23 UTC 2010


This introduces a new set of APIs in src/util/command.h
to use for invoking commands. This is intended to replace
all current usage of virRun and virExec variants, with a
more flexible and less error prone API.
---
 src/Makefile.am              |    1 +
 src/libvirt_private.syms     |   25 ++
 src/util/command.c           |  838 ++++++++++++++++++++++++++++++++++++++++++
 src/util/command.h           |  202 ++++++++++
 tests/.gitignore             |    4 +
 tests/Makefile.am            |   14 +-
 tests/commanddata/test1.log  |   12 +
 tests/commanddata/test10.log |   14 +
 tests/commanddata/test11.log |   12 +
 tests/commanddata/test12.log |   12 +
 tests/commanddata/test13.log |   12 +
 tests/commanddata/test2.log  |   14 +
 tests/commanddata/test3.log  |   12 +
 tests/commanddata/test4.log  |   10 +
 tests/commanddata/test5.log  |    6 +
 tests/commanddata/test6.log  |   11 +
 tests/commanddata/test7.log  |    7 +
 tests/commanddata/test8.log  |   14 +
 tests/commanddata/test9.log  |   14 +
 tests/commandhelper.c        |  112 ++++++
 tests/commandtest.c          |  494 +++++++++++++++++++++++++
 21 files changed, 1839 insertions(+), 1 deletions(-)
 create mode 100644 src/util/command.c
 create mode 100644 src/util/command.h
 create mode 100644 tests/commanddata/test1.log
 create mode 100644 tests/commanddata/test10.log
 create mode 100644 tests/commanddata/test11.log
 create mode 100644 tests/commanddata/test12.log
 create mode 100644 tests/commanddata/test13.log
 create mode 100644 tests/commanddata/test2.log
 create mode 100644 tests/commanddata/test3.log
 create mode 100644 tests/commanddata/test4.log
 create mode 100644 tests/commanddata/test5.log
 create mode 100644 tests/commanddata/test6.log
 create mode 100644 tests/commanddata/test7.log
 create mode 100644 tests/commanddata/test8.log
 create mode 100644 tests/commanddata/test9.log
 create mode 100644 tests/commandhelper.c
 create mode 100644 tests/commandtest.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 750612f..2f10b6d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -48,6 +48,7 @@ UTIL_SOURCES =							\
 		util/bitmap.c util/bitmap.h			\
 		util/bridge.c util/bridge.h			\
 		util/buf.c util/buf.h				\
+		util/command.c util/command.h			\
 		util/conf.c util/conf.h				\
 		util/cgroup.c util/cgroup.h			\
 		util/event.c util/event.h			\
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index e808375..fdd23f9 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -82,6 +82,31 @@ virCgroupSetMemorySoftLimit;
 virCgroupSetSwapHardLimit;
 
 
+# command.h
+virCommandAddArg;
+virCommandAddArgPair;
+virCommandAddEnvPass;
+virCommandAddEnvPair;
+virCommandAddEnvString;
+virCommandAddEnvPassCommon;
+virCommandClearCaps;
+virCommandDaemonize;
+virCommandFree;
+virCommandNew;
+virCommandPreserveFD;
+virCommandRun;
+virCommandSetErrorBuffer;
+virCommandSetErrorFD;
+virCommandSetInputBuffer;
+virCommandSetInputFD;
+virCommandSetOutputBuffer;
+virCommandSetOutputFD;
+virCommandSetPidFile;
+virCommandSetPreExecHook;
+virCommandToString;
+virCommandWriteArgLog;
+
+
 # conf.h
 virConfFree;
 virConfFreeValue;
diff --git a/src/util/command.c b/src/util/command.c
new file mode 100644
index 0000000..3f6c6f5
--- /dev/null
+++ b/src/util/command.c
@@ -0,0 +1,838 @@
+/*
+ * command.c: Child command execution
+ *
+ * Copyright (C) 2010 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
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ */
+
+#include <config.h>
+
+#include "command.h"
+#include "memory.h"
+#include "virterror_internal.h"
+#include "util.h"
+#include "logging.h"
+
+#include <stdlib.h>
+#include <poll.h>
+#include <sys/wait.h>
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#define virCommandError(code, ...)                                      \
+    virReportErrorHelper(NULL, VIR_FROM_NONE, code, __FILE__,           \
+                         __FUNCTION__, __LINE__, __VA_ARGS__)
+
+struct _virCommand {
+    int has_error;
+
+    int nargs;
+    char **args;
+
+    int nenv;
+    char **env;
+
+    fd_set preserve;
+    unsigned int flags;
+
+    const char *inbuf;
+    char **outbuf;
+    char **errbuf;
+
+    int infd;
+    int inpipe;
+    int outfd;
+    int errfd;
+    int *outfdptr;
+    int *errfdptr;
+
+    virExecHook hook;
+    void *opaque;
+
+    pid_t pid;
+    char *pidfile;
+};
+
+/*
+ * Create a new command for named binary
+ */
+virCommandPtr virCommandNew(const char *binary)
+{
+    const char *const args[] = { binary, NULL };
+
+    return virCommandNewArgs(args);
+}
+
+/*
+ * Create a new command with a NULL terminated
+ * set of args, taking binary from argv[0]
+ */
+virCommandPtr virCommandNewArgs(const char *const*args)
+{
+    virCommandPtr cmd;
+
+    if (VIR_ALLOC(cmd) < 0)
+        return NULL;
+
+    virCommandAddArgSet(cmd, args);
+
+    if (cmd->has_error) {
+        virCommandFree(cmd);
+        return NULL;
+    }
+
+    FD_ZERO(&cmd->preserve);
+    cmd->infd = cmd->outfd = cmd->errfd = -1;
+    cmd->inpipe = -1;
+    cmd->pid = -1;
+
+    return cmd;
+}
+
+
+/*
+ * Preserve the specified file descriptor
+ * in the child, instead of closing it
+ */
+void virCommandPreserveFD(virCommandPtr cmd,
+                          int fd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    FD_SET(fd, &cmd->preserve);
+}
+
+/*
+ * Save the child PID in a pidfile
+ */
+void virCommandSetPidFile(virCommandPtr cmd,
+                          const char *pidfile)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    VIR_FREE(cmd->pidfile);
+    if (!(cmd->pidfile = strdup(pidfile)))
+        cmd->has_error = 1;
+}
+
+/*
+ * Remove all capabilities from the child
+ */
+void virCommandClearCaps(virCommandPtr cmd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->flags |= VIR_EXEC_CLEAR_CAPS;
+}
+
+
+/*
+ * Re-allow a specific capability
+ */
+void virCommandAllowCap(virCommandPtr cmd,
+                        int capability ATTRIBUTE_UNUSED)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    /* XXX ? */
+}
+
+
+/*
+ * Daemonize the child process
+ */
+void virCommandDaemonize(virCommandPtr cmd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->flags |= VIR_EXEC_DAEMON;
+}
+
+
+/*
+ * Add an environment variable to the child
+ * using separate name & value strings
+ */
+void virCommandAddEnvPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *value)
+{
+    char *env;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (virAsprintf(&env, "%s=%s", name, value ? value : "") < 0) {
+        cmd->has_error = 1;
+        return;
+    }
+
+    if (VIR_REALLOC_N(cmd->env, cmd->nenv + 2) < 0) {
+        VIR_FREE(env);
+        cmd->has_error = 1;
+        return;
+    }
+
+    cmd->env[cmd->nenv] = env;
+    cmd->env[cmd->nenv+1] = NULL;
+    cmd->nenv++;
+}
+
+
+/*
+ * Add an environemnt variable to the child
+ * using a preformated env string FOO=BAR
+ */
+void virCommandAddEnvString(virCommandPtr cmd,
+                            const char *str)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    char *env;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (!(env = strdup(str))) {
+        cmd->has_error = 1;
+        return;
+    }
+
+    if (VIR_REALLOC_N(cmd->env, cmd->nenv + 2) < 0) {
+        VIR_FREE(env);
+        cmd->has_error = 1;
+        return;
+    }
+
+    cmd->env[cmd->nenv] = env;
+    cmd->env[cmd->nenv+1] = NULL;
+    cmd->nenv++;
+}
+
+
+/*
+ * Pass an environment variable to the child
+ * using current process' value
+ */
+void virCommandAddEnvPass(virCommandPtr cmd,
+                          const char *name)
+{
+    char *value;
+    if (!cmd || cmd->has_error)
+        return;
+
+    value = getenv(name);
+    if (value)
+        virCommandAddEnvPair(cmd, name, value);
+}
+
+
+void virCommandAddEnvPassCommon(virCommandPtr cmd)
+{
+    virCommandAddEnvPair(cmd, "LC_ALL", "C");
+
+    virCommandAddEnvPass(cmd, "LD_PRELOAD");
+    virCommandAddEnvPass(cmd, "LD_LIBRARY_PATH");
+    virCommandAddEnvPass(cmd, "PATH");
+    virCommandAddEnvPass(cmd, "HOME");
+    virCommandAddEnvPass(cmd, "USER");
+    virCommandAddEnvPass(cmd, "LOGNAME");
+    virCommandAddEnvPass(cmd, "TMPDIR");
+}
+
+/*
+ * Add a command line argument to the child
+ */
+void virCommandAddArg(virCommandPtr cmd,
+                      const char *val)
+{
+    char *arg;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (!(arg = strdup(val))) {
+        cmd->has_error = 1;
+        return;
+    }
+
+    if (VIR_REALLOC_N(cmd->args, cmd->nargs + 2) < 0) {
+        VIR_FREE(arg);
+        cmd->has_error = 1;
+        return;
+    }
+
+    cmd->args[cmd->nargs] = arg;
+    cmd->args[cmd->nargs+1] = NULL;
+    cmd->nargs++;
+}
+
+
+void virCommandAddArgPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *val)
+{
+    char *arg;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (virAsprintf(&arg, "%s=%s", name, val) < 0) {
+        cmd->has_error = 1;
+        return;
+    }
+
+    if (VIR_REALLOC_N(cmd->args, cmd->nargs + 2) < 0) {
+        VIR_FREE(arg);
+        cmd->has_error = 1;
+        return;
+    }
+
+    cmd->args[cmd->nargs] = arg;
+    cmd->args[cmd->nargs+1] = NULL;
+    cmd->nargs++;
+}
+
+/*
+ * Add a NULL terminated list of args
+ */
+void virCommandAddArgSet(virCommandPtr cmd,
+                         const char *const*vals)
+{
+    int narg = 0;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    while (vals[narg] != NULL)
+        narg++;
+
+    if (VIR_REALLOC_N(cmd->args, cmd->nargs + narg + 1) < 0) {
+        cmd->has_error = 1;
+        return;
+    }
+
+    narg = 0;
+    while (vals[narg] != NULL) {
+        char *arg = strdup(vals[narg]);
+        if (!arg) {
+            cmd->has_error = 1;
+            return;
+        }
+        cmd->args[cmd->nargs + narg] = arg;
+        narg++;
+    }
+    cmd->args[cmd->nargs + narg] = NULL;
+    cmd->nargs += narg;
+}
+
+
+/*
+ * Feed the child's stdin from a string buffer
+ */
+void virCommandSetInputBuffer(virCommandPtr cmd,
+                              const char *inbuf)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->inbuf = inbuf;
+    cmd->infd = -1;
+    cmd->inpipe = -1;
+}
+
+
+/*
+ * Capture the child's stdout to a string buffer
+ */
+void virCommandSetOutputBuffer(virCommandPtr cmd,
+                               char **outbuf)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->outbuf = outbuf;
+    cmd->outfdptr = &cmd->outfd;
+}
+
+
+/*
+ * Capture the child's stderr to a string buffer
+ */
+void virCommandSetErrorBuffer(virCommandPtr cmd,
+                              char **errbuf)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->errbuf = errbuf;
+    cmd->errfdptr = &cmd->errfd;
+}
+
+
+/*
+ * Attach a file descriptor to the child's stdin
+ */
+void virCommandSetInputFD(virCommandPtr cmd,
+                          int infd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->inbuf = NULL;
+    cmd->inpipe = -1;
+    cmd->infd = infd;
+}
+
+
+/*
+ * Attach a file descriptor to the child's stdout
+ */
+void virCommandSetOutputFD(virCommandPtr cmd,
+                           int *outfd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->outbuf = NULL;
+    cmd->outfdptr = outfd;
+}
+
+
+/*
+ * Attach a file descriptor to the child's stderr
+ */
+void virCommandSetErrorFD(virCommandPtr cmd,
+                          int *errfd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->errbuf = NULL;
+    cmd->errfdptr = errfd;
+}
+
+
+void virCommandSetPreExecHook(virCommandPtr cmd,
+                              virExecHook hook,
+                              void *opaque)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->hook = hook;
+    cmd->opaque = opaque;
+}
+
+
+static int
+virCommandProcessIO(virCommandPtr cmd)
+{
+    int infd = -1, outfd = -1, errfd = -1;
+    size_t inlen = 0, outlen = 0, errlen = 0;
+    size_t inoff = 0;
+
+    /* With an input buffer, feed data to child
+     * via pipe */
+    if (cmd->inbuf) {
+        inlen = strlen(cmd->inbuf);
+        infd = cmd->inpipe;
+    }
+
+    /* With out/err buffer, we're the outfd/errfd
+     * have been filled with an FD for us */
+    if (cmd->outbuf)
+        outfd = cmd->outfd;
+    if (cmd->errbuf)
+        errfd = cmd->errfd;
+
+    for (;;) {
+        int i;
+        struct pollfd fds[3];
+        int nfds = 0;
+
+        if (infd != -1) {
+            fds[nfds].fd = infd;
+            fds[nfds].events = POLLOUT;
+            nfds++;
+        }
+        if (outfd != -1) {
+            fds[nfds].fd = outfd;
+            fds[nfds].events = POLLIN;
+            nfds++;
+        }
+        if (errfd != -1) {
+            fds[nfds].fd = errfd;
+            fds[nfds].events = POLLIN;
+            nfds++;
+        }
+
+        if (nfds == 0)
+            break;
+
+        if (poll(fds,nfds, -1) < 0) {
+            if ((errno == EAGAIN) || (errno == EINTR))
+                continue;
+            virReportSystemError(errno, "%s",
+                                 _("unable to poll on child"));
+            return -1;
+        }
+
+        for (i = 0; i < nfds ; i++) {
+            if (fds[i].fd == errfd ||
+                fds[i].fd == outfd) {
+                char data[1024];
+                char **buf;
+                size_t *len;
+                int done;
+                if (fds[i].fd == outfd) {
+                    buf = cmd->outbuf;
+                    len = &outlen;
+                } else {
+                    buf = cmd->errbuf;
+                    len = &errlen;
+                }
+
+                done = read(fds[i].fd, data, sizeof(data));
+                if (done < 0) {
+                    if (errno != EINTR &&
+                        errno != EAGAIN) {
+                        virReportSystemError(errno, "%s",
+                                             _("unable to write to child input"));
+                        return -1;
+                    }
+                } else if (done == 0) {
+                    if (fds[i].fd == outfd)
+                        outfd = -1;
+                    else
+                        errfd = -1;
+                } else {
+                    if (VIR_REALLOC_N(*buf, *len + done + 1) < 0) {
+                        virReportOOMError();
+                        return -1;
+                    }
+                    memmove(*buf + *len, data, done);
+                    *len += done;
+                    (*buf)[*len] = '\0';
+                }
+            } else {
+                int done;
+
+                done = write(infd, cmd->inbuf + inoff,
+                             inlen - inoff);
+                if (done < 0) {
+                    if (errno != EINTR &&
+                        errno != EAGAIN) {
+                        virReportSystemError(errno, "%s",
+                                             _("unable to write to child input"));
+                        return -1;
+                    }
+                } else {
+                    inoff += done;
+                    if (inoff == inlen) {
+                        close(infd);
+                        infd = -1;
+                    }
+                }
+            }
+
+        }
+    }
+
+    return 0;
+
+}
+
+
+/*
+ * Run the command and wait for completion.
+ * Returns -1 on any error executing the
+ * command. Returns 0 if the command executed,
+ * with the exit status set
+ */
+int virCommandRun(virCommandPtr cmd,
+                  int *exitstatus)
+{
+    int ret = 0;
+    char *outbuf = NULL;
+    char *errbuf = NULL;
+    int infd[2];
+    if (!cmd || cmd->has_error) {
+        virReportOOMError();
+        return -1;
+    }
+
+    /* If we have an input buffer, we need
+     * a pipe to feed the data to the child */
+    if (cmd->inbuf) {
+        if (pipe(infd) < 0) {
+            virReportSystemError(errno, "%s",
+                                 _("unable to open pipe"));
+            return -1;
+        }
+        cmd->infd = infd[0];
+        cmd->inpipe = infd[1];
+    }
+
+    if (virCommandRunAsync(cmd, NULL) < 0) {
+        if (cmd->inbuf) {
+            close(infd[0]);
+            close(infd[1]);
+        }
+        return -1;
+    }
+
+    /* If caller hasn't requested capture of
+     * stdout/err, then capture it ourselves
+     * so we can log it
+     */
+    if (!cmd->outbuf &&
+        !cmd->outfdptr) {
+        cmd->outfd = -1;
+        cmd->outfdptr = &cmd->outfd;
+        cmd->outbuf = &outbuf;
+    }
+    if (!cmd->errbuf &&
+        !cmd->errfdptr) {
+        cmd->errfd = -1;
+        cmd->errfdptr = &cmd->errfd;
+        cmd->errbuf = &errbuf;
+    }
+
+    if (cmd->inbuf || cmd->outbuf || cmd->errbuf)
+        ret = virCommandProcessIO(cmd);
+
+    if (virCommandWait(cmd, exitstatus) < 0)
+        ret = -1;
+
+    VIR_DEBUG("Result stdout: '%s' stderr: '%s'",
+              NULLSTR(*cmd->outbuf),
+              NULLSTR(*cmd->errbuf));
+
+    /* Reset any capturing, in case caller runs
+     * this identical command again */
+    if (cmd->inbuf) {
+        close(infd[0]);
+        close(infd[1]);
+    }
+    if (cmd->outbuf == &outbuf) {
+        if (cmd->outfd != -1)
+            close(cmd->outfd);
+        cmd->outfd = -1;
+        cmd->outfdptr = NULL;
+        cmd->outbuf = NULL;
+    }
+    if (cmd->errbuf == &errbuf) {
+        if (cmd->errfd != -1)
+            close(cmd->errfd);
+        cmd->errfd = -1;
+        cmd->errfdptr = NULL;
+        cmd->errbuf = NULL;
+    }
+
+    return ret;
+}
+
+
+/*
+ * Run the command asynchronously
+ * Returns -1 on any error executing the
+ * command. Returns 0 if the command executed.
+ */
+int virCommandRunAsync(virCommandPtr cmd,
+                       pid_t *pid)
+{
+    int ret;
+    char *str;
+
+    if (!cmd || cmd->has_error) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (cmd->pid != -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR,
+                        _("command is already running as pid %d"),
+                        cmd->pid);
+        return -1;
+    }
+
+    str = virArgvToString((const char *const *)cmd->args);
+    VIR_DEBUG("About to run %s", str ? str : cmd->args[0]);
+    VIR_FREE(str);
+
+    ret = virExecWithHook((const char *const *)cmd->args,
+                          (const char *const *)cmd->env,
+                          &cmd->preserve,
+                          &cmd->pid,
+                          cmd->infd,
+                          cmd->outfdptr,
+                          cmd->errfdptr,
+                          cmd->flags,
+                          cmd->hook,
+                          cmd->opaque,
+                          cmd->pidfile);
+
+    VIR_DEBUG("Command result %d, with PID is %d",
+              ret, (int)cmd->pid);
+
+    if (ret == 0 && pid)
+        *pid = cmd->pid;
+
+    return ret;
+}
+
+
+/*
+ * Wait for the async command to complete.
+ * Return -1 on any error waiting for
+ * completion. Returns 0 if the command
+ * finished with the exit status set
+ */
+int virCommandWait(virCommandPtr cmd,
+                   int *exitstatus)
+{
+    int ret;
+    int status;
+
+    if (!cmd || cmd->has_error) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (cmd->pid == -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("command is not yet running"));
+        return -1;
+    }
+
+
+    /* Wait for intermediate process to exit */
+    while ((ret = waitpid(cmd->pid, &status, 0)) == -1 &&
+           errno == EINTR);
+
+    if (ret == -1) {
+        virReportSystemError(errno, _("unable to wait for process %d"),
+                             cmd->pid);
+        return -1;
+    }
+
+    cmd->pid = -1;
+
+    if (exitstatus == NULL) {
+        if (status != 0) {
+            virCommandError(VIR_ERR_INTERNAL_ERROR,
+                            _("Intermediate daemon process exited with status %d."),
+                            WEXITSTATUS(status));
+            return -1;
+        }
+    } else {
+        *exitstatus = status;
+    }
+
+    return 0;
+}
+
+
+void virCommandWriteArgLog(virCommandPtr cmd, int logfd)
+{
+    int ioError = 0;
+    int i;
+
+    for (i = 0 ; i < cmd->nenv ; i++) {
+        if (safewrite(logfd, cmd->env[i], strlen(cmd->env[i])) < 0)
+            ioError = errno;
+        if (safewrite(logfd, " ", 1) < 0)
+            ioError  = errno;
+    }
+    for (i = 0 ; i < cmd->nargs ; i++) {
+        if (safewrite(logfd, cmd->args[i], strlen(cmd->args[i])) < 0)
+            ioError = errno;
+        if (safewrite(logfd, " ", 1) < 0)
+            ioError  = errno;
+    }
+
+    if (ioError) {
+        char ebuf[1024];
+        VIR_WARN("Unable to write command %s args to logfile: %s",
+                 cmd->args[0], virStrerror(ioError, ebuf, sizeof ebuf));
+    }
+}
+
+
+char *virCommandToString(virCommandPtr cmd)
+{
+    int i;
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    if (!cmd || cmd->has_error) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    for (i = 0 ; i < cmd->nenv ; i++) {
+        virBufferAdd(&buf, cmd->env[i], strlen(cmd->env[i]));
+        virBufferAddLit(&buf, " ");
+    }
+    for (i = 0 ; i < cmd->nargs ; i++) {
+        virBufferAdd(&buf, cmd->args[i], strlen(cmd->args[i]));
+        virBufferAddLit(&buf, " ");
+    }
+
+    if (virBufferError(&buf)) {
+        virBufferFreeAndReset(&buf);
+        virReportOOMError();
+        return NULL;
+    }
+
+    return virBufferContentAndReset(&buf);
+}
+
+
+/*
+ * Release all resources
+ *
+ * XXX close all FDs in cmd->preserve
+ */
+void virCommandFree(virCommandPtr cmd)
+{
+    int i;
+    if (!cmd)
+        return;
+
+    if (cmd->outfd != -1)
+        close(cmd->outfd);
+    if (cmd->errfd != -1)
+        close(cmd->errfd);
+
+    for (i = 0 ; i < cmd->nargs ; i++)
+        VIR_FREE(cmd->args[i]);
+    VIR_FREE(cmd->args);
+
+    for (i = 0 ; i < cmd->nenv ; i++)
+        VIR_FREE(cmd->env[i]);
+    VIR_FREE(cmd->env);
+
+    VIR_FREE(cmd->pidfile);
+
+    VIR_FREE(cmd);
+}
diff --git a/src/util/command.h b/src/util/command.h
new file mode 100644
index 0000000..8b99361
--- /dev/null
+++ b/src/util/command.h
@@ -0,0 +1,202 @@
+/*
+ * command.h: Child command execution
+ *
+ * Copyright (C) 2010 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
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ */
+
+#ifndef __VIR_COMMAND_H__
+# define __VIR_COMMAND_H__
+
+# include "internal.h"
+# include "util.h"
+
+typedef struct _virCommand virCommand;
+typedef virCommand *virCommandPtr;
+
+/*
+ * Create a new command for named binary
+ */
+virCommandPtr virCommandNew(const char *binary);
+
+/*
+ * Create a new command with a NULL terminated
+ * set of args, taking binary from argv[0]
+ */
+virCommandPtr virCommandNewArgs(const char *const*args);
+
+/* All error report from these setup APIs is
+ * delayed until the Run/Exec/Wait methods
+ */
+
+/*
+ * Preserve the specified file descriptor
+ * in the child, instead of closing it
+ */
+void virCommandPreserveFD(virCommandPtr cmd,
+                          int fd);
+
+/*
+ * Save the child PID in a pidfile
+ */
+void virCommandSetPidFile(virCommandPtr cmd,
+                          const char *pidfile);
+
+/*
+ * Remove all capabilities from the child
+ */
+void virCommandClearCaps(virCommandPtr cmd);
+
+/*
+ * Re-allow a specific capability
+ */
+void virCommandAllowCap(virCommandPtr cmd,
+                        int capability);
+
+/*
+ * Daemonize the child process
+ */
+void virCommandDaemonize(virCommandPtr cmd);
+
+
+/*
+ * Add an environment variable to the child
+ * using separate name & value strings
+ */
+void virCommandAddEnvPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *value);
+
+/*
+ * Add an environemnt variable to the child
+ * using a preformated env string FOO=BAR
+ */
+void virCommandAddEnvString(virCommandPtr cmd,
+                            const char *str);
+/*
+ * Pass an environment variable to the child
+ * using current process' value
+ */
+void virCommandAddEnvPass(virCommandPtr cmd,
+                          const char *name);
+/*
+ * Pass a common set of environment variables
+ * to the child using current process' values
+ */
+void virCommandAddEnvPassCommon(virCommandPtr cmd);
+
+/*
+ * Add a command line argument to the child
+ */
+void virCommandAddArg(virCommandPtr cmd,
+                      const char *val);
+/*
+ * Add a command line argument to the child
+ */
+void virCommandAddArgPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *val);
+/*
+ * Add a NULL terminated list of args
+ */
+void virCommandAddArgSet(virCommandPtr cmd,
+                         const char *const*vals);
+
+
+/*
+ * Feed the child's stdin from a string buffer.
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetInputBuffer(virCommandPtr cmd,
+                              const char *inbuf);
+/*
+ * Capture the child's stdout to a string buffer
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetOutputBuffer(virCommandPtr cmd,
+                               char **outbuf);
+/*
+ * Capture the child's stderr to a string buffer
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetErrorBuffer(virCommandPtr cmd,
+                              char **errbuf);
+
+/*
+ * Set a file descriptor as the child's stdin
+ */
+void virCommandSetInputFD(virCommandPtr cmd,
+                          int infd);
+/*
+ * Set a file descriptor as the child's stdout
+ */
+void virCommandSetOutputFD(virCommandPtr cmd,
+                           int *outfd);
+/*
+ * Set a file descriptor as the child's stderr
+ */
+void virCommandSetErrorFD(virCommandPtr cmd,
+                          int *errfd);
+
+/*
+ * A hook function to run between fork + exec
+ */
+void virCommandSetPreExecHook(virCommandPtr cmd,
+                              virExecHook hook,
+                              void *opaque);
+
+/*
+ * Run the command and wait for completion.
+ * Returns -1 on any error executing the
+ * command. Returns 0 if the command executed,
+ * with the exit status set
+ */
+int virCommandRun(virCommandPtr cmd,
+                  int *exitstatus) ATTRIBUTE_RETURN_CHECK;
+
+/*
+ * Run the command asynchronously
+ * Returns -1 on any error executing the
+ * command. Returns 0 if the command executed.
+ */
+int virCommandRunAsync(virCommandPtr cmd,
+                       pid_t *pid) ATTRIBUTE_RETURN_CHECK;
+
+char *virCommandToString(virCommandPtr cmd);
+
+/*
+ * Wait for the async command to complete.
+ * Return -1 on any error waiting for
+ * completion. Returns 0 if the command
+ * finished with the exit status set
+ */
+int virCommandWait(virCommandPtr cmd,
+                   int *exitstatus) ATTRIBUTE_RETURN_CHECK;
+
+void virCommandWriteArgLog(virCommandPtr cmd,
+                           int fd);
+
+/*
+ * Release all resources
+ */
+void virCommandFree(virCommandPtr cmd);
+
+
+#endif /* __VIR_COMMAND_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 8ad3e98..393a131 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -32,3 +32,7 @@ xencapstest
 xmconfigtest
 xml2sexprtest
 xml2vmxtest
+commandtest
+commandhelper
+commandhelper.pid
+commandhelper.log
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 77b6fb9..2b98835 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -72,7 +72,8 @@ EXTRA_DIST =		\
 	qemuhelpdata
 
 check_PROGRAMS = virshtest conftest sockettest \
-        nodeinfotest qparamtest virbuftest
+        nodeinfotest qparamtest virbuftest \
+	commandtest commandhelper
 
 if WITH_XEN
 check_PROGRAMS += xml2sexprtest sexpr2xmltest \
@@ -154,6 +155,7 @@ TESTS = virshtest \
 	qparamtest \
 	virbuftest \
 	sockettest \
+	commandtest \
 	$(test_scripts)
 
 if WITH_XEN
@@ -339,6 +341,16 @@ nodeinfotest_SOURCES = \
 	nodeinfotest.c testutils.h testutils.c
 nodeinfotest_LDADD = $(LDADDS)
 
+commandtest_SOURCES = \
+	commandtest.c testutils.h testutils.c
+commandtest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\""
+commandtest_LDADD = $(LDADDS)
+
+commandhelper_SOURCES = \
+	commandhelper.c
+commandhelper_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\""
+commandhelper_LDADD = $(LDADDS)
+
 if WITH_SECDRIVER_SELINUX
 seclabeltest_SOURCES = \
 	seclabeltest.c
diff --git a/tests/commanddata/test1.log b/tests/commanddata/test1.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test1.log
@@ -0,0 +1,12 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test10.log b/tests/commanddata/test10.log
new file mode 100644
index 0000000..e1d6092
--- /dev/null
+++ b/tests/commanddata/test10.log
@@ -0,0 +1,14 @@
+ARG:-version
+ARG:-log=bar.log
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test11.log b/tests/commanddata/test11.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test11.log
@@ -0,0 +1,12 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test12.log b/tests/commanddata/test12.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test12.log
@@ -0,0 +1,12 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test13.log b/tests/commanddata/test13.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test13.log
@@ -0,0 +1,12 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test2.log b/tests/commanddata/test2.log
new file mode 100644
index 0000000..6bd7974
--- /dev/null
+++ b/tests/commanddata/test2.log
@@ -0,0 +1,14 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+FD:3
+FD:5
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test3.log b/tests/commanddata/test3.log
new file mode 100644
index 0000000..1876685
--- /dev/null
+++ b/tests/commanddata/test3.log
@@ -0,0 +1,12 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:yes
+CWD:/
diff --git a/tests/commanddata/test4.log b/tests/commanddata/test4.log
new file mode 100644
index 0000000..f745c3f
--- /dev/null
+++ b/tests/commanddata/test4.log
@@ -0,0 +1,10 @@
+ENV:HOME=/home/test
+ENV:LC_ALL=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test5.log b/tests/commanddata/test5.log
new file mode 100644
index 0000000..5394428
--- /dev/null
+++ b/tests/commanddata/test5.log
@@ -0,0 +1,6 @@
+ENV:DISPLAY=:0.0
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test6.log b/tests/commanddata/test6.log
new file mode 100644
index 0000000..cdfe445
--- /dev/null
+++ b/tests/commanddata/test6.log
@@ -0,0 +1,11 @@
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:LC_ALL=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test7.log b/tests/commanddata/test7.log
new file mode 100644
index 0000000..87874fd
--- /dev/null
+++ b/tests/commanddata/test7.log
@@ -0,0 +1,7 @@
+ENV:LANG=C
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test8.log b/tests/commanddata/test8.log
new file mode 100644
index 0000000..e1d6092
--- /dev/null
+++ b/tests/commanddata/test8.log
@@ -0,0 +1,14 @@
+ARG:-version
+ARG:-log=bar.log
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commanddata/test9.log b/tests/commanddata/test9.log
new file mode 100644
index 0000000..e1d6092
--- /dev/null
+++ b/tests/commanddata/test9.log
@@ -0,0 +1,14 @@
+ARG:-version
+ARG:-log=bar.log
+ENV:DISPLAY=:0.0
+ENV:HOME=/home/test
+ENV:HOSTNAME=test
+ENV:LANG=C
+ENV:LOGNAME=testTMPDIR=/tmp
+ENV:PATH=/usr/bin:/bin
+ENV:USER=test
+FD:0
+FD:1
+FD:2
+DAEMON:no
+CWD:/tmp
diff --git a/tests/commandhelper.c b/tests/commandhelper.c
new file mode 100644
index 0000000..f22a4d3
--- /dev/null
+++ b/tests/commandhelper.c
@@ -0,0 +1,112 @@
+
+#include <config.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "internal.h"
+#include "util.h"
+#include "memory.h"
+
+
+static int envsort(const void *a, const void *b) {
+    const char *const*astrptr = a;
+    const char *const*bstrptr = b;
+    const char *astr = *astrptr;
+    const char *bstr = *bstrptr;
+    char *aeq = strchr(astr, '=');
+    char *beq = strchr(bstr, '=');
+    char *akey = strndup(astr, aeq - astr);
+    char *bkey = strndup(bstr, beq - bstr);
+    int ret = strcmp(akey, bkey);
+    free(akey);
+    free(bkey);
+    return ret;
+}
+
+int main(int argc, char **argv) {
+    int i, n;
+    char **origenv;
+    char **newenv;
+    FILE *log = fopen(abs_builddir "/commandhelper.log", "w");
+
+    if (!log)
+        goto error;
+
+    for (i = 1 ; i < argc ; i++) {
+        fprintf(log, "ARG:%s\n", argv[i]);
+    }
+
+    origenv = environ;
+    n = 0;
+    while (*origenv != NULL) {
+        n++;
+        origenv++;
+    }
+
+    if (VIR_ALLOC_N(newenv, n) < 0) {
+        exit(EXIT_FAILURE);
+    }
+
+    origenv = environ;
+    n = i = 0;
+    while (*origenv != NULL) {
+        newenv[i++] = *origenv;
+        n++;
+        origenv++;
+    }
+    qsort(newenv, n, sizeof(newenv[0]), envsort);
+
+    for (i = 0 ; i < n ; i++) {
+        fprintf(log, "ENV:%s\n", newenv[i]);
+    }
+
+    for (i = 0 ; i < sysconf(_SC_OPEN_MAX) ; i++) {
+        int f;
+        int closed;
+        if (i == fileno(log))
+            continue;
+        closed = fcntl(i, F_GETFD, &f) == -1 &&
+            errno == EBADF;
+        if (!closed)
+            fprintf(log, "FD:%d\n", i);
+    }
+
+    fprintf(log, "DAEMON:%s\n", getppid() == 1 ? "yes" : "no");
+    char cwd[1024];
+    fprintf(log, "CWD:%s\n", getcwd(cwd, sizeof(cwd)));
+
+    fclose(log);
+
+    char buf[1024];
+    ssize_t got;
+
+    fprintf(stdout, "BEGIN STDOUT\n");
+    fflush(stdout);
+    fprintf(stderr, "BEGIN STDERR\n");
+    fflush(stderr);
+
+    for (;;) {
+        got = read(STDIN_FILENO, buf, sizeof(buf));
+        if (got < 0)
+            goto error;
+        if (got == 0)
+            break;
+        if (safewrite(STDOUT_FILENO, buf, got) != got)
+            goto error;
+        if (safewrite(STDERR_FILENO, buf, got) != got)
+            goto error;
+    }
+
+    fprintf(stdout, "END STDOUT\n");
+    fflush(stdout);
+    fprintf(stderr, "END STDERR\n");
+    fflush(stderr);
+
+    return EXIT_SUCCESS;
+
+error:
+    return EXIT_FAILURE;
+}
diff --git a/tests/commandtest.c b/tests/commandtest.c
new file mode 100644
index 0000000..c66d345
--- /dev/null
+++ b/tests/commandtest.c
@@ -0,0 +1,494 @@
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "testutils.h"
+#include "internal.h"
+#include "nodeinfo.h"
+#include "util.h"
+#include "memory.h"
+#include "command.h"
+
+#ifndef __linux__
+
+static int
+mymain(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+    exit (EXIT_AM_SKIP);
+}
+
+#else
+
+static char *progname;
+static char *abs_srcdir;
+
+
+static int checkoutput(const char *testname) {
+    int ret = -1;
+    char cwd[1024];
+    char *expectname = NULL;
+    char *expectlog = NULL;
+    char *actualname = NULL;
+    char *actuallog = NULL;
+
+    if (!getcwd(cwd, sizeof(cwd)))
+        return -1;
+
+    if (virAsprintf(&expectname, "%s/commanddata/%s.log", abs_srcdir, testname) < 0)
+        goto cleanup;
+    if (virAsprintf(&actualname, "%s/commandhelper.log", abs_builddir) < 0)
+        goto cleanup;
+
+    if (virFileReadAll(expectname, 1024*64, &expectlog) < 0) {
+        fprintf(stderr, "cannot read %s\n", expectname);
+        goto cleanup;
+    }
+
+    if (virFileReadAll(actualname, 1024*64, &actuallog) < 0) {
+        fprintf(stderr, "cannot read %s\n", actualname);
+        goto cleanup;
+    }
+
+    if (STRNEQ(expectlog, actuallog)) {
+        virtTestDifference(stderr, expectlog, actuallog);
+        goto cleanup;
+    }
+
+
+    ret = 0;
+
+cleanup:
+    unlink(actuallog);
+    VIR_FREE(actuallog);
+    VIR_FREE(actualname);
+    VIR_FREE(expectlog);
+    VIR_FREE(expectname);
+    return ret;
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test0(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        return 0;
+    }
+
+    virCommandFree(cmd);
+
+    return -1;
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test1(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test1");
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * stdin/out/err + two extra FD open
+ */
+static int test2(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+    int newfd1 = dup(STDERR_FILENO);
+    int newfd2 = dup(STDERR_FILENO);
+    int newfd3 = dup(STDERR_FILENO);
+    close(newfd2);
+
+    virCommandPreserveFD(cmd, newfd1);
+    virCommandPreserveFD(cmd, newfd3);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test2");
+}
+
+
+/*
+ * Run program, no args, inherit all ENV, CWD is /
+ * Only stdin/out/err open.
+ * Daemonized
+ */
+static int test3(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+    pid_t pid;
+    char *pidfile = virFilePid(abs_builddir, "commandhelper");
+
+    virCommandSetPidFile(cmd, pidfile);
+    virCommandDaemonize(cmd);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    if (virFileReadPid(abs_builddir, "commandhelper", &pid) != 0) {
+        printf("cannot read pidfile\n");
+        return -1;
+    }
+    while (kill(pid, 0) != -1)
+        usleep(100*1000);
+
+    virCommandFree(cmd);
+
+    VIR_FREE(pidfile);
+
+    return checkoutput("test3");
+}
+
+
+/*
+ * Run program, no args, inherit filtered ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test4(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandAddEnvPassCommon(cmd);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test4");
+}
+
+
+/*
+ * Run program, no args, inherit filtered ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test5(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandAddEnvPass(cmd, "DISPLAY");
+    virCommandAddEnvPass(cmd, "DOESNOTEXIST");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test5");
+}
+
+
+/*
+ * Run program, no args, inherit filtered ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test6(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandAddEnvPassCommon(cmd);
+    virCommandAddEnvPass(cmd, "DISPLAY");
+    virCommandAddEnvPass(cmd, "DOESNOTEXIST");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test6");
+}
+
+/*
+ * Run program, no args, inherit filtered ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test7(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandAddEnvString(cmd, "LANG=C");
+    virCommandAddEnvPair(cmd, "USER", "test");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test7");
+}
+
+
+/*
+ * Run program, some args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test8(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandAddArg(cmd, "-version");
+    virCommandAddArgPair(cmd, "-log", "bar.log");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test8");
+}
+
+
+/*
+ * Run program, some args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test9(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+    const char *const args[] = {
+        "-version", "-log=bar.log", NULL,
+    };
+
+    virCommandAddArgSet(cmd, args);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test9");
+}
+
+/*
+ * Run program, some args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test10(const void *unused ATTRIBUTE_UNUSED) {
+    const char *args[] = {
+        abs_builddir "/commandhelper",
+        "-version", "-log=bar.log", NULL,
+    };
+    virCommandPtr cmd = virCommandNewArgs(args);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test10");
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open. Set stdin data
+ */
+static int test11(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandSetInputBuffer(cmd, "Hello World\n");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test11");
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open. Set stdin data
+ */
+static int test12(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+    char *outactual = NULL;
+    const char *outexpect = "BEGIN STDOUT\n"
+        "Hello World\n"
+        "END STDOUT\n";
+    int ret = -1;
+
+    virCommandSetInputBuffer(cmd, "Hello World\n");
+    virCommandSetOutputBuffer(cmd, &outactual);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    if (!STREQ(outactual, outexpect)) {
+        virtTestDifference(stderr, outactual, outexpect);
+        goto cleanup;
+    }
+
+    if (checkoutput("test12") < 0)
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(outactual);
+    return ret;
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open. Set stdin data
+ */
+static int test13(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+    char *outactual = NULL;
+    const char *outexpect = "BEGIN STDOUT\n"
+        "Hello World\n"
+        "END STDOUT\n";
+    char *erractual = NULL;
+    const char *errexpect = "BEGIN STDERR\n"
+        "Hello World\n"
+        "END STDERR\n";
+    int ret = -1;
+
+    virCommandSetInputBuffer(cmd, "Hello World\n");
+    virCommandSetOutputBuffer(cmd, &outactual);
+    virCommandSetErrorBuffer(cmd, &erractual);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    if (!STREQ(outactual, outexpect)) {
+        virtTestDifference(stderr, outactual, outexpect);
+        goto cleanup;
+    }
+    if (!STREQ(erractual, errexpect)) {
+        virtTestDifference(stderr, erractual, errexpect);
+        goto cleanup;
+    }
+
+    if (checkoutput("test13") < 0)
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(outactual);
+    VIR_FREE(erractual);
+    return ret;
+}
+
+
+static int
+mymain(int argc, char **argv)
+{
+    int ret = 0;
+    char cwd[PATH_MAX];
+
+    abs_srcdir = getenv("abs_srcdir");
+    if (!abs_srcdir)
+        abs_srcdir = getcwd(cwd, sizeof(cwd));
+
+    progname = argv[0];
+
+    if (argc > 1) {
+        fprintf(stderr, "Usage: %s\n", progname);
+        return(EXIT_FAILURE);
+    }
+
+    if (chdir("/tmp") < 0)
+        return(EXIT_FAILURE);
+
+    virInitialize();
+
+    const char *const newenv[] = {
+        "PATH=/usr/bin:/bin",
+        "HOSTNAME=test",
+        "LANG=C",
+        "HOME=/home/test",
+        "USER=test",
+        "LOGNAME=test"
+        "TMPDIR=/tmp",
+        "DISPLAY=:0.0",
+        NULL
+    };
+    environ = (char **)newenv;
+
+#define DO_TEST(NAME)                                                 \
+    if (virtTestRun("Command Exec " #NAME " test",                    \
+                    1, NAME, NULL) < 0)                               \
+        ret = -1
+
+    char *actualname;
+    if (virAsprintf(&actualname, "%s/commandhelper.log", abs_builddir) < 0)
+        return EXIT_FAILURE;
+    unlink(actualname);
+    VIR_FREE(actualname);
+
+    DO_TEST(test0);
+    DO_TEST(test1);
+    DO_TEST(test2);
+    DO_TEST(test3);
+    DO_TEST(test4);
+    DO_TEST(test5);
+    DO_TEST(test6);
+    DO_TEST(test7);
+    DO_TEST(test8);
+    DO_TEST(test9);
+    DO_TEST(test10);
+    DO_TEST(test11);
+    DO_TEST(test12);
+    DO_TEST(test13);
+
+    return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+#endif /* __linux__ */
+
+VIRT_TEST_MAIN(mymain)
-- 
1.7.2.3




More information about the libvir-list mailing list