[libvirt] [PATCHv2 3/8] Introduce new APIs for spawning processes

Eric Blake eblake at redhat.com
Tue Nov 23 23:49:56 UTC 2010


From: Daniel P. Berrange <berrange at redhat.com>

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/util/command.c: New file.
* src/util/command.h: New header.
* src/Makefile.am (UTIL_SOURCES): Build it.
* src/libvirt_private.syms: Export symbols internally.
* tests/commandtest.c: New test.
* tests/Makefile.am (check_PROGRAMS): Run it.
* tests/commandhelper.c: Auxiliary program.
* tests/commanddata/test2.log - test15.log: New expected outputs.
* cfg.mk (useless_free_options): Add virCommandFree.
* po/POTFILES.in: New translation.
* .x-sc_avoid_write: Add exemption.
* tests/.gitignore: Ignore new built file.
---

v2: add virCommandTransferFD, virCommandToString, virCommandAddArgFormat,
virCommandNewArgList, virCommandWriteArgLog, virCommandNonblockingFDs.
Fix virCommandRunAsync and virCommandFree to free transfered FDs.
Add a bit more test exposure.

 .x-sc_avoid_write            |    1 +
 cfg.mk                       |    1 +
 po/POTFILES.in               |    1 +
 src/Makefile.am              |    1 +
 src/libvirt_private.syms     |   35 ++
 src/util/command.c           | 1138 ++++++++++++++++++++++++++++++++++++++++++
 src/util/command.h           |  260 ++++++++++
 tests/.gitignore             |    4 +
 tests/Makefile.am            |   18 +-
 tests/commanddata/test10.log |   14 +
 tests/commanddata/test11.log |   14 +
 tests/commanddata/test12.log |   12 +
 tests/commanddata/test13.log |   12 +
 tests/commanddata/test14.log |   12 +
 tests/commanddata/test15.log |   12 +
 tests/commanddata/test16.log |    1 +
 tests/commanddata/test2.log  |   12 +
 tests/commanddata/test3.log  |   14 +
 tests/commanddata/test4.log  |   12 +
 tests/commanddata/test5.log  |   10 +
 tests/commanddata/test6.log  |    6 +
 tests/commanddata/test7.log  |   11 +
 tests/commanddata/test8.log  |    7 +
 tests/commanddata/test9.log  |   18 +
 tests/commandhelper.c        |  137 +++++
 tests/commandtest.c          |  630 +++++++++++++++++++++++
 26 files changed, 2392 insertions(+), 1 deletions(-)
 create mode 100644 src/util/command.c
 create mode 100644 src/util/command.h
 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/test14.log
 create mode 100644 tests/commanddata/test15.log
 create mode 100644 tests/commanddata/test16.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/.x-sc_avoid_write b/.x-sc_avoid_write
index 232504f..f6fc1b2 100644
--- a/.x-sc_avoid_write
+++ b/.x-sc_avoid_write
@@ -1,6 +1,7 @@
 ^src/libvirt\.c$
 ^src/fdstream\.c$
 ^src/qemu/qemu_monitor\.c$
+^src/util/command\.c$
 ^src/util/util\.c$
 ^src/xen/xend_internal\.c$
 ^daemon/libvirtd.c$
diff --git a/cfg.mk b/cfg.mk
index dea8301..6312632 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -77,6 +77,7 @@ useless_free_options =				\
   --name=virCapabilitiesFreeHostNUMACell	\
   --name=virCapabilitiesFreeMachines		\
   --name=virCgroupFree				\
+  --name=virCommandFree				\
   --name=virConfFreeList			\
   --name=virConfFreeValue			\
   --name=virDomainChrDefFree			\
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2820ac1..e7be0d3 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -78,6 +78,7 @@ src/uml/uml_driver.c
 src/util/authhelper.c
 src/util/bridge.c
 src/util/cgroup.c
+src/util/command.c
 src/util/conf.c
 src/util/dnsmasq.c
 src/util/hooks.c
diff --git a/src/Makefile.am b/src/Makefile.am
index a9a1986..0923d60 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 d9f70a7..f97a315 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -83,6 +83,41 @@ virCgroupSetMemorySoftLimit;
 virCgroupSetSwapHardLimit;


+# command.h
+virCommandAddArg;
+virCommandAddArgFormat;
+virCommandAddArgList;
+virCommandAddArgPair;
+virCommandAddArgSet;
+virCommandAddEnvPair;
+virCommandAddEnvPass;
+virCommandAddEnvPassCommon;
+virCommandAddEnvString;
+virCommandClearCaps;
+virCommandDaemonize;
+virCommandFree;
+virCommandNew;
+virCommandNewArgList;
+virCommandNewArgs;
+virCommandNonblockingFDs;
+virCommandPreserveFD;
+virCommandRun;
+virCommandRunAsync;
+virCommandSetErrorBuffer;
+virCommandSetErrorFD;
+virCommandSetInputBuffer;
+virCommandSetInputFD;
+virCommandSetOutputBuffer;
+virCommandSetOutputFD;
+virCommandSetPidFile;
+virCommandSetPreExecHook;
+virCommandSetWorkingDirectory;
+virCommandToString;
+virCommandTransferFD;
+virCommandWait;
+virCommandWriteArgLog;
+
+
 # conf.h
 virConfFree;
 virConfFreeValue;
diff --git a/src/util/command.c b/src/util/command.c
new file mode 100644
index 0000000..948a957
--- /dev/null
+++ b/src/util/command.c
@@ -0,0 +1,1138 @@
+/*
+ * 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 <poll.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "command.h"
+#include "memory.h"
+#include "virterror_internal.h"
+#include "util.h"
+#include "logging.h"
+#include "files.h"
+#include "buf.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; /* ENOMEM on allocation failure, -1 for anything else.  */
+
+    char **args;
+    size_t nargs;
+    size_t maxargs;
+
+    char **env;
+    size_t nenv;
+    size_t maxenv;
+
+    char *pwd;
+
+    /* XXX Use int[] if we ever need to support more than FD_SETSIZE fd's.  */
+    fd_set preserve; /* FDs to pass to child. */
+    fd_set transfer; /* FDs to close in parent. */
+
+    unsigned int flags;
+
+    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;
+
+    FD_ZERO(&cmd->preserve);
+    FD_ZERO(&cmd->transfer);
+    cmd->infd = cmd->outfd = cmd->errfd = -1;
+    cmd->inpipe = -1;
+    cmd->pid = -1;
+
+    virCommandAddArgSet(cmd, args);
+
+    if (cmd->has_error) {
+        virCommandFree(cmd);
+        return NULL;
+    }
+
+    return cmd;
+}
+
+/*
+ * Create a new command with a NULL terminated
+ * list of args, starting with the binary to run
+ */
+virCommandPtr
+virCommandNewArgList(const char *binary, ...)
+{
+    virCommandPtr cmd = virCommandNew(binary);
+    va_list list;
+    const char *arg;
+
+    if (!cmd || cmd->has_error)
+        return NULL;
+
+    va_start(list, binary);
+    while ((arg = va_arg(list, const char *)) != NULL)
+        virCommandAddArg(cmd, arg);
+    va_end(list);
+    return cmd;
+}
+
+
+/*
+ * Preserve the specified file descriptor in the child, instead of
+ * closing it.  FD must not be one of the three standard streams.  If
+ * transfer is true, then fd will be closed in the parent after a call
+ * to Run/RunAsync/Free, otherwise caller is still responsible for fd.
+ */
+static void
+virCommandKeepFD(virCommandPtr cmd, int fd, bool transfer)
+{
+    if (!cmd)
+        return;
+
+    if (fd <= STDERR_FILENO || FD_SETSIZE <= fd) {
+        if (!cmd->has_error)
+            cmd->has_error = -1;
+        VIR_DEBUG("cannot preserve %d", fd);
+        return;
+    }
+
+    FD_SET(fd, &cmd->preserve);
+    if (transfer)
+        FD_SET(fd, &cmd->transfer);
+}
+
+/*
+ * Preserve the specified file descriptor
+ * in the child, instead of closing it.
+ * The parent is still responsible for managing fd.
+ */
+void
+virCommandPreserveFD(virCommandPtr cmd, int fd)
+{
+    return virCommandKeepFD(cmd, fd, false);
+}
+
+/*
+ * Transfer the specified file descriptor
+ * to the child, instead of closing it.
+ * Close the fd in the parent during Run/RunAsync/Free.
+ */
+void
+virCommandTransferFD(virCommandPtr cmd, int fd)
+{
+    return virCommandKeepFD(cmd, fd, true);
+}
+
+
+/*
+ * 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 = ENOMEM;
+    }
+}
+
+
+/*
+ * Remove all capabilities from the child
+ */
+void
+virCommandClearCaps(virCommandPtr cmd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->flags |= VIR_EXEC_CLEAR_CAPS;
+}
+
+#if 0 /* XXX Enable if we have a need for capability management.  */
+
+/*
+ * Re-allow a specific capability
+ */
+void
+virCommandAllowCap(virCommandPtr cmd,
+                   int capability ATTRIBUTE_UNUSED)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    /* XXX ? */
+}
+
+#endif /* 0 */
+
+
+/*
+ * Daemonize the child process
+ */
+void
+virCommandDaemonize(virCommandPtr cmd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->flags |= VIR_EXEC_DAEMON;
+}
+
+/*
+ * Set FDs created by virCommandSetOutputFD and virCommandSetErrorFD
+ * as non-blocking in the parent.
+ */
+void
+virCommandNonblockingFDs(virCommandPtr cmd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->flags |= VIR_EXEC_NONBLOCK;
+}
+
+/*
+ * 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 = ENOMEM;
+        return;
+    }
+
+    /* env plus trailing NULL */
+    if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 1 + 1) < 0) {
+        VIR_FREE(env);
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    cmd->env[cmd->nenv++] = env;
+}
+
+
+/*
+ * Add an environment variable to the child
+ * using a preformatted 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 = ENOMEM;
+        return;
+    }
+
+    /* env plus trailing NULL */
+    if (VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 1 + 1) < 0) {
+        VIR_FREE(env);
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    cmd->env[cmd->nenv++] = env;
+}
+
+
+/*
+ * 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);
+}
+
+
+/*
+ * Set LC_ALL to C, and propagate other essential environment
+ * variables from the parent process.
+ */
+void
+virCommandAddEnvPassCommon(virCommandPtr cmd)
+{
+    /* Attempt to Pre-allocate; allocation failure will be detected
+     * later during virCommandAdd*.  */
+    ignore_value(VIR_RESIZE_N(cmd->env, cmd->maxenv, cmd->nenv, 9));
+
+    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 = ENOMEM;
+        return;
+    }
+
+    /* Arg plus trailing NULL. */
+    if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) {
+        VIR_FREE(arg);
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    cmd->args[cmd->nargs++] = arg;
+}
+
+
+/*
+ * Add a command line argument created by a printf-style format
+ */
+void
+virCommandAddArgFormat(virCommandPtr cmd, const char *format, ...)
+{
+    char *arg;
+    va_list list;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    va_start(list, format);
+    if (virVasprintf(&arg, format, list) < 0) {
+        cmd->has_error = ENOMEM;
+        va_end(list);
+        return;
+    }
+    va_end(list);
+
+    /* Arg plus trailing NULL. */
+    if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, 1 + 1) < 0) {
+        VIR_FREE(arg);
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    cmd->args[cmd->nargs++] = arg;
+}
+
+/*
+ * Add "NAME=VAL" as a single command line argument to the child
+ */
+void
+virCommandAddArgPair(virCommandPtr cmd, const char *name, const char *val)
+{
+    virCommandAddArgFormat(cmd, "%s=%s", name, val);
+}
+
+/*
+ * 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++;
+
+    /* narg plus trailing NULL. */
+    if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) {
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    narg = 0;
+    while (vals[narg] != NULL) {
+        char *arg = strdup(vals[narg++]);
+        if (!arg) {
+            cmd->has_error = ENOMEM;
+            return;
+        }
+        cmd->args[cmd->nargs++] = arg;
+    }
+}
+
+/*
+ * Add a NULL terminated list of args
+ */
+void
+virCommandAddArgList(virCommandPtr cmd, ...)
+{
+    va_list list;
+    int narg = 0;
+
+    if (!cmd || cmd->has_error)
+        return;
+
+    va_start(list, cmd);
+    while (va_arg(list, const char *) != NULL)
+        narg++;
+    va_end(list);
+
+    /* narg plus trailing NULL. */
+    if (VIR_RESIZE_N(cmd->args, cmd->maxargs, cmd->nargs, narg + 1) < 0) {
+        cmd->has_error = ENOMEM;
+        return;
+    }
+
+    va_start(list, cmd);
+    while (1) {
+        char *arg = va_arg(list, char *);
+        if (!arg)
+            break;
+        arg = strdup(arg);
+        if (!arg) {
+            cmd->has_error = ENOMEM;
+            va_end(list);
+            return;
+        }
+        cmd->args[cmd->nargs++] = arg;
+    }
+    va_end(list);
+}
+
+/*
+ * Set the working directory of a non-daemon child process, rather
+ * than the parent's working directory.  Daemons automatically get /
+ * without using this call.
+ */
+void
+virCommandSetWorkingDirectory(virCommandPtr cmd, const char *pwd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (cmd->pwd) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot set directory twice");
+    } else {
+        cmd->pwd = strdup(pwd);
+        if (!cmd->pwd)
+            cmd->has_error = ENOMEM;
+    }
+}
+
+
+/*
+ * Feed the child's stdin from a string buffer
+ */
+void
+virCommandSetInputBuffer(virCommandPtr cmd, const char *inbuf)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (cmd->infd != -1 || cmd->inbuf) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify input twice");
+        return;
+    }
+
+    cmd->inbuf = strdup(inbuf);
+    if (!cmd->inbuf)
+        cmd->has_error = ENOMEM;
+}
+
+
+/*
+ * Capture the child's stdout to a string buffer
+ */
+void
+virCommandSetOutputBuffer(virCommandPtr cmd, char **outbuf)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (cmd->outfd != -1) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify output twice");
+        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;
+
+    if (cmd->errfd != -1) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify stderr twice");
+        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;
+
+    if (infd < 0 || cmd->inbuf) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify input twice");
+        return;
+    }
+
+    cmd->infd = infd;
+}
+
+
+/*
+ * Attach a file descriptor to the child's stdout
+ */
+void
+virCommandSetOutputFD(virCommandPtr cmd, int *outfd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (!outfd || cmd->outfd != -1) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify output twice");
+        return;
+    }
+
+    cmd->outfdptr = outfd;
+}
+
+
+/*
+ * Attach a file descriptor to the child's stderr
+ */
+void
+virCommandSetErrorFD(virCommandPtr cmd, int *errfd)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    if (!errfd || cmd->errfd != -1) {
+        cmd->has_error = -1;
+        VIR_DEBUG0("cannot specify stderr twice");
+        return;
+    }
+
+    cmd->errfdptr = errfd;
+}
+
+
+/*
+ * Run HOOK(OPAQUE) in the child as the last thing before changing
+ * directories, dropping capabilities, and executing the new process.
+ * Force the child to fail if HOOK does not return zero.
+ */
+void
+virCommandSetPreExecHook(virCommandPtr cmd, virExecHook hook, void *opaque)
+{
+    if (!cmd || cmd->has_error)
+        return;
+
+    cmd->hook = hook;
+    cmd->opaque = opaque;
+}
+
+
+/*
+ * Call after adding all arguments and environment settings, but before
+ * Run/RunAsync, to immediately output the environment and arguments of
+ * cmd to logfd.  If virCommandRun cannot succeed (because of an
+ * out-of-memory condition while building cmd), nothing will be logged.
+ */
+void
+virCommandWriteArgLog(virCommandPtr cmd, int logfd)
+{
+    int ioError = 0;
+    size_t i;
+
+    /* Any errors will be reported later by virCommandRun, which means
+     * no command will be run, so there is nothing to log. */
+    if (!cmd || cmd->has_error)
+        return;
+
+    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, i == cmd->nargs - 1 ? "\n" : " ", 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));
+    }
+}
+
+
+/*
+ * Call after adding all arguments and environment settings, but before
+ * Run/RunAsync, to return a string representation of the environment and
+ * arguments of cmd.  If virCommandRun cannot succeed (because of an
+ * out-of-memory condition while building cmd), NULL will be returned.
+ * Caller is responsible for freeing the resulting string.
+ */
+char *
+virCommandToString(virCommandPtr cmd)
+{
+    size_t i;
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+    /* Cannot assume virCommandRun will be called; so report the error
+     * now.  If virCommandRun is called, it will report the same error. */
+    if (!cmd || cmd->has_error == -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("invalid use of command API"));
+        return NULL;
+    }
+    if (cmd->has_error == ENOMEM) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    for (i = 0; i < cmd->nenv; i++) {
+        virBufferAdd(&buf, cmd->env[i], strlen(cmd->env[i]));
+        virBufferAddChar(&buf, ' ');
+    }
+    virBufferAdd(&buf, cmd->args[0], strlen(cmd->args[0]));
+    for (i = 1; i < cmd->nargs; i++) {
+        virBufferAddChar(&buf, ' ');
+        virBufferAdd(&buf, cmd->args[i], strlen(cmd->args[i]));
+    }
+
+    if (virBufferError(&buf)) {
+        virBufferFreeAndReset(&buf);
+        virReportOOMError();
+        return NULL;
+    }
+
+    return virBufferContentAndReset(&buf);
+}
+
+
+/*
+ * Manage input and output to the child process.
+ */
+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, 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;
+                    }
+                    memcpy(*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) {
+                        int tmpfd = infd;
+                        if (VIR_CLOSE(infd) < 0)
+                            VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+                    }
+                }
+            }
+
+        }
+    }
+
+    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 == -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("invalid use of command API"));
+        return -1;
+    }
+    if (cmd->has_error == ENOMEM) {
+        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) {
+            int tmpfd = infd[0];
+            if (VIR_CLOSE(infd[0]) < 0)
+                VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+            tmpfd = infd[1];
+            if (VIR_CLOSE(infd[1]) < 0)
+                VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+        }
+        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) {
+        int tmpfd = infd[0];
+        if (VIR_CLOSE(infd[0]) < 0)
+            VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+        tmpfd = infd[1];
+        if (VIR_CLOSE(infd[1]) < 0)
+            VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+    }
+    if (cmd->outbuf == &outbuf) {
+        int tmpfd = cmd->outfd;
+        if (VIR_CLOSE(cmd->outfd) < 0)
+            VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+        cmd->outfdptr = NULL;
+        cmd->outbuf = NULL;
+    }
+    if (cmd->errbuf == &errbuf) {
+        int tmpfd = cmd->errfd;
+        if (VIR_CLOSE(cmd->errfd) < 0)
+            VIR_DEBUG("ignoring failed close on fd %d", tmpfd);
+        cmd->errfdptr = NULL;
+        cmd->errbuf = NULL;
+    }
+
+    return ret;
+}
+
+
+/*
+ * Perform all virCommand-specific actions, along with the user hook.
+ */
+static int
+virCommandHook(void *data)
+{
+    virCommandPtr cmd = data;
+    int res = 0;
+
+    if (cmd->hook)
+        res = cmd->hook(cmd->opaque);
+    if (res == 0 && cmd->pwd) {
+        VIR_DEBUG("Running child in %s", cmd->pwd);
+        res = chdir(cmd->pwd);
+    }
+    return res;
+}
+
+
+/*
+ * 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;
+    int i;
+
+    if (!cmd || cmd->has_error == -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("invalid use of command API"));
+        return -1;
+    }
+    if (cmd->has_error == ENOMEM) {
+        virReportOOMError();
+        return -1;
+    }
+
+    if (cmd->pid != -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR,
+                        _("command is already running as pid %d"),
+                        cmd->pid);
+        return -1;
+    }
+
+    if (cmd->pwd && (cmd->flags & VIR_EXEC_DAEMON)) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR,
+                        _("daemonized command cannot set working directory %s"),
+                        cmd->pwd);
+        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,
+                          virCommandHook,
+                          cmd,
+                          cmd->pidfile);
+
+    VIR_DEBUG("Command result %d, with PID %d",
+              ret, (int)cmd->pid);
+
+    for (i = STDERR_FILENO + 1; i < FD_SETSIZE; i++) {
+        if (FD_ISSET(i, &cmd->transfer)) {
+            int tmpfd = i;
+            VIR_FORCE_CLOSE(tmpfd);
+            FD_CLR(i, &cmd->transfer);
+        }
+    }
+
+    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 == -1) {
+        virCommandError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("invalid use of command API"));
+        return -1;
+    }
+    if (cmd->has_error == ENOMEM) {
+        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;
+}
+
+
+/*
+ * Release all resources
+ */
+void
+virCommandFree(virCommandPtr cmd)
+{
+    int i;
+    if (!cmd)
+        return;
+
+    for (i = STDERR_FILENO + 1; i < FD_SETSIZE; i++) {
+        if (FD_ISSET(i, &cmd->transfer)) {
+            int tmpfd = i;
+            VIR_FORCE_CLOSE(tmpfd);
+        }
+    }
+
+    VIR_FORCE_CLOSE(cmd->outfd);
+    VIR_FORCE_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->pwd);
+
+    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..9b04e68
--- /dev/null
+++ b/src/util/command.h
@@ -0,0 +1,260 @@
+/*
+ * 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) ATTRIBUTE_NONNULL(1);
+
+/*
+ * Create a new command with a NULL terminated
+ * set of args, taking binary from argv[0]
+ */
+virCommandPtr virCommandNewArgs(const char *const*args) ATTRIBUTE_NONNULL(1);
+
+/*
+ * Create a new command with a NULL terminated
+ * list of args, starting with the binary to run
+ */
+virCommandPtr virCommandNewArgList(const char *binary, ...)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL;
+
+/* All error report from these setup APIs is
+ * delayed until the Run/RunAsync methods
+ */
+
+/*
+ * Preserve the specified file descriptor
+ * in the child, instead of closing it.
+ * The parent is still responsible for managing fd.
+ */
+void virCommandPreserveFD(virCommandPtr cmd,
+                          int fd);
+
+/*
+ * Transfer the specified file descriptor
+ * to the child, instead of closing it.
+ * Close the fd in the parent during Run/RunAsync/Free.
+ */
+void virCommandTransferFD(virCommandPtr cmd,
+                          int fd);
+
+/*
+ * Save the child PID in a pidfile
+ */
+void virCommandSetPidFile(virCommandPtr cmd,
+                          const char *pidfile) ATTRIBUTE_NONNULL(2);
+
+/*
+ * Remove all capabilities from the child
+ */
+void virCommandClearCaps(virCommandPtr cmd);
+
+# if 0
+/*
+ * Re-allow a specific capability
+ */
+void virCommandAllowCap(virCommandPtr cmd,
+                        int capability);
+# endif
+
+/*
+ * Daemonize the child process
+ */
+void virCommandDaemonize(virCommandPtr cmd);
+
+/*
+ * Set FDs created by virCommandSetOutputFD and virCommandSetErrorFD
+ * as non-blocking in the parent.
+ */
+void virCommandNonblockingFDs(virCommandPtr cmd);
+
+/*
+ * Add an environment variable to the child
+ * using separate name & value strings
+ */
+void virCommandAddEnvPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *value) ATTRIBUTE_NONNULL(2);
+
+/*
+ * Add an environemnt variable to the child
+ * using a preformated env string FOO=BAR
+ */
+void virCommandAddEnvString(virCommandPtr cmd,
+                            const char *str) ATTRIBUTE_NONNULL(2);
+/*
+ * Pass an environment variable to the child
+ * using current process' value
+ */
+void virCommandAddEnvPass(virCommandPtr cmd,
+                          const char *name) ATTRIBUTE_NONNULL(2);
+/*
+ * 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) ATTRIBUTE_NONNULL(2);
+
+/*
+ * Add a command line argument created by a printf-style format
+ */
+void virCommandAddArgFormat(virCommandPtr cmd,
+                            const char *format, ...)
+    ATTRIBUTE_NONNULL(2) ATTRIBUTE_FMT_PRINTF(2, 3);
+
+/*
+ * Add a command line argument to the child
+ */
+void virCommandAddArgPair(virCommandPtr cmd,
+                          const char *name,
+                          const char *val)
+    ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+/*
+ * Add a NULL terminated array of args
+ */
+void virCommandAddArgSet(virCommandPtr cmd,
+                         const char *const*vals) ATTRIBUTE_NONNULL(2);
+/*
+ * Add a NULL terminated list of args
+ */
+void virCommandAddArgList(virCommandPtr cmd,
+                          ... /* const char *arg, ..., NULL */)
+    ATTRIBUTE_SENTINEL;
+
+/*
+ * Set the working directory of a non-daemon child process, rather
+ * than the parent's working directory.  Daemons automatically get /
+ * without using this call.
+ */
+void virCommandSetWorkingDirectory(virCommandPtr cmd,
+                                   const char *pwd) ATTRIBUTE_NONNULL(2);
+
+/*
+ * Feed the child's stdin from a string buffer.
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetInputBuffer(virCommandPtr cmd,
+                              const char *inbuf) ATTRIBUTE_NONNULL(2);
+/*
+ * Capture the child's stdout to a string buffer
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetOutputBuffer(virCommandPtr cmd,
+                               char **outbuf) ATTRIBUTE_NONNULL(2);
+/*
+ * Capture the child's stderr to a string buffer
+ *
+ * NB: Only works with virCommandRun()
+ */
+void virCommandSetErrorBuffer(virCommandPtr cmd,
+                              char **errbuf) ATTRIBUTE_NONNULL(2);
+
+/*
+ * 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) ATTRIBUTE_NONNULL(2);
+/*
+ * Set a file descriptor as the child's stderr
+ */
+void virCommandSetErrorFD(virCommandPtr cmd,
+                          int *errfd) ATTRIBUTE_NONNULL(2);
+
+/*
+ * A hook function to run between fork + exec
+ */
+void virCommandSetPreExecHook(virCommandPtr cmd,
+                              virExecHook hook,
+                              void *opaque) ATTRIBUTE_NONNULL(2);
+
+/*
+ * Call after adding all arguments and environment settings, but before
+ * Run/RunAsync, to immediately output the environment and arguments of
+ * cmd to logfd.  If virCommandRun cannot succeed (because of an
+ * out-of-memory condition while building cmd), nothing will be logged.
+ */
+void virCommandWriteArgLog(virCommandPtr cmd,
+                           int logfd);
+
+/*
+ * Call after adding all arguments and environment settings, but before
+ * Run/RunAsync, to return a string representation of the environment and
+ * arguments of cmd.  If virCommandRun cannot succeed (because of an
+ * out-of-memory condition while building cmd), NULL will be returned.
+ * Caller is responsible for freeing the resulting string.
+ */
+char *virCommandToString(virCommandPtr cmd) ATTRIBUTE_RETURN_CHECK;
+
+/*
+ * 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;
+
+/*
+ * 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;
+
+/*
+ * Release all resources
+ */
+void virCommandFree(virCommandPtr cmd);
+
+
+#endif /* __VIR_COMMAND_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 8ad3e98..e3906f0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,6 +1,10 @@
 *.exe
 .deps
 .libs
+commandhelper
+commandhelper.log
+commandhelper.pid
+commandtest
 conftest
 esxutilstest
 eventtest
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 77b6fb9..ff3f135 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,20 @@ 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)
+
+statstest_SOURCES = \
+	statstest.c testutils.h testutils.c
+statstest_LDADD = $(LDADDS)
+
 if WITH_SECDRIVER_SELINUX
 seclabeltest_SOURCES = \
 	seclabeltest.c
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..e1d6092
--- /dev/null
+++ b/tests/commanddata/test11.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/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/test14.log b/tests/commanddata/test14.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test14.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/test15.log b/tests/commanddata/test15.log
new file mode 100644
index 0000000..f439a85
--- /dev/null
+++ b/tests/commanddata/test15.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:.../commanddata
diff --git a/tests/commanddata/test16.log b/tests/commanddata/test16.log
new file mode 100644
index 0000000..2433a4d
--- /dev/null
+++ b/tests/commanddata/test16.log
@@ -0,0 +1 @@
+A=B /bin/true C
diff --git a/tests/commanddata/test2.log b/tests/commanddata/test2.log
new file mode 100644
index 0000000..1b59206
--- /dev/null
+++ b/tests/commanddata/test2.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/test3.log b/tests/commanddata/test3.log
new file mode 100644
index 0000000..6bd7974
--- /dev/null
+++ b/tests/commanddata/test3.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/test4.log b/tests/commanddata/test4.log
new file mode 100644
index 0000000..1876685
--- /dev/null
+++ b/tests/commanddata/test4.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/test5.log b/tests/commanddata/test5.log
new file mode 100644
index 0000000..f745c3f
--- /dev/null
+++ b/tests/commanddata/test5.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/test6.log b/tests/commanddata/test6.log
new file mode 100644
index 0000000..5394428
--- /dev/null
+++ b/tests/commanddata/test6.log
@@ -0,0 +1,6 @@
+ENV:DISPLAY=:0.0
+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..cdfe445
--- /dev/null
+++ b/tests/commanddata/test7.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/test8.log b/tests/commanddata/test8.log
new file mode 100644
index 0000000..87874fd
--- /dev/null
+++ b/tests/commanddata/test8.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/test9.log b/tests/commanddata/test9.log
new file mode 100644
index 0000000..2607530
--- /dev/null
+++ b/tests/commanddata/test9.log
@@ -0,0 +1,18 @@
+ARG:-version
+ARG:-log=bar.log
+ARG:arg1
+ARG:arg2
+ARG:arg3
+ARG:arg4
+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..2ee9153
--- /dev/null
+++ b/tests/commandhelper.c
@@ -0,0 +1,137 @@
+/*
+ * commandhelper.c: Auxiliary program for commandtest
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "internal.h"
+#include "util.h"
+#include "memory.h"
+#include "files.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];
+    getcwd(cwd, sizeof(cwd));
+    if (strlen(cwd) > strlen("/commanddata") &&
+        STREQ(cwd + strlen(cwd) - strlen("/commanddata"), "/commanddata"))
+        strcpy(cwd, ".../commanddata");
+    fprintf(log, "CWD:%s\n", cwd);
+
+    VIR_FORCE_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..46d6ae5
--- /dev/null
+++ b/tests/commandtest.c
@@ -0,0 +1,630 @@
+/*
+ * commandtest.c: Test the libCommand API
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "testutils.h"
+#include "internal.h"
+#include "nodeinfo.h"
+#include "util.h"
+#include "memory.h"
+#include "command.h"
+#include "files.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
+ * No slot for return status must log error.
+ */
+static int test0(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd;
+    char *log;
+    int ret = -1;
+
+    free(virtTestLogContentAndReset());
+    cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist");
+    if (virCommandRun(cmd, NULL) == 0)
+        goto cleanup;
+    if ((log = virtTestLogContentAndReset()) == NULL)
+        goto cleanup;
+    if (strstr(log, ": error :") == NULL)
+        goto cleanup;
+    virResetLastError();
+    ret = 0;
+
+cleanup:
+    virCommandFree(cmd);
+    return ret;
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ * Capturing return status must not log error.
+ */
+static int test1(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd;
+    int ret = -1;
+    int status;
+
+    cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist");
+    if (virCommandRun(cmd, &status) < 0)
+        goto cleanup;
+    if (status == 0)
+        goto cleanup;
+    ret = 0;
+
+cleanup:
+    virCommandFree(cmd);
+    return ret;
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test2(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("test2");
+}
+
+/*
+ * Run program, no args, inherit all ENV, keep CWD.
+ * stdin/out/err + two extra FD open
+ */
+static int test3(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);
+
+    virCommandPreserveFD(cmd, newfd1);
+    virCommandTransferFD(cmd, newfd3);
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    if (fcntl(newfd1, F_GETFL) < 0 ||
+        fcntl(newfd2, F_GETFL) < 0 ||
+        fcntl(newfd3, F_GETFL) >= 0) {
+        puts("fds in wrong state");
+        return -1;
+    }
+
+    virCommandFree(cmd);
+    VIR_FORCE_CLOSE(newfd1);
+    VIR_FORCE_CLOSE(newfd2);
+
+    return checkoutput("test3");
+}
+
+
+/*
+ * Run program, no args, inherit all ENV, CWD is /
+ * Only stdin/out/err open.
+ * Daemonized
+ */
+static int test4(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("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");
+
+    virCommandAddEnvPassCommon(cmd);
+
+    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");
+
+    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");
+
+    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("test7");
+}
+
+/*
+ * Run program, no args, inherit filtered ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test8(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("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[] = { "arg1", "arg2", NULL };
+
+    virCommandAddArg(cmd, "-version");
+    virCommandAddArgPair(cmd, "-log", "bar.log");
+    virCommandAddArgSet(cmd, args);
+    virCommandAddArgList(cmd, "arg3", "arg4", NULL);
+
+    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) {
+    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("test10");
+}
+
+/*
+ * Run program, some args, inherit all ENV, keep CWD.
+ * Only stdin/out/err open
+ */
+static int test11(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("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");
+
+    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("test12");
+}
+
+/*
+ * 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";
+    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("test13") < 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 test14(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("test14") < 0)
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(outactual);
+    VIR_FREE(erractual);
+    return ret;
+}
+
+
+/*
+ * Run program, no args, inherit all ENV, change CWD.
+ * Only stdin/out/err open
+ */
+static int test15(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
+
+    virCommandSetWorkingDirectory(cmd, abs_builddir "/commanddata");
+
+    if (virCommandRun(cmd, NULL) < 0) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot run child %s\n", err->message);
+        return -1;
+    }
+
+    virCommandFree(cmd);
+
+    return checkoutput("test15");
+}
+
+/*
+ * Don't run program; rather, log what would be run.
+ */
+static int test16(const void *unused ATTRIBUTE_UNUSED) {
+    virCommandPtr cmd = virCommandNew("/bin/true");
+    char *outactual = NULL;
+    const char *outexpect = "A=B /bin/true C";
+    int ret = -1;
+    int fd = -1;
+
+    virCommandAddEnvPair(cmd, "A", "B");
+    virCommandAddArg(cmd, "C");
+
+    if ((outactual = virCommandToString(cmd)) == NULL) {
+        virErrorPtr err = virGetLastError();
+        printf("Cannot convert to string: %s\n", err->message);
+        return -1;
+    }
+    if ((fd = open(abs_builddir "/commandhelper.log",
+                   O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) {
+        printf("Cannot open log file: %s\n", strerror (errno));
+        goto cleanup;
+    }
+    virCommandWriteArgLog(cmd, fd);
+    if (VIR_CLOSE(fd) < 0) {
+        printf("Cannot close log file: %s\n", strerror (errno));
+        goto cleanup;
+    }
+
+    virCommandFree(cmd);
+
+    if (checkoutput("test16") < 0)
+        goto cleanup;
+
+    if (!STREQ(outactual, outexpect)) {
+        virtTestDifference(stderr, outactual, outexpect);
+        goto cleanup;
+    }
+    ret = 0;
+
+cleanup:
+    VIR_FORCE_CLOSE(fd);
+    VIR_FREE(outactual);
+    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);
+    DO_TEST(test14);
+    DO_TEST(test15);
+    DO_TEST(test16);
+
+    return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+#endif /* __linux__ */
+
+VIRT_TEST_MAIN(mymain)
-- 
1.7.3.2




More information about the libvir-list mailing list