From: Daniel P. Berrange <berrange(a)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