libvirt/tests/commandtest.c

1316 lines
32 KiB
C
Raw Normal View History

/*
* commandtest.c: Test the libCommand API
*
* Copyright (C) 2010-2014 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, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "testutils.h"
#include "internal.h"
2012-12-13 02:06:53 +08:00
#include "viralloc.h"
#include "vircommand.h"
#include "virfile.h"
#include "virpidfile.h"
#include "virerror.h"
#include "virthread.h"
#include "virstring.h"
#include "virprocess.h"
#define VIR_FROM_THIS VIR_FROM_NONE
typedef struct _virCommandTestData virCommandTestData;
typedef virCommandTestData *virCommandTestDataPtr;
struct _virCommandTestData {
virMutex lock;
virThread thread;
bool quit;
bool running;
};
#ifdef WIN32
int
main(void)
{
return EXIT_AM_SKIP;
}
#else
static int checkoutput(const char *testname,
char *prefix)
{
int ret = -1;
char *expectname = NULL;
char *expectlog = NULL;
char *actualname = NULL;
char *actuallog = NULL;
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 (prefix) {
char *tmp = NULL;
if (virAsprintf(&tmp, "%s%s", prefix, expectlog) < 0)
goto cleanup;
VIR_FREE(expectlog);
expectlog = tmp;
}
if (STRNEQ(expectlog, actuallog)) {
virTestDifference(stderr, expectlog, actuallog);
goto cleanup;
}
ret = 0;
cleanup:
if (actualname)
unlink(actualname);
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;
int ret = -1;
cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist");
if (virCommandRun(cmd, NULL) == 0)
goto cleanup;
2010-12-06 19:58:56 +08:00
if (virGetLastErrorCode() == VIR_ERR_OK)
goto cleanup;
2010-12-06 19:58:56 +08:00
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");
util: make it easier to grab only regular command exit Auditing all callers of virCommandRun and virCommandWait that passed a non-NULL pointer for exit status turned up some interesting observations. Many callers were merely passing a pointer to avoid the overall command dying, but without caring what the exit status was - but these callers would be better off treating a child death by signal as an abnormal exit. Other callers were actually acting on the status, but not all of them remembered to filter by WIFEXITED and convert with WEXITSTATUS; depending on the platform, this can result in a status being reported as 256 times too big. And among those that correctly parse the output, it gets rather verbose. Finally, there were the callers that explicitly checked that the status was 0, and gave their own message, but with fewer details than what virCommand gives for free. So the best idea is to move the complexity out of callers and into virCommand - by default, we return the actual exit status already cleaned through WEXITSTATUS and treat signals as a failed command; but the few callers that care can ask for raw status and act on it themselves. * src/util/vircommand.h (virCommandRawStatus): New prototype. * src/libvirt_private.syms (util/command.h): Export it. * docs/internals/command.html.in: Document it. * src/util/vircommand.c (virCommandRawStatus): New function. (virCommandWait): Adjust semantics. * tests/commandtest.c (test1): Test it. * daemon/remote.c (remoteDispatchAuthPolkit): Adjust callers. * src/access/viraccessdriverpolkit.c (virAccessDriverPolkitCheck): Likewise. * src/fdstream.c (virFDStreamCloseInt): Likewise. * src/lxc/lxc_process.c (virLXCProcessStart): Likewise. * src/qemu/qemu_command.c (qemuCreateInBridgePortWithHelper): Likewise. * src/xen/xen_driver.c (xenUnifiedXendProbe): Simplify. * tests/reconnect.c (mymain): Likewise. * tests/statstest.c (mymain): Likewise. * src/bhyve/bhyve_process.c (virBhyveProcessStart) (virBhyveProcessStop): Don't overwrite virCommand error. * src/libvirt.c (virConnectAuthGainPolkit): Likewise. * src/openvz/openvz_driver.c (openvzDomainGetBarrierLimit) (openvzDomainSetBarrierLimit): Likewise. * src/util/virebtables.c (virEbTablesOnceInit): Likewise. * src/util/viriptables.c (virIpTablesOnceInit): Likewise. * src/util/virnetdevveth.c (virNetDevVethCreate): Fix debug message. * src/qemu/qemu_capabilities.c (virQEMUCapsInitQMP): Add comment. * src/storage/storage_backend_iscsi.c (virStorageBackendISCSINodeUpdate): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2014-02-20 08:32:19 +08:00
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
if (status != EXIT_ENOENT)
goto cleanup;
virCommandRawStatus(cmd);
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_ENOENT)
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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
if ((ret = checkoutput("test2", NULL)) != 0) {
virCommandFree(cmd);
return ret;
}
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test2", NULL);
}
/*
* 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);
int ret = -1;
virCommandPassFD(cmd, newfd1, 0);
virCommandPassFD(cmd, newfd3,
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (fcntl(newfd1, F_GETFL) < 0 ||
fcntl(newfd2, F_GETFL) < 0 ||
fcntl(newfd3, F_GETFL) >= 0) {
puts("fds in wrong state");
goto cleanup;
}
ret = checkoutput("test3", NULL);
cleanup:
virCommandFree(cmd);
/* coverity[double_close] */
VIR_FORCE_CLOSE(newfd1);
VIR_FORCE_CLOSE(newfd2);
return ret;
}
/*
* 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");
char *pidfile = virPidFileBuildPath(abs_builddir, "commandhelper");
pid_t pid;
int ret = -1;
if (!pidfile)
goto cleanup;
virCommandSetPidFile(cmd, pidfile);
virCommandDaemonize(cmd);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (virPidFileRead(abs_builddir, "commandhelper", &pid) < 0) {
printf("cannot read pidfile\n");
goto cleanup;
}
while (kill(pid, 0) != -1)
usleep(100*1000);
ret = checkoutput("test4", NULL);
cleanup:
virCommandFree(cmd);
if (pidfile)
unlink(pidfile);
VIR_FREE(pidfile);
return ret;
}
/*
* 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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test5", NULL);
}
/*
* 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");
virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL);
virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test6", NULL);
}
/*
* 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);
virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL);
virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test7", NULL);
}
/*
* 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, "USER=bogus");
virCommandAddEnvString(cmd, "LANG=C");
virCommandAddEnvPair(cmd, "USER", "also bogus");
virCommandAddEnvPair(cmd, "USER", "test");
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test8", NULL);
}
/*
* 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 };
virBuffer buf = VIR_BUFFER_INITIALIZER;
virCommandAddArg(cmd, "-version");
virCommandAddArgPair(cmd, "-log", "bar.log");
virCommandAddArgSet(cmd, args);
virCommandAddArgBuffer(cmd, &buf);
virBufferAddLit(&buf, "arg4");
virCommandAddArgBuffer(cmd, &buf);
virCommandAddArgList(cmd, "arg5", "arg6", NULL);
if (virBufferUse(&buf)) {
printf("Buffer not transferred\n");
virBufferFreeAndReset(&buf);
virCommandFree(cmd);
return -1;
}
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test9", NULL);
}
/*
* 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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test10", NULL);
}
/*
* 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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test11", NULL);
}
/*
* 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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
virCommandFree(cmd);
return -1;
}
virCommandFree(cmd);
return checkoutput("test12", NULL);
}
/*
* 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) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (!outactual)
goto cleanup;
virCommandFree(cmd);
cmd = NULL;
if (STRNEQ(outactual, outexpect)) {
virTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
ret = checkoutput("test13", NULL);
cleanup:
virCommandFree(cmd);
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";
char *jointactual = NULL;
const char *jointexpect = "BEGIN STDOUT\n"
"BEGIN STDERR\n"
"Hello World\n"
"Hello World\n"
"END STDOUT\n"
"END STDERR\n";
int ret = -1;
virCommandSetInputBuffer(cmd, "Hello World\n");
virCommandSetOutputBuffer(cmd, &outactual);
virCommandSetErrorBuffer(cmd, &erractual);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (!outactual || !erractual)
goto cleanup;
virCommandFree(cmd);
cmd = virCommandNew(abs_builddir "/commandhelper");
virCommandSetInputBuffer(cmd, "Hello World\n");
virCommandSetOutputBuffer(cmd, &jointactual);
virCommandSetErrorBuffer(cmd, &jointactual);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (!jointactual)
goto cleanup;
if (STRNEQ(outactual, outexpect)) {
virTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
if (STRNEQ(erractual, errexpect)) {
virTestDifference(stderr, errexpect, erractual);
goto cleanup;
}
if (STRNEQ(jointactual, jointexpect)) {
virTestDifference(stderr, jointexpect, jointactual);
goto cleanup;
}
ret = checkoutput("test14", NULL);
cleanup:
virCommandFree(cmd);
VIR_FREE(outactual);
VIR_FREE(erractual);
VIR_FREE(jointactual);
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");
2010-12-06 20:03:26 +08:00
char *cwd = NULL;
int ret = -1;
2010-12-06 20:03:26 +08:00
if (virAsprintf(&cwd, "%s/commanddata", abs_srcdir) < 0)
goto cleanup;
virCommandSetWorkingDirectory(cmd, cwd);
virCommandSetUmask(cmd, 002);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
2010-12-06 20:03:26 +08:00
goto cleanup;
}
ret = checkoutput("test15", NULL);
2010-12-06 20:03:26 +08:00
cleanup:
2010-12-06 20:03:26 +08:00
VIR_FREE(cwd);
virCommandFree(cmd);
2010-12-06 20:03:26 +08:00
return ret;
}
/*
* Don't run program; rather, log what would be run.
*/
static int test16(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNew("true");
char *outactual = NULL;
const char *outexpect = "A=B C='D E' true F 'G H'";
int ret = -1;
int fd = -1;
virCommandAddEnvPair(cmd, "A", "B");
virCommandAddEnvPair(cmd, "C", "D E");
virCommandAddArg(cmd, "F");
virCommandAddArg(cmd, "G H");
if ((outactual = virCommandToString(cmd)) == NULL) {
printf("Cannot convert to string: %s\n", virGetLastErrorMessage());
goto cleanup;
}
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;
}
if (STRNEQ(outactual, outexpect)) {
virTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
ret = checkoutput("test16", NULL);
cleanup:
virCommandFree(cmd);
VIR_FORCE_CLOSE(fd);
VIR_FREE(outactual);
return ret;
}
/*
* Test string handling when no output is present.
*/
static int test17(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNew("true");
int ret = -1;
char *outbuf;
char *errbuf = NULL;
virCommandSetOutputBuffer(cmd, &outbuf);
if (outbuf != NULL) {
puts("buffer not sanitized at registration");
goto cleanup;
}
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
sa_assert(outbuf);
if (*outbuf) {
puts("output buffer is not an allocated empty string");
goto cleanup;
}
VIR_FREE(outbuf);
if (VIR_STRDUP(outbuf, "should not be leaked") < 0) {
puts("test framework failure");
goto cleanup;
}
virCommandSetErrorBuffer(cmd, &errbuf);
if (errbuf != NULL) {
puts("buffer not sanitized at registration");
goto cleanup;
}
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (*outbuf || *errbuf) {
puts("output buffers are not allocated empty strings");
goto cleanup;
}
ret = 0;
cleanup:
virCommandFree(cmd);
VIR_FREE(outbuf);
VIR_FREE(errbuf);
return ret;
}
/*
* Run long-running daemon, to ensure no hang.
*/
static int test18(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL);
char *pidfile = virPidFileBuildPath(abs_builddir, "commandhelper");
pid_t pid;
int ret = -1;
if (!pidfile)
goto cleanup;
virCommandSetPidFile(cmd, pidfile);
virCommandDaemonize(cmd);
alarm(5);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
alarm(0);
if (virPidFileRead(abs_builddir, "commandhelper", &pid) < 0) {
printf("cannot read pidfile\n");
goto cleanup;
}
virCommandFree(cmd);
cmd = NULL;
if (kill(pid, 0) != 0) {
printf("daemon should still be running\n");
goto cleanup;
}
while (kill(pid, SIGINT) != -1)
usleep(100*1000);
ret = 0;
cleanup:
virCommandFree(cmd);
if (pidfile)
unlink(pidfile);
VIR_FREE(pidfile);
return ret;
}
/*
* Asynchronously run long-running daemon, to ensure no hang.
*/
static int test19(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL);
pid_t pid;
int ret = -1;
alarm(5);
if (virCommandRunAsync(cmd, &pid) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (kill(pid, 0) != 0) {
printf("Child should still be running");
goto cleanup;
}
virCommandAbort(cmd);
if (kill(pid, 0) == 0) {
printf("Child should be aborted");
goto cleanup;
}
alarm(0);
ret = 0;
cleanup:
virCommandFree(cmd);
return ret;
}
/*
* Run program, no args, inherit all ENV, keep CWD.
* Ignore huge stdin data, to provoke SIGPIPE or EPIPE in parent.
*/
static int test20(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNewArgList(abs_builddir "/commandhelper",
"--close-stdin", NULL);
char *buf;
int ret = -1;
struct sigaction sig_action;
sig_action.sa_handler = SIG_IGN;
sig_action.sa_flags = 0;
sigemptyset(&sig_action.sa_mask);
sigaction(SIGPIPE, &sig_action, NULL);
if (virAsprintf(&buf, "1\n%100000d\n", 2) < 0)
goto cleanup;
virCommandSetInputBuffer(cmd, buf);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
ret = checkoutput("test20", NULL);
cleanup:
virCommandFree(cmd);
VIR_FREE(buf);
return ret;
}
static 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
};
static int test21(const void *unused ATTRIBUTE_UNUSED)
{
virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
int ret = -1;
const char *wrbuf = "Hello world\n";
char *outbuf = NULL, *errbuf = NULL;
const char *outbufExpected = "BEGIN STDOUT\n"
"Hello world\n"
"END STDOUT\n";
const char *errbufExpected = "BEGIN STDERR\n"
"Hello world\n"
"END STDERR\n";
virCommandSetInputBuffer(cmd, wrbuf);
virCommandSetOutputBuffer(cmd, &outbuf);
virCommandSetErrorBuffer(cmd, &errbuf);
virCommandDoAsyncIO(cmd);
if (virCommandRunAsync(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (virCommandWait(cmd, NULL) < 0)
goto cleanup;
if (virTestGetVerbose())
printf("STDOUT:%s\nSTDERR:%s\n", NULLSTR(outbuf), NULLSTR(errbuf));
if (STRNEQ_NULLABLE(outbuf, outbufExpected)) {
virTestDifference(stderr, outbufExpected, outbuf);
goto cleanup;
}
if (STRNEQ_NULLABLE(errbuf, errbufExpected)) {
virTestDifference(stderr, errbufExpected, errbuf);
goto cleanup;
}
ret = checkoutput("test21", NULL);
cleanup:
VIR_FREE(outbuf);
VIR_FREE(errbuf);
virCommandFree(cmd);
return ret;
}
static int
test22(const void *unused ATTRIBUTE_UNUSED)
{
int ret = -1;
virCommandPtr cmd;
int status = -1;
cmd = virCommandNewArgList("/bin/sh", "-c", "exit 3", NULL);
util: make it easier to grab only regular command exit Auditing all callers of virCommandRun and virCommandWait that passed a non-NULL pointer for exit status turned up some interesting observations. Many callers were merely passing a pointer to avoid the overall command dying, but without caring what the exit status was - but these callers would be better off treating a child death by signal as an abnormal exit. Other callers were actually acting on the status, but not all of them remembered to filter by WIFEXITED and convert with WEXITSTATUS; depending on the platform, this can result in a status being reported as 256 times too big. And among those that correctly parse the output, it gets rather verbose. Finally, there were the callers that explicitly checked that the status was 0, and gave their own message, but with fewer details than what virCommand gives for free. So the best idea is to move the complexity out of callers and into virCommand - by default, we return the actual exit status already cleaned through WEXITSTATUS and treat signals as a failed command; but the few callers that care can ask for raw status and act on it themselves. * src/util/vircommand.h (virCommandRawStatus): New prototype. * src/libvirt_private.syms (util/command.h): Export it. * docs/internals/command.html.in: Document it. * src/util/vircommand.c (virCommandRawStatus): New function. (virCommandWait): Adjust semantics. * tests/commandtest.c (test1): Test it. * daemon/remote.c (remoteDispatchAuthPolkit): Adjust callers. * src/access/viraccessdriverpolkit.c (virAccessDriverPolkitCheck): Likewise. * src/fdstream.c (virFDStreamCloseInt): Likewise. * src/lxc/lxc_process.c (virLXCProcessStart): Likewise. * src/qemu/qemu_command.c (qemuCreateInBridgePortWithHelper): Likewise. * src/xen/xen_driver.c (xenUnifiedXendProbe): Simplify. * tests/reconnect.c (mymain): Likewise. * tests/statstest.c (mymain): Likewise. * src/bhyve/bhyve_process.c (virBhyveProcessStart) (virBhyveProcessStop): Don't overwrite virCommand error. * src/libvirt.c (virConnectAuthGainPolkit): Likewise. * src/openvz/openvz_driver.c (openvzDomainGetBarrierLimit) (openvzDomainSetBarrierLimit): Likewise. * src/util/virebtables.c (virEbTablesOnceInit): Likewise. * src/util/viriptables.c (virIpTablesOnceInit): Likewise. * src/util/virnetdevveth.c (virNetDevVethCreate): Fix debug message. * src/qemu/qemu_capabilities.c (virQEMUCapsInitQMP): Add comment. * src/storage/storage_backend_iscsi.c (virStorageBackendISCSINodeUpdate): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2014-02-20 08:32:19 +08:00
if (virCommandRun(cmd, &status) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
util: make it easier to grab only regular command exit Auditing all callers of virCommandRun and virCommandWait that passed a non-NULL pointer for exit status turned up some interesting observations. Many callers were merely passing a pointer to avoid the overall command dying, but without caring what the exit status was - but these callers would be better off treating a child death by signal as an abnormal exit. Other callers were actually acting on the status, but not all of them remembered to filter by WIFEXITED and convert with WEXITSTATUS; depending on the platform, this can result in a status being reported as 256 times too big. And among those that correctly parse the output, it gets rather verbose. Finally, there were the callers that explicitly checked that the status was 0, and gave their own message, but with fewer details than what virCommand gives for free. So the best idea is to move the complexity out of callers and into virCommand - by default, we return the actual exit status already cleaned through WEXITSTATUS and treat signals as a failed command; but the few callers that care can ask for raw status and act on it themselves. * src/util/vircommand.h (virCommandRawStatus): New prototype. * src/libvirt_private.syms (util/command.h): Export it. * docs/internals/command.html.in: Document it. * src/util/vircommand.c (virCommandRawStatus): New function. (virCommandWait): Adjust semantics. * tests/commandtest.c (test1): Test it. * daemon/remote.c (remoteDispatchAuthPolkit): Adjust callers. * src/access/viraccessdriverpolkit.c (virAccessDriverPolkitCheck): Likewise. * src/fdstream.c (virFDStreamCloseInt): Likewise. * src/lxc/lxc_process.c (virLXCProcessStart): Likewise. * src/qemu/qemu_command.c (qemuCreateInBridgePortWithHelper): Likewise. * src/xen/xen_driver.c (xenUnifiedXendProbe): Simplify. * tests/reconnect.c (mymain): Likewise. * tests/statstest.c (mymain): Likewise. * src/bhyve/bhyve_process.c (virBhyveProcessStart) (virBhyveProcessStop): Don't overwrite virCommand error. * src/libvirt.c (virConnectAuthGainPolkit): Likewise. * src/openvz/openvz_driver.c (openvzDomainGetBarrierLimit) (openvzDomainSetBarrierLimit): Likewise. * src/util/virebtables.c (virEbTablesOnceInit): Likewise. * src/util/viriptables.c (virIpTablesOnceInit): Likewise. * src/util/virnetdevveth.c (virNetDevVethCreate): Fix debug message. * src/qemu/qemu_capabilities.c (virQEMUCapsInitQMP): Add comment. * src/storage/storage_backend_iscsi.c (virStorageBackendISCSINodeUpdate): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2014-02-20 08:32:19 +08:00
goto cleanup;
}
if (status != 3) {
printf("Unexpected status %d\n", status);
goto cleanup;
}
virCommandRawStatus(cmd);
if (virCommandRun(cmd, &status) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 3) {
printf("Unexpected status %d\n", status);
goto cleanup;
}
virCommandFree(cmd);
cmd = virCommandNewArgList("/bin/sh", "-c", "kill -9 $$", NULL);
util: make it easier to grab only regular command exit Auditing all callers of virCommandRun and virCommandWait that passed a non-NULL pointer for exit status turned up some interesting observations. Many callers were merely passing a pointer to avoid the overall command dying, but without caring what the exit status was - but these callers would be better off treating a child death by signal as an abnormal exit. Other callers were actually acting on the status, but not all of them remembered to filter by WIFEXITED and convert with WEXITSTATUS; depending on the platform, this can result in a status being reported as 256 times too big. And among those that correctly parse the output, it gets rather verbose. Finally, there were the callers that explicitly checked that the status was 0, and gave their own message, but with fewer details than what virCommand gives for free. So the best idea is to move the complexity out of callers and into virCommand - by default, we return the actual exit status already cleaned through WEXITSTATUS and treat signals as a failed command; but the few callers that care can ask for raw status and act on it themselves. * src/util/vircommand.h (virCommandRawStatus): New prototype. * src/libvirt_private.syms (util/command.h): Export it. * docs/internals/command.html.in: Document it. * src/util/vircommand.c (virCommandRawStatus): New function. (virCommandWait): Adjust semantics. * tests/commandtest.c (test1): Test it. * daemon/remote.c (remoteDispatchAuthPolkit): Adjust callers. * src/access/viraccessdriverpolkit.c (virAccessDriverPolkitCheck): Likewise. * src/fdstream.c (virFDStreamCloseInt): Likewise. * src/lxc/lxc_process.c (virLXCProcessStart): Likewise. * src/qemu/qemu_command.c (qemuCreateInBridgePortWithHelper): Likewise. * src/xen/xen_driver.c (xenUnifiedXendProbe): Simplify. * tests/reconnect.c (mymain): Likewise. * tests/statstest.c (mymain): Likewise. * src/bhyve/bhyve_process.c (virBhyveProcessStart) (virBhyveProcessStop): Don't overwrite virCommand error. * src/libvirt.c (virConnectAuthGainPolkit): Likewise. * src/openvz/openvz_driver.c (openvzDomainGetBarrierLimit) (openvzDomainSetBarrierLimit): Likewise. * src/util/virebtables.c (virEbTablesOnceInit): Likewise. * src/util/viriptables.c (virIpTablesOnceInit): Likewise. * src/util/virnetdevveth.c (virNetDevVethCreate): Fix debug message. * src/qemu/qemu_capabilities.c (virQEMUCapsInitQMP): Add comment. * src/storage/storage_backend_iscsi.c (virStorageBackendISCSINodeUpdate): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2014-02-20 08:32:19 +08:00
if (virCommandRun(cmd, &status) == 0) {
printf("Death by signal not detected, status %d\n", status);
goto cleanup;
}
virCommandRawStatus(cmd);
if (virCommandRun(cmd, &status) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
printf("Unexpected status %d\n", status);
goto cleanup;
}
ret = 0;
cleanup:
virCommandFree(cmd);
return ret;
}
static int
test23(const void *unused ATTRIBUTE_UNUSED)
{
/* Not strictly a virCommand test, but this is the easiest place
* to test this lower-level interface. It takes a double fork to
* test virProcessExitWithStatus. */
int ret = -1;
int status = -1;
pid_t pid;
virFork: simplify semantics The old semantics of virFork() violates the priciple of good usability: it requires the caller to check the pid argument after use, *even when virFork returned -1*, in order to properly abort a child process that failed setup done immediately after fork() - that is, the caller must call _exit() in the child. While uses in virfile.c did this correctly, uses in 'virsh lxc-enter-namespace' and 'virt-login-shell' would happily return from the calling function in both the child and the parent, leading to very confusing results. [Thankfully, I found the problem by inspection, and can't actually trigger the double return on error without an LD_PRELOAD library.] It is much better if the semantics of virFork are impossible to abuse. Looking at virFork(), the parent could only ever return -1 with a non-negative pid if it misused pthread_sigmask, but this never happens. Up until this patch series, the child could return -1 with non-negative pid if it fails to set up signals correctly, but we recently fixed that to make the child call _exit() at that point instead of forcing the caller to do it. Thus, the return value and contents of the pid argument are now redundant (a -1 return now happens only for failure to fork, a child 0 return only happens for a successful 0 pid, and a parent 0 return only happens for a successful non-zero pid), so we might as well return the pid directly rather than an integer of whether it succeeded or failed; this is also good from the interface design perspective as users are already familiar with fork() semantics. One last change in this patch: before returning the pid directly, I found cases where using virProcessWait unconditionally on a cleanup path of a virFork's -1 pid return would be nicer if there were a way to avoid it overwriting an earlier message. While such paths are a bit harder to come by with my change to a direct pid return, I decided to keep the virProcessWait change in this patch. * src/util/vircommand.h (virFork): Change signature. * src/util/vircommand.c (virFork): Guarantee that child will only return on success, to simplify callers. Return pid rather than status, now that the situations are always the same. (virExec): Adjust caller, also avoid open-coding process death. * src/util/virprocess.c (virProcessWait): Tweak semantics when pid is -1. (virProcessRunInMountNamespace): Adjust caller. * src/util/virfile.c (virFileAccessibleAs, virFileOpenForked) (virDirCreate): Likewise. * tools/virt-login-shell.c (main): Likewise. * tools/virsh-domain.c (cmdLxcEnterNamespace): Likewise. * tests/commandtest.c (test23): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2013-12-22 08:54:33 +08:00
if ((pid = virFork()) < 0)
goto cleanup;
if (pid == 0) {
virFork: simplify semantics The old semantics of virFork() violates the priciple of good usability: it requires the caller to check the pid argument after use, *even when virFork returned -1*, in order to properly abort a child process that failed setup done immediately after fork() - that is, the caller must call _exit() in the child. While uses in virfile.c did this correctly, uses in 'virsh lxc-enter-namespace' and 'virt-login-shell' would happily return from the calling function in both the child and the parent, leading to very confusing results. [Thankfully, I found the problem by inspection, and can't actually trigger the double return on error without an LD_PRELOAD library.] It is much better if the semantics of virFork are impossible to abuse. Looking at virFork(), the parent could only ever return -1 with a non-negative pid if it misused pthread_sigmask, but this never happens. Up until this patch series, the child could return -1 with non-negative pid if it fails to set up signals correctly, but we recently fixed that to make the child call _exit() at that point instead of forcing the caller to do it. Thus, the return value and contents of the pid argument are now redundant (a -1 return now happens only for failure to fork, a child 0 return only happens for a successful 0 pid, and a parent 0 return only happens for a successful non-zero pid), so we might as well return the pid directly rather than an integer of whether it succeeded or failed; this is also good from the interface design perspective as users are already familiar with fork() semantics. One last change in this patch: before returning the pid directly, I found cases where using virProcessWait unconditionally on a cleanup path of a virFork's -1 pid return would be nicer if there were a way to avoid it overwriting an earlier message. While such paths are a bit harder to come by with my change to a direct pid return, I decided to keep the virProcessWait change in this patch. * src/util/vircommand.h (virFork): Change signature. * src/util/vircommand.c (virFork): Guarantee that child will only return on success, to simplify callers. Return pid rather than status, now that the situations are always the same. (virExec): Adjust caller, also avoid open-coding process death. * src/util/virprocess.c (virProcessWait): Tweak semantics when pid is -1. (virProcessRunInMountNamespace): Adjust caller. * src/util/virfile.c (virFileAccessibleAs, virFileOpenForked) (virDirCreate): Likewise. * tools/virt-login-shell.c (main): Likewise. * tools/virsh-domain.c (cmdLxcEnterNamespace): Likewise. * tests/commandtest.c (test23): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2013-12-22 08:54:33 +08:00
if ((pid = virFork()) < 0)
_exit(EXIT_FAILURE);
if (pid == 0)
_exit(42);
if (virProcessWait(pid, &status, true) < 0)
_exit(EXIT_FAILURE);
virProcessExitWithStatus(status);
_exit(EXIT_FAILURE);
}
if (virProcessWait(pid, &status, true) < 0)
goto cleanup;
if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) {
printf("Unexpected status %d\n", status);
goto cleanup;
}
virFork: simplify semantics The old semantics of virFork() violates the priciple of good usability: it requires the caller to check the pid argument after use, *even when virFork returned -1*, in order to properly abort a child process that failed setup done immediately after fork() - that is, the caller must call _exit() in the child. While uses in virfile.c did this correctly, uses in 'virsh lxc-enter-namespace' and 'virt-login-shell' would happily return from the calling function in both the child and the parent, leading to very confusing results. [Thankfully, I found the problem by inspection, and can't actually trigger the double return on error without an LD_PRELOAD library.] It is much better if the semantics of virFork are impossible to abuse. Looking at virFork(), the parent could only ever return -1 with a non-negative pid if it misused pthread_sigmask, but this never happens. Up until this patch series, the child could return -1 with non-negative pid if it fails to set up signals correctly, but we recently fixed that to make the child call _exit() at that point instead of forcing the caller to do it. Thus, the return value and contents of the pid argument are now redundant (a -1 return now happens only for failure to fork, a child 0 return only happens for a successful 0 pid, and a parent 0 return only happens for a successful non-zero pid), so we might as well return the pid directly rather than an integer of whether it succeeded or failed; this is also good from the interface design perspective as users are already familiar with fork() semantics. One last change in this patch: before returning the pid directly, I found cases where using virProcessWait unconditionally on a cleanup path of a virFork's -1 pid return would be nicer if there were a way to avoid it overwriting an earlier message. While such paths are a bit harder to come by with my change to a direct pid return, I decided to keep the virProcessWait change in this patch. * src/util/vircommand.h (virFork): Change signature. * src/util/vircommand.c (virFork): Guarantee that child will only return on success, to simplify callers. Return pid rather than status, now that the situations are always the same. (virExec): Adjust caller, also avoid open-coding process death. * src/util/virprocess.c (virProcessWait): Tweak semantics when pid is -1. (virProcessRunInMountNamespace): Adjust caller. * src/util/virfile.c (virFileAccessibleAs, virFileOpenForked) (virDirCreate): Likewise. * tools/virt-login-shell.c (main): Likewise. * tools/virsh-domain.c (cmdLxcEnterNamespace): Likewise. * tests/commandtest.c (test23): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2013-12-22 08:54:33 +08:00
if ((pid = virFork()) < 0)
goto cleanup;
if (pid == 0) {
virFork: simplify semantics The old semantics of virFork() violates the priciple of good usability: it requires the caller to check the pid argument after use, *even when virFork returned -1*, in order to properly abort a child process that failed setup done immediately after fork() - that is, the caller must call _exit() in the child. While uses in virfile.c did this correctly, uses in 'virsh lxc-enter-namespace' and 'virt-login-shell' would happily return from the calling function in both the child and the parent, leading to very confusing results. [Thankfully, I found the problem by inspection, and can't actually trigger the double return on error without an LD_PRELOAD library.] It is much better if the semantics of virFork are impossible to abuse. Looking at virFork(), the parent could only ever return -1 with a non-negative pid if it misused pthread_sigmask, but this never happens. Up until this patch series, the child could return -1 with non-negative pid if it fails to set up signals correctly, but we recently fixed that to make the child call _exit() at that point instead of forcing the caller to do it. Thus, the return value and contents of the pid argument are now redundant (a -1 return now happens only for failure to fork, a child 0 return only happens for a successful 0 pid, and a parent 0 return only happens for a successful non-zero pid), so we might as well return the pid directly rather than an integer of whether it succeeded or failed; this is also good from the interface design perspective as users are already familiar with fork() semantics. One last change in this patch: before returning the pid directly, I found cases where using virProcessWait unconditionally on a cleanup path of a virFork's -1 pid return would be nicer if there were a way to avoid it overwriting an earlier message. While such paths are a bit harder to come by with my change to a direct pid return, I decided to keep the virProcessWait change in this patch. * src/util/vircommand.h (virFork): Change signature. * src/util/vircommand.c (virFork): Guarantee that child will only return on success, to simplify callers. Return pid rather than status, now that the situations are always the same. (virExec): Adjust caller, also avoid open-coding process death. * src/util/virprocess.c (virProcessWait): Tweak semantics when pid is -1. (virProcessRunInMountNamespace): Adjust caller. * src/util/virfile.c (virFileAccessibleAs, virFileOpenForked) (virDirCreate): Likewise. * tools/virt-login-shell.c (main): Likewise. * tools/virsh-domain.c (cmdLxcEnterNamespace): Likewise. * tests/commandtest.c (test23): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
2013-12-22 08:54:33 +08:00
if ((pid = virFork()) < 0)
_exit(EXIT_FAILURE);
if (pid == 0) {
raise(SIGKILL);
_exit(EXIT_FAILURE);
}
if (virProcessWait(pid, &status, true) < 0)
_exit(EXIT_FAILURE);
virProcessExitWithStatus(status);
_exit(EXIT_FAILURE);
}
if (virProcessWait(pid, &status, true) < 0)
goto cleanup;
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
printf("Unexpected status %d\n", status);
goto cleanup;
}
ret = 0;
cleanup:
return ret;
}
static int test24(const void *unused ATTRIBUTE_UNUSED)
{
char *pidfile = virPidFileBuildPath(abs_builddir, "commandhelper");
char *prefix = NULL;
int newfd1 = dup(STDERR_FILENO);
int newfd2 = dup(STDERR_FILENO);
int newfd3 = dup(STDERR_FILENO);
int ret = -1;
pid_t pid;
virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper");
if (!pidfile)
goto cleanup;
if (VIR_CLOSE(newfd1) < 0)
printf("Cannot close fd %d\n", newfd1);
virCommandSetPidFile(cmd, pidfile);
virCommandDaemonize(cmd);
virCommandPassFD(cmd, newfd2, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
virCommandPassFD(cmd, newfd3, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
virCommandPassListenFDs(cmd);
if (virCommandRun(cmd, NULL) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
goto cleanup;
}
if (virPidFileRead(abs_builddir, "commandhelper", &pid) < 0) {
printf("cannot read pidfile\n");
goto cleanup;
}
if (virAsprintf(&prefix,
"ENV:LISTEN_FDS=2\nENV:LISTEN_PID=%u\n",
pid) < 0)
goto cleanup;
while (kill(pid, 0) != -1)
usleep(100*1000);
ret = checkoutput("test24", prefix);
cleanup:
if (pidfile)
unlink(pidfile);
VIR_FREE(pidfile);
VIR_FREE(prefix);
virCommandFree(cmd);
VIR_FORCE_CLOSE(newfd1);
/* coverity[double_close] */
VIR_FORCE_CLOSE(newfd2);
VIR_FORCE_CLOSE(newfd3);
return ret;
}
static int test25(const void *unused ATTRIBUTE_UNUSED)
{
int ret = -1;
int pipeFD[2] = { -1, -1};
int rv = 0;
ssize_t tries = 100;
pid_t pid;
gid_t *groups = NULL;
int ngroups;
virCommandPtr cmd = virCommandNew("some/nonexistent/binary");
if (pipe(pipeFD) < 0) {
fprintf(stderr, "Unable to create pipe\n");
goto cleanup;
}
if (virSetNonBlock(pipeFD[0]) < 0) {
fprintf(stderr, "Unable to make read end of pipe nonblocking\n");
goto cleanup;
}
if ((ngroups = virGetGroupList(virCommandGetUID(cmd), virCommandGetGID(cmd),
&groups)) < 0)
goto cleanup;
/* Now, fork and try to exec a nonexistent binary. */
pid = virFork();
if (pid < 0) {
fprintf(stderr, "Unable to spawn child\n");
goto cleanup;
}
if (pid == 0) {
/* Child */
rv = virCommandExec(cmd, groups, ngroups);
if (safewrite(pipeFD[1], &rv, sizeof(rv)) < 0)
fprintf(stderr, "Unable to write to pipe\n");
_exit(EXIT_FAILURE);
}
/* Parent */
while (--tries) {
if (saferead(pipeFD[0], &rv, sizeof(rv)) < 0) {
if (errno != EWOULDBLOCK) {
fprintf(stderr, "Unable to read from pipe\n");
goto cleanup;
}
usleep(10 * 1000);
} else {
break;
}
}
if (!tries) {
fprintf(stderr, "Child hasn't returned anything\n");
goto cleanup;
}
if (rv >= 0) {
fprintf(stderr, "Child should have returned an error\n");
goto cleanup;
}
ret = 0;
cleanup:
VIR_FORCE_CLOSE(pipeFD[0]);
VIR_FORCE_CLOSE(pipeFD[1]);
VIR_FREE(groups);
virCommandFree(cmd);
return ret;
}
static void virCommandThreadWorker(void *opaque)
{
virCommandTestDataPtr test = opaque;
virMutexLock(&test->lock);
while (!test->quit) {
virMutexUnlock(&test->lock);
if (virEventRunDefaultImpl() < 0) {
test->quit = true;
break;
}
virMutexLock(&test->lock);
}
test->running = false;
virMutexUnlock(&test->lock);
return;
}
static void
virCommandTestFreeTimer(int timer ATTRIBUTE_UNUSED,
void *opaque ATTRIBUTE_UNUSED)
{
/* nothing to be done here */
}
static int
tests: simplify common setup A few of the tests were missing basic sanity checks, while most of them were doing copy-and-paste initialization (in fact, some of them pasted the argc > 1 check more than once!). It's much nicer to do things in one common place, and minimizes the size of the next patch that fixes getcwd usage. * tests/testutils.h (EXIT_AM_HARDFAIL): New define. (progname, abs_srcdir): Define for all tests. (VIRT_TEST_MAIN): Change callback signature. * tests/testutils.c (virtTestMain): Do more common init. * tests/commandtest.c (mymain): Simplify. * tests/cputest.c (mymain): Likewise. * tests/esxutilstest.c (mymain): Likewise. * tests/eventtest.c (mymain): Likewise. * tests/hashtest.c (mymain): Likewise. * tests/networkxml2xmltest.c (mymain): Likewise. * tests/nodedevxml2xmltest.c (myname): Likewise. * tests/nodeinfotest.c (mymain): Likewise. * tests/nwfilterxml2xmltest.c (mymain): Likewise. * tests/qemuargv2xmltest.c (mymain): Likewise. * tests/qemuhelptest.c (mymain): Likewise. * tests/qemuxml2argvtest.c (mymain): Likewise. * tests/qemuxml2xmltest.c (mymain): Likewise. * tests/qparamtest.c (mymain): Likewise. * tests/sexpr2xmltest.c (mymain): Likewise. * tests/sockettest.c (mymain): Likewise. * tests/statstest.c (mymain): Likewise. * tests/storagepoolxml2xmltest.c (mymain): Likewise. * tests/storagevolxml2xmltest.c (mymain): Likewise. * tests/virbuftest.c (mymain): Likewise. * tests/virshtest.c (mymain): Likewise. * tests/vmx2xmltest.c (mymain): Likewise. * tests/xencapstest.c (mymain): Likewise. * tests/xmconfigtest.c (mymain): Likewise. * tests/xml2sexprtest.c (mymain): Likewise. * tests/xml2vmxtest.c (mymain): Likewise.
2011-04-30 00:21:20 +08:00
mymain(void)
{
int ret = 0;
int fd;
virCommandTestDataPtr test = NULL;
int timer = -1;
int virinitret;
if (virThreadInitialize() < 0)
return EXIT_FAILURE;
if (chdir("/tmp") < 0)
return EXIT_FAILURE;
umask(022);
setpgid(0, 0);
ignore_value(setsid());
/* Our test expects particular fd values; to get that, we must not
* leak fds that we inherited from a lazy parent. At the same
* time, virInitialize may open some fds (perhaps via third-party
* libraries that it uses), and we must not kill off an fd that
* this process opens as it might break expectations of a
* pthread_atfork handler, as well as interfering with our tests
* trying to ensure we aren't leaking to our children. The
* solution is to do things in two phases - reserve the fds we
* want by overwriting any externally inherited fds, then
* initialize, then clear the slots for testing. */
if ((fd = open("/dev/null", O_RDONLY)) < 0 ||
dup2(fd, 3) < 0 ||
dup2(fd, 4) < 0 ||
dup2(fd, 5) < 0 ||
dup2(fd, 6) < 0 ||
dup2(fd, 7) < 0 ||
dup2(fd, 8) < 0 ||
(fd > 8 && VIR_CLOSE(fd) < 0)) {
VIR_FORCE_CLOSE(fd);
return EXIT_FAILURE;
}
/* Prime the debug/verbose settings from the env vars,
* since we're about to reset 'environ' */
ignore_value(virTestGetDebug());
ignore_value(virTestGetVerbose());
/* Make sure to not leak fd's */
virinitret = virInitialize();
/* Phase two of killing interfering fds; see above. */
/* coverity[overwrite_var] - silence the obvious */
fd = 3;
VIR_FORCE_CLOSE(fd);
fd = 4;
VIR_FORCE_CLOSE(fd);
fd = 5;
VIR_FORCE_CLOSE(fd);
fd = 6;
VIR_FORCE_CLOSE(fd);
fd = 7;
VIR_FORCE_CLOSE(fd);
fd = 8;
VIR_FORCE_CLOSE(fd);
if (virinitret < 0)
return EXIT_FAILURE;
virEventRegisterDefaultImpl();
if (VIR_ALLOC(test) < 0)
goto cleanup;
if (virMutexInit(&test->lock) < 0) {
printf("Unable to init mutex: %d\n", errno);
goto cleanup;
}
virMutexLock(&test->lock);
if (virThreadCreate(&test->thread,
true,
virCommandThreadWorker,
test) < 0) {
virMutexUnlock(&test->lock);
goto cleanup;
}
test->running = true;
virMutexUnlock(&test->lock);
environ = (char **)newenv;
# define DO_TEST(NAME) \
if (virTestRun("Command Exec " #NAME " test", \
NAME, NULL) < 0) \
ret = -1
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);
DO_TEST(test17);
DO_TEST(test18);
DO_TEST(test19);
DO_TEST(test20);
DO_TEST(test21);
DO_TEST(test22);
DO_TEST(test23);
DO_TEST(test24);
DO_TEST(test25);
virMutexLock(&test->lock);
if (test->running) {
test->quit = true;
/* HACK: Add a dummy timeout to break event loop */
timer = virEventAddTimeout(0, virCommandTestFreeTimer, NULL, NULL);
}
virMutexUnlock(&test->lock);
cleanup:
if (test->running)
virThreadJoin(&test->thread);
if (timer != -1)
virEventRemoveTimeout(timer);
virMutexDestroy(&test->lock);
VIR_FREE(test);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
VIR_TEST_MAIN(mymain)
#endif /* !WIN32 */