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.
This commit is contained in:
Daniel P. Berrange 2010-05-25 12:14:06 +01:00 committed by Eric Blake
parent fce3baee26
commit f16ad06fb2
26 changed files with 2409 additions and 2 deletions

View File

@ -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$

4
cfg.mk
View File

@ -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

View File

@ -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

View File

@ -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 \

View File

@ -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;

1145
src/util/command.c Normal file

File diff suppressed because it is too large Load Diff

260
src/util/command.h Normal file
View File

@ -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__ */

4
tests/.gitignore vendored
View File

@ -1,6 +1,10 @@
*.exe
.deps
.libs
commandhelper
commandhelper.log
commandhelper.pid
commandtest
conftest
esxutilstest
eventtest

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
A=B /bin/true C

View File

@ -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

View File

@ -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

View File

@ -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:/

View File

@ -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

View File

@ -0,0 +1,6 @@
ENV:DISPLAY=:0.0
FD:0
FD:1
FD:2
DAEMON:no
CWD:/tmp

View File

@ -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

View File

@ -0,0 +1,7 @@
ENV:LANG=C
ENV:USER=test
FD:0
FD:1
FD:2
DAEMON:no
CWD:/tmp

View File

@ -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

137
tests/commandhelper.c Normal file
View File

@ -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;
}

642
tests/commandtest.c Normal file
View File

@ -0,0 +1,642 @@
/*
* 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)