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