提交 f16ad06f 编写于 作者: D Daniel P. Berrange 提交者: Eric Blake

Introduce new APIs for spawning processes

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.
(msg_gen_function): Add virCommandError.
* po/POTFILES.in: New translation.
* .x-sc_avoid_write: Add exemption.
* tests/.gitignore: Ignore new built file.
上级 fce3baee
^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$
......
......@@ -81,6 +81,7 @@ useless_free_options = \
--name=virCapabilitiesFreeHostNUMACell \
--name=virCapabilitiesFreeMachines \
--name=virCgroupFree \
--name=virCommandFree \
--name=virConfFreeList \
--name=virConfFreeValue \
--name=virDomainChrDefFree \
......@@ -368,9 +369,9 @@ msg_gen_function += umlReportError
msg_gen_function += vah_error
msg_gen_function += vah_warning
msg_gen_function += vboxError
msg_gen_function += virCommandError
msg_gen_function += virConfError
msg_gen_function += virDomainReportError
msg_gen_function += virSecurityReportError
msg_gen_function += virHashError
msg_gen_function += virLibConnError
msg_gen_function += virLibDomainError
......@@ -379,6 +380,7 @@ msg_gen_function += virNodeDeviceReportError
msg_gen_function += virRaiseError
msg_gen_function += virReportErrorHelper
msg_gen_function += virReportSystemError
msg_gen_function += virSecurityReportError
msg_gen_function += virSexprError
msg_gen_function += virStorageReportError
msg_gen_function += virXMLError
......
......@@ -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
......
......@@ -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 \
......
......@@ -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;
......
/*
* 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 args[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->outfdptr) {
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->errfdptr) {
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 (cmd->infd != -1 || cmd->inbuf) {
cmd->has_error = -1;
VIR_DEBUG0("cannot specify input twice");
return;
}
if (infd < 0) {
cmd->has_error = -1;
VIR_DEBUG0("cannot specify invalid input fd");
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 (cmd->outfdptr) {
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 (cmd->errfdptr) {
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;
if (cmd->hook) {
cmd->has_error = -1;
VIR_DEBUG0("cannot specify hook twice");
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"));
cmd->has_error = -1;
return -1;
}
cmd->infd = infd[0];
cmd->inpipe = infd[1];
}
/* If caller hasn't requested capture of stdout/err, then capture
* it ourselves so we can log it.
*/
if (!cmd->outfdptr) {
cmd->outfdptr = &cmd->outfd;
cmd->outbuf = &outbuf;
}
if (!cmd->errfdptr) {
cmd->errfdptr = &cmd->errfd;
cmd->errbuf = &errbuf;
}
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);
}
cmd->has_error = -1;
return -1;
}
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 = virCommandToString(cmd);
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,
_("Child 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);
}
/*
* 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__ */
*.exe
.deps
.libs
commandhelper
commandhelper.log
commandhelper.pid
commandtest
conftest
esxutilstest
eventtest
......
......@@ -73,7 +73,8 @@ EXTRA_DIST = \
cputestdata
check_PROGRAMS = virshtest conftest sockettest \
nodeinfotest qparamtest virbuftest
nodeinfotest qparamtest virbuftest \
commandtest commandhelper
if WITH_XEN
check_PROGRAMS += xml2sexprtest sexpr2xmltest \
......@@ -157,6 +158,7 @@ TESTS = virshtest \
qparamtest \
virbuftest \
sockettest \
commandtest \
$(test_scripts)
if WITH_XEN
......@@ -349,6 +351,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
......
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
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
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
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
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
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
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
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
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:/
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
ENV:DISPLAY=:0.0
FD:0
FD:1
FD:2
DAEMON:no
CWD:/tmp
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
ENV:LANG=C
ENV:USER=test
FD:0
FD:1
FD:2
DAEMON:no
CWD:/tmp
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
/*
* 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;
}
/*
* 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"
#ifdef WIN32
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 (twice), 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");
int ret;
if (virCommandRun(cmd, NULL) < 0) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
return -1;
}
if ((ret = checkoutput("test2")) != 0) {
virCommandFree(cmd);
return ret;
}
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 /* !WIN32 */
VIRT_TEST_MAIN(mymain)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册