libvirt/tests/commandtest.c
Eric Blake 54e99644bf command: shell-quote when logging commands
Without this patch, logged command executions can be ambiguous if
the command contained any shell metacharacters.  This has caused
more than one person to attempt to patch clients to add unnecessary
quoting, without realizing that the command itself was run with
correct args, and only the logged output was ambiguous.

* src/util/command.c (virCommandToString): Add shell escapes.
* tests/commandtest.c (test16): Test new behavior.
* tests/commanddata/test16.log: Update expected output.
* tests/qemuxml2argvdata/qemuxml2argv-*.args: Likewise.
* tests/networkxml2argvdata/*.argv: Likewise.
2012-08-31 08:10:58 -07:00

922 lines
22 KiB
C

/*
* commandtest.c: Test the libCommand API
*
* Copyright (C) 2010-2012 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 <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 "virfile.h"
#include "virpidfile.h"
#include "virterror_internal.h"
#define VIR_FROM_THIS VIR_FROM_NONE
#ifdef WIN32
int
main(void)
{
return EXIT_AM_SKIP;
}
#else
static int checkoutput(const char *testname)
{
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 (STRNEQ(expectlog, actuallog)) {
virtTestDifference(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;
if (virGetLastError() == 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);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
int ret = -1;
virCommandPreserveFD(cmd, newfd1);
virCommandTransferFD(cmd, newfd3);
if (virCommandRun(cmd, NULL) < 0) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
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");
cleanup:
virCommandFree(cmd);
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
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");
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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 };
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
virCommandFree(cmd);
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);
goto cleanup;
}
if (!outactual)
goto cleanup;
virCommandFree(cmd);
cmd = NULL;
if (!STREQ(outactual, outexpect)) {
virtTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
ret = checkoutput("test13");
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
goto cleanup;
}
if (!jointactual)
goto cleanup;
if (!STREQ(outactual, outexpect)) {
virtTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
if (!STREQ(erractual, errexpect)) {
virtTestDifference(stderr, errexpect, erractual);
goto cleanup;
}
if (!STREQ(jointactual, jointexpect)) {
virtTestDifference(stderr, jointexpect, jointactual);
goto cleanup;
}
ret = checkoutput("test14");
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");
char *cwd = NULL;
int ret = -1;
if (virAsprintf(&cwd, "%s/commanddata", abs_srcdir) < 0)
goto cleanup;
virCommandSetWorkingDirectory(cmd, cwd);
if (virCommandRun(cmd, NULL) < 0) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
goto cleanup;
}
ret = checkoutput("test15");
cleanup:
VIR_FREE(cwd);
virCommandFree(cmd);
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) {
virErrorPtr err = virGetLastError();
printf("Cannot convert to string: %s\n", err->message);
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 (!STREQ(outactual, outexpect)) {
virtTestDifference(stderr, outexpect, outactual);
goto cleanup;
}
ret = checkoutput("test16");
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;
virCommandSetOutputBuffer(cmd, &outbuf);
if (outbuf != NULL) {
puts("buffer not sanitized at registration");
goto cleanup;
}
if (virCommandRun(cmd, NULL) < 0) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
goto cleanup;
}
if (!outbuf || *outbuf) {
puts("output buffer is not an allocated empty string");
goto cleanup;
}
VIR_FREE(outbuf);
if ((outbuf = strdup("should not be leaked")) == NULL) {
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
goto cleanup;
}
if (!outbuf || *outbuf || !errbuf || *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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
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) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
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) {
virReportOOMError();
goto cleanup;
}
virCommandSetInputBuffer(cmd, buf);
if (virCommandRun(cmd, NULL) < 0) {
virErrorPtr err = virGetLastError();
printf("Cannot run child %s\n", err->message);
goto cleanup;
}
ret = checkoutput("test20");
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
mymain(void)
{
int ret = 0;
int fd;
if (chdir("/tmp") < 0)
return EXIT_FAILURE;
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 ||
(fd > 5 && VIR_CLOSE(fd) < 0))
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());
if (virInitialize() < 0)
return EXIT_FAILURE;
/* Phase two of killing interfering fds; see above. */
fd = 3;
VIR_FORCE_CLOSE(fd);
fd = 4;
VIR_FORCE_CLOSE(fd);
fd = 5;
VIR_FORCE_CLOSE(fd);
environ = (char **)newenv;
# define DO_TEST(NAME) \
if (virtTestRun("Command Exec " #NAME " test", \
1, 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);
return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
VIRT_TEST_MAIN(mymain)
#endif /* !WIN32 */