Signed-off-by: Shi Lei <shilei.massclouds@xxxxxxx> --- docs/internals/command.html.in | 68 +++++++ src/libvirt_private.syms | 2 + src/util/vircommand.c | 110 ++++++++++- src/util/vircommand.h | 4 + src/util/virprocess.c | 107 ++++++++--- src/util/virprocess.h | 4 + tests/commandtest.c | 411 ++++++++++++++++++++++++++++++++++++++--- 7 files changed, 651 insertions(+), 55 deletions(-) diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in index 43f51a4..e064019 100644 --- a/docs/internals/command.html.in +++ b/docs/internals/command.html.in @@ -530,6 +530,74 @@ if (WEXITSTATUS(status)...) { virCommandAbort to reap the process. </p> + <h3><a id="timeout">Set timeout for commands</a></h3> + + <p> + With <code>virCommandSetTimeout</code>, commands can be set + timeout in milliseconds. It can be used under one of these conditions: + </p> + + <p> + 1. In synchronous mode, using <code>virCommandRun</code> NOT in + daemon mode. By default, <code>virCommandRun</code> waits for the + command to complete & exit and then checks its exit status. If the + caller uses <code>virCommandSetTimeout</code> before it, the command will be + aborted internally when timeout and there is no need to use + <code>virCommandAbort</code> to reap the child process. + </p> + +<pre> +int timeout = 3000; /* in milliseconds */ +virCommandSetTimeout(cmd, timeout); +if (virCommandRun(cmd, NULL) < 0) { + if (virCommandGetErr(cmd) == ETIME) + ... do something for timeout, e.g. report error ... + + return -1; +} +</pre> + + <p> + The argument <code>timeout</code> of the <code>virCommandSetTimeout</code> + accepts a positive value. When timeout, <code>virCommandRun</code> return + -1 and the caller can use <code>virCommandGetErr</code> to check errorcode. And + <code>ETIME</code> means command timeout. + </p> + <p> + <strong>Note:</strong> <code>virCommandSetTimeout</code> cannot mix with + <code>virCommandDaemonize</code>. Or <code>virCommandRun</code> fails directly. + </p> + + <p> + 2. In asynchronous mode, the caller can also call <code>virCommandSetTimeout</code> + before <code>virCommandRunAsync</code> to limit the running time of the command. + The caller uses <code>virCommandWait</code> to wait for the child process to complete + & exit. <code>virCommandWait</code> returns -1 if timeout and the caller can use + <code>virCommandGetErr</code> to check the reason. + </p> + +<pre> +char *outbuf = NULL; +char *errbuf = NULL; +int timeout = 3000; /* in milliseconds */ +virCommandSetTimeout(cmd, timeout); + +virCommandSetOutputBuffer(cmd, &outbuf); +virCommandSetErrorBuffer(cmd, &errbuf); +virCommandDoAsyncIO(cmd); +if (virCommandRunAsync(cmd, NULL) < 0) + return -1; + +... do something ... + +if (virCommandWait(cmd, NULL) < 0) { + if (virCommandGetErr(cmd) == ETIME) + ... do something for timeout, e.g. report error ... + + return -1; +} +</pre> + <h3><a id="release">Releasing resources</a></h3> <p> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 32ed5a0..7a44d19 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1604,6 +1604,7 @@ virCommandDaemonize; virCommandDoAsyncIO; virCommandExec; virCommandFree; +virCommandGetErr; virCommandGetGID; virCommandGetUID; virCommandHandshakeNotify; @@ -1638,6 +1639,7 @@ virCommandSetOutputFD; virCommandSetPidFile; virCommandSetPreExecHook; virCommandSetSELinuxLabel; +virCommandSetTimeout; virCommandSetUID; virCommandSetUmask; virCommandSetWorkingDirectory; diff --git a/src/util/vircommand.c b/src/util/vircommand.c index 8be3fdf..1966648 100644 --- a/src/util/vircommand.c +++ b/src/util/vircommand.c @@ -53,6 +53,7 @@ #include "virbuffer.h" #include "virthread.h" #include "virstring.h" +#include "virtime.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -136,6 +137,8 @@ struct _virCommand { char *appArmorProfile; #endif int mask; + + int timeout; }; /* See virCommandSetDryRun for description for this variable */ @@ -906,6 +909,8 @@ virCommandNewArgs(const char *const*args) cmd->uid = -1; cmd->gid = -1; + cmd->timeout = -1; + virCommandAddArgSet(cmd, args); return cmd; @@ -1077,6 +1082,13 @@ virCommandGetUID(virCommandPtr cmd) } +int +virCommandGetErr(virCommandPtr cmd) +{ + return cmd ? cmd->has_error : -1; +} + + void virCommandSetGID(virCommandPtr cmd, gid_t gid) { @@ -2016,6 +2028,8 @@ virCommandProcessIO(virCommandPtr cmd) size_t inlen = 0, outlen = 0, errlen = 0; size_t inoff = 0; int ret = 0; + int timeout = -1; + unsigned long long start; if (dryRunBuffer || dryRunCallback) { VIR_DEBUG("Dry run requested, skipping I/O processing"); @@ -2041,6 +2055,11 @@ virCommandProcessIO(virCommandPtr cmd) if (VIR_REALLOC_N(*cmd->errbuf, 1) < 0) ret = -1; } + if (cmd->timeout > 0) { + timeout = cmd->timeout; + if (virTimeMillisNow(&start) < 0) + ret = -1; + } if (ret == -1) goto cleanup; ret = -1; @@ -2069,17 +2088,45 @@ virCommandProcessIO(virCommandPtr cmd) nfds++; } - if (nfds == 0) + if (nfds == 0) { + /* + * Timeout can be changed during loops. + * Set cmd->timeout with rest of timeout, + * and virCommandWait may uses it later + */ + if (timeout != -1 && timeout != cmd->timeout) + cmd->timeout = timeout; break; + } + + ret = poll(fds, nfds, timeout); + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) { + if (cmd->timeout > 0) { + unsigned long long now; + if (virTimeMillisNow(&now) < 0) + goto cleanup; - if (poll(fds, nfds, -1) < 0) { - if (errno == EAGAIN || errno == EINTR) + if (timeout > (int)(now - start)) + timeout -= (int)(now - start); + else + timeout = 0; + } continue; + } virReportSystemError(errno, "%s", _("unable to poll on child")); goto cleanup; } + if (ret == 0) { + /* Timeout to abort command and return failure */ + virCommandAbort(cmd); + cmd->has_error = ETIME; + ret = -1; + goto cleanup; + } + for (i = 0; i < nfds; i++) { if (fds[i].revents & (POLLIN | POLLHUP | POLLERR) && (fds[i].fd == errfd || fds[i].fd == outfd)) { @@ -2126,6 +2173,7 @@ virCommandProcessIO(virCommandPtr cmd) done = write(cmd->inpipe, cmd->inbuf + inoff, inlen - inoff); + if (done < 0) { if (errno == EPIPE) { VIR_DEBUG("child closed stdin early, ignoring EPIPE " @@ -2348,7 +2396,9 @@ virCommandDoAsyncIOHelper(void *opaque) virCommandPtr cmd = opaque; if (virCommandProcessIO(cmd) < 0) { /* If something went wrong, save errno or -1*/ - cmd->has_error = errno ? errno : -1; + /* except ETIME which means timeout */ + if (cmd->has_error != ETIME) + cmd->has_error = errno ? errno : -1; } } @@ -2438,6 +2488,13 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid) _("creation of pid file requires daemonized command")); goto cleanup; } + if (cmd->timeout > 0) { + if (cmd->flags & VIR_EXEC_DAEMON) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("daemonized command cannot use virCommandSetTimeout")); + goto cleanup; + } + } str = virCommandToString(cmd); if (dryRunBuffer || dryRunCallback) { @@ -2531,6 +2588,11 @@ virCommandWait(virCommandPtr cmd, int *exitstatus) virReportOOMError(); return -1; } + if (cmd->has_error == ETIME) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("timeout waiting for child io")); + return -1; + } if (cmd->has_error) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("invalid use of command API")); @@ -2559,7 +2621,17 @@ virCommandWait(virCommandPtr cmd, int *exitstatus) * message is not as detailed as what we can provide. So, we * guarantee that virProcessWait only fails due to failure to wait, * and repeat the exitstatus check code ourselves. */ - ret = virProcessWait(cmd->pid, &status, true); + if (cmd->timeout >= 0) { + ret = virProcessWaitTimeout(cmd->pid, &status, true, &(cmd->timeout)); + if (ret == 1) { + virCommandAbort(cmd); + cmd->has_error = ETIME; + ret = -1; + } + } else { + ret = virProcessWait(cmd->pid, &status, true); + } + if (cmd->flags & VIR_EXEC_ASYNC_IO) { cmd->flags &= ~VIR_EXEC_ASYNC_IO; virThreadJoin(cmd->asyncioThread); @@ -3163,3 +3235,31 @@ virCommandRunNul(virCommandPtr cmd ATTRIBUTE_UNUSED, return -1; } #endif /* WIN32 */ + + +/** + * virCommandSetTimeout: + * @cmd: the command to set timeout + * @timeout: the number of milliseconds for timeout. + * it should be a positive value. + * + * Set timeout for the command. When timeout, abort the command. + * By default, command without calling this function has no timeout. + * Note: virCommandSetTimeout should be used under one of the conditions: + * 1) virCommandRun WITHOUT setting VIR_EXEC_DAEMON + * 2) virCommandRunAsync and virCommandWait + */ +void +virCommandSetTimeout(virCommandPtr cmd, int timeout) +{ + if (!cmd || cmd->has_error) + return; + + if (timeout <= 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("timeout can't be zero or negative")); + return; + } + + cmd->timeout = timeout; +} diff --git a/src/util/vircommand.h b/src/util/vircommand.h index 90bcc6c..99785af 100644 --- a/src/util/vircommand.h +++ b/src/util/vircommand.h @@ -77,6 +77,8 @@ void virCommandSetGID(virCommandPtr cmd, gid_t gid); void virCommandSetUID(virCommandPtr cmd, uid_t uid); +int virCommandGetErr(virCommandPtr cmd) ATTRIBUTE_NONNULL(1); + void virCommandSetMaxMemLock(virCommandPtr cmd, unsigned long long bytes); void virCommandSetMaxProcesses(virCommandPtr cmd, unsigned int procs); void virCommandSetMaxFiles(virCommandPtr cmd, unsigned int files); @@ -219,6 +221,8 @@ int virCommandRunNul(virCommandPtr cmd, virCommandRunNulFunc func, void *data); +void virCommandSetTimeout(virCommandPtr cmd, int timeout); + VIR_DEFINE_AUTOPTR_FUNC(virCommand, virCommandFree) #endif /* __VIR_COMMAND_H__ */ diff --git a/src/util/virprocess.c b/src/util/virprocess.c index ecea27a..e290c95 100644 --- a/src/util/virprocess.c +++ b/src/util/virprocess.c @@ -61,6 +61,7 @@ #include "virutil.h" #include "virstring.h" #include "vircommand.h" +#include "virtime.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -211,32 +212,12 @@ virProcessAbort(pid_t pid) #endif -/** - * virProcessWait: - * @pid: child to wait on - * @exitstatus: optional status collection - * @raw: whether to pass non-normal status back to caller - * - * Wait for a child process to complete. If @pid is -1, do nothing, but - * return -1 (useful for error cleanup, and assumes an earlier message was - * already issued). All other pids issue an error message on failure. - * - * If @exitstatus is NULL, then the child must exit normally with status 0. - * Otherwise, if @raw is false, the child must exit normally, and - * @exitstatus will contain the final exit status (no need for the caller - * to use WEXITSTATUS()). If @raw is true, then the result of waitpid() is - * returned in @exitstatus, and the caller must use WIFEXITED() and friends - * to decipher the child's status. - * - * Returns 0 on a successful wait. Returns -1 on any error waiting for - * completion, or if the command completed with a status that cannot be - * reflected via the choice of @exitstatus and @raw. - */ -int -virProcessWait(pid_t pid, int *exitstatus, bool raw) +static int +virProcessWaitHelper(pid_t pid, int *exitstatus, bool raw, const int *timeout) { int ret; int status; + unsigned long long start; VIR_AUTOFREE(char *) st = NULL; if (pid <= 0) { @@ -246,9 +227,32 @@ virProcessWait(pid_t pid, int *exitstatus, bool raw) return -1; } + if (virTimeMillisNow(&start) < 0) + return -1; + /* Wait for intermediate process to exit */ - while ((ret = waitpid(pid, &status, 0)) == -1 && - errno == EINTR); + if (timeout && *timeout >= 0) { + for (;;) { + while ((ret = waitpid(pid, &status, WNOHANG)) == -1 && + errno == EINTR); + + if (ret == 0) { + unsigned long long now; + if (virTimeMillisNow(&now) < 0) + return -1; + + if ((int)(now - start) >= *timeout) + return 1; /* Timeout */ + + usleep(100 * 1000); + continue; + } + + break; + } + } else { + while ((ret = waitpid(pid, &status, 0)) == -1 && errno == EINTR); + } if (ret == -1) { virReportSystemError(errno, _("unable to wait for process %lld"), @@ -278,6 +282,59 @@ virProcessWait(pid_t pid, int *exitstatus, bool raw) } +/** + * virProcessWait: + * @pid: child to wait on + * @exitstatus: optional status collection + * @raw: whether to pass non-normal status back to caller + * + * Wait for a child process to complete. If @pid is -1, do nothing, but + * return -1 (useful for error cleanup, and assumes an earlier message was + * already issued). All other pids issue an error message on failure. + * + * If @exitstatus is NULL, then the child must exit normally with status 0. + * Otherwise, if @raw is false, the child must exit normally, and + * @exitstatus will contain the final exit status (no need for the caller + * to use WEXITSTATUS()). If @raw is true, then the result of waitpid() is + * returned in @exitstatus, and the caller must use WIFEXITED() and friends + * to decipher the child's status. + * + * Returns 0 on a successful wait. Returns -1 on any error waiting for + * completion, or if the command completed with a status that cannot be + * reflected via the choice of @exitstatus and @raw. + */ +int +virProcessWait(pid_t pid, int *exitstatus, bool raw) +{ + return virProcessWaitHelper(pid, exitstatus, raw, NULL); +} + + +/** + * virProcessWaitTimeout: + * @pid: child to wait on + * @exitstatus: optional status collection + * @raw: whether to pass non-normal status back to caller + * @timeout: pointer to timeout. + * + * Just like virProcessWait, but it has timeout. + * Expect @timeout to be altered by outside, so passes pointer of it. + * + * Returns 0 on a successful wait. + * Returns 1 on timeout. + * Returns -1 on any error waiting for completion. + */ +int +virProcessWaitTimeout(pid_t pid, int *exitstatus, bool raw, const int *timeout) +{ + if (!timeout || *timeout < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("timeout invalid")); + return -1; + } + return virProcessWaitHelper(pid, exitstatus, raw, timeout); +} + + /* send signal to a single process */ int virProcessKill(pid_t pid, int sig) { diff --git a/src/util/virprocess.h b/src/util/virprocess.h index 3c5a882..bc4abd1 100644 --- a/src/util/virprocess.h +++ b/src/util/virprocess.h @@ -52,6 +52,10 @@ int virProcessWait(pid_t pid, int *exitstatus, bool raw) ATTRIBUTE_RETURN_CHECK; +int +virProcessWaitTimeout(pid_t pid, int *exitstatus, bool raw, const int *timeout) + ATTRIBUTE_RETURN_CHECK; + int virProcessKill(pid_t pid, int sig); int virProcessKillPainfully(pid_t pid, bool force); diff --git a/tests/commandtest.c b/tests/commandtest.c index 744a387..26c6b58 100644 --- a/tests/commandtest.c +++ b/tests/commandtest.c @@ -51,6 +51,10 @@ struct _virCommandTestData { bool running; }; +struct testdata { + int timeout; /* milliseconds */ +}; + #ifdef WIN32 int @@ -113,17 +117,22 @@ static int checkoutput(const char *testname, 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) +static int test0(const void *opaque) { virCommandPtr cmd; int ret = -1; + const struct testdata *data = opaque; cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist"); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) == 0) goto cleanup; @@ -138,18 +147,23 @@ static int test0(const void *unused ATTRIBUTE_UNUSED) 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) +static int test1(const void *opaque) { virCommandPtr cmd; int ret = -1; int status; + const struct testdata *data = opaque; cmd = virCommandNew(abs_builddir "/commandhelper-doesnotexist"); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, &status) < 0) goto cleanup; if (status != EXIT_ENOENT) @@ -167,14 +181,18 @@ static int test1(const void *unused ATTRIBUTE_UNUSED) 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) +static int test2(const void *opaque) { - virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); int ret; + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; + if (data) + virCommandSetTimeout(cmd, data->timeout); if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); @@ -198,13 +216,15 @@ static int test2(const void *unused ATTRIBUTE_UNUSED) 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) +static int test3(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; int newfd1 = dup(STDERR_FILENO); int newfd2 = dup(STDERR_FILENO); int newfd3 = dup(STDERR_FILENO); @@ -214,6 +234,9 @@ static int test3(const void *unused ATTRIBUTE_UNUSED) virCommandPassFD(cmd, newfd3, VIR_COMMAND_PASS_FD_CLOSE_PARENT); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -282,12 +305,16 @@ static int test4(const void *unused ATTRIBUTE_UNUSED) * Run program, no args, inherit filtered ENV, keep CWD. * Only stdin/out/err open */ -static int test5(const void *unused ATTRIBUTE_UNUSED) +static int test5(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; virCommandAddEnvPassCommon(cmd); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -304,13 +331,17 @@ static int test5(const void *unused ATTRIBUTE_UNUSED) * Run program, no args, inherit filtered ENV, keep CWD. * Only stdin/out/err open */ -static int test6(const void *unused ATTRIBUTE_UNUSED) +static int test6(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL); virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -327,14 +358,18 @@ static int test6(const void *unused ATTRIBUTE_UNUSED) * Run program, no args, inherit filtered ENV, keep CWD. * Only stdin/out/err open */ -static int test7(const void *unused ATTRIBUTE_UNUSED) +static int test7(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; virCommandAddEnvPassCommon(cmd); virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL); virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -346,19 +381,24 @@ static int test7(const void *unused ATTRIBUTE_UNUSED) 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) +static int test8(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; virCommandAddEnvString(cmd, "USER=bogus"); virCommandAddEnvString(cmd, "LANG=C"); virCommandAddEnvPair(cmd, "USER", "also bogus"); virCommandAddEnvPair(cmd, "USER", "test"); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -375,9 +415,10 @@ static int test8(const void *unused ATTRIBUTE_UNUSED) * Run program, some args, inherit all ENV, keep CWD. * Only stdin/out/err open */ -static int test9(const void *unused ATTRIBUTE_UNUSED) +static int test9(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; const char* const args[] = { "arg1", "arg2", NULL }; virBuffer buf = VIR_BUFFER_INITIALIZER; @@ -396,6 +437,9 @@ static int test9(const void *unused ATTRIBUTE_UNUSED) return -1; } + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -412,15 +456,19 @@ static int test9(const void *unused ATTRIBUTE_UNUSED) * Run program, some args, inherit all ENV, keep CWD. * Only stdin/out/err open */ -static int test10(const void *unused ATTRIBUTE_UNUSED) +static int test10(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; const char *const args[] = { "-version", "-log=bar.log", NULL, }; virCommandAddArgSet(cmd, args); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -432,17 +480,22 @@ static int test10(const void *unused ATTRIBUTE_UNUSED) 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) +static int test11(const void *opaque) { const char *args[] = { abs_builddir "/commandhelper", "-version", "-log=bar.log", NULL, }; virCommandPtr cmd = virCommandNewArgs(args); + const struct testdata *data = opaque; + + if (data) + virCommandSetTimeout(cmd, data->timeout); if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); @@ -455,16 +508,21 @@ static int test11(const void *unused ATTRIBUTE_UNUSED) 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) +static int test12(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; virCommandSetInputBuffer(cmd, "Hello World\n"); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); virCommandFree(cmd); @@ -476,13 +534,15 @@ static int test12(const void *unused ATTRIBUTE_UNUSED) 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) +static int test13(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; char *outactual = NULL; const char *outexpect = "BEGIN STDOUT\n" "Hello World\n" @@ -492,6 +552,9 @@ static int test13(const void *unused ATTRIBUTE_UNUSED) virCommandSetInputBuffer(cmd, "Hello World\n"); virCommandSetOutputBuffer(cmd, &outactual); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -515,13 +578,15 @@ static int test13(const void *unused ATTRIBUTE_UNUSED) 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) +static int test14(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; char *outactual = NULL; const char *outexpect = "BEGIN STDOUT\n" "Hello World\n" @@ -544,6 +609,9 @@ static int test14(const void *unused ATTRIBUTE_UNUSED) virCommandSetOutputBuffer(cmd, &outactual); virCommandSetErrorBuffer(cmd, &erractual); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -557,6 +625,9 @@ static int test14(const void *unused ATTRIBUTE_UNUSED) virCommandSetInputBuffer(cmd, "Hello World\n"); virCommandSetOutputBuffer(cmd, &jointactual); virCommandSetErrorBuffer(cmd, &jointactual); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -592,9 +663,10 @@ static int test14(const void *unused ATTRIBUTE_UNUSED) * Run program, no args, inherit all ENV, change CWD. * Only stdin/out/err open */ -static int test15(const void *unused ATTRIBUTE_UNUSED) +static int test15(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; char *cwd = NULL; int ret = -1; @@ -603,6 +675,9 @@ static int test15(const void *unused ATTRIBUTE_UNUSED) virCommandSetWorkingDirectory(cmd, cwd); virCommandSetUmask(cmd, 002); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -617,17 +692,22 @@ static int test15(const void *unused ATTRIBUTE_UNUSED) return ret; } + /* * Don't run program; rather, log what would be run. */ -static int test16(const void *unused ATTRIBUTE_UNUSED) +static int test16(const void *opaque) { virCommandPtr cmd = virCommandNew("true"); + const struct testdata *data = opaque; char *outactual = NULL; const char *outexpect = "A=B C='D E' true F 'G H'"; int ret = -1; int fd = -1; + if (data) + virCommandSetTimeout(cmd, data->timeout); + virCommandAddEnvPair(cmd, "A", "B"); virCommandAddEnvPair(cmd, "C", "D E"); virCommandAddArg(cmd, "F"); @@ -662,16 +742,21 @@ static int test16(const void *unused ATTRIBUTE_UNUSED) return ret; } + /* * Test string handling when no output is present. */ -static int test17(const void *unused ATTRIBUTE_UNUSED) +static int test17(const void *opaque) { virCommandPtr cmd = virCommandNew("true"); + const struct testdata *data = opaque; int ret = -1; char *outbuf; char *errbuf = NULL; + if (data) + virCommandSetTimeout(cmd, data->timeout); + virCommandSetOutputBuffer(cmd, &outbuf); if (outbuf != NULL) { puts("buffer not sanitized at registration"); @@ -718,6 +803,7 @@ static int test17(const void *unused ATTRIBUTE_UNUSED) return ret; } + /* * Run long-running daemon, to ensure no hang. */ @@ -766,15 +852,20 @@ static int test18(const void *unused ATTRIBUTE_UNUSED) return ret; } + /* * Asynchronously run long-running daemon, to ensure no hang. */ -static int test19(const void *unused ATTRIBUTE_UNUSED) +static int test19(const void *opaque) { + const struct testdata *data = opaque; virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL); pid_t pid; int ret = -1; + if (data) + virCommandSetTimeout(cmd, data->timeout); + alarm(5); if (virCommandRunAsync(cmd, &pid) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); @@ -802,14 +893,16 @@ static int test19(const void *unused ATTRIBUTE_UNUSED) 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) +static int test20(const void *opaque) { virCommandPtr cmd = virCommandNewArgList(abs_builddir "/commandhelper", "--close-stdin", NULL); + const struct testdata *data = opaque; char *buf; int ret = -1; @@ -825,6 +918,9 @@ static int test20(const void *unused ATTRIBUTE_UNUSED) goto cleanup; virCommandSetInputBuffer(cmd, buf); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRun(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -837,6 +933,7 @@ static int test20(const void *unused ATTRIBUTE_UNUSED) return ret; } + static const char *const newenv[] = { "PATH=/usr/bin:/bin", "HOSTNAME=test", @@ -849,9 +946,11 @@ static const char *const newenv[] = { NULL }; -static int test21(const void *unused ATTRIBUTE_UNUSED) + +static int test21(const void *opaque) { virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; int ret = -1; const char *wrbuf = "Hello world\n"; char *outbuf = NULL, *errbuf = NULL; @@ -867,6 +966,9 @@ static int test21(const void *unused ATTRIBUTE_UNUSED) virCommandSetErrorBuffer(cmd, &errbuf); virCommandDoAsyncIO(cmd); + if (data) + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRunAsync(cmd, NULL) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); goto cleanup; @@ -896,14 +998,17 @@ static int test21(const void *unused ATTRIBUTE_UNUSED) return ret; } -static int -test22(const void *unused ATTRIBUTE_UNUSED) + +static int test22(const void *opaque) { int ret = -1; virCommandPtr cmd; int status = -1; + const struct testdata *data = opaque; cmd = virCommandNewArgList("/bin/sh", "-c", "exit 3", NULL); + if (data) + virCommandSetTimeout(cmd, data->timeout); if (virCommandRun(cmd, &status) < 0) { printf("Cannot run child %s\n", virGetLastErrorMessage()); @@ -949,8 +1054,7 @@ test22(const void *unused ATTRIBUTE_UNUSED) } -static int -test23(const void *unused ATTRIBUTE_UNUSED) +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 @@ -1006,6 +1110,7 @@ test23(const void *unused ATTRIBUTE_UNUSED) return ret; } + static int test24(const void *unused ATTRIBUTE_UNUSED) { char *pidfile = virPidFileBuildPath(abs_builddir, "commandhelper"); @@ -1138,6 +1243,211 @@ static int test25(const void *unused ATTRIBUTE_UNUSED) } +/* + * virCommandSetTimeout cannot mix with daemon + */ +static int test26(const void *opaque) +{ + const char *expect_msg = + "internal error: daemonized command cannot use virCommandSetTimeout"; + virCommandPtr cmd = virCommandNew(abs_builddir "/commandhelper"); + const struct testdata *data = opaque; + char *pidfile = virPidFileBuildPath(abs_builddir, "commandhelper"); + int ret = -1; + + if (!data) + goto cleanup; + if (!pidfile) + goto cleanup; + + virCommandSetPidFile(cmd, pidfile); + virCommandDaemonize(cmd); + virCommandSetTimeout(cmd, data->timeout); + + if (virCommandRun(cmd, NULL) != -1 || + STRNEQ(virGetLastErrorMessage(), expect_msg)) { + printf("virCommandSetTimeout mixes with virCommandDaemonize %s\n", + virGetLastErrorMessage()); + goto cleanup; + } + + ret = 0; + cleanup: + virCommandFree(cmd); + if (pidfile) + unlink(pidfile); + VIR_FREE(pidfile); + return ret; +} + + +/* + * virCommandRunAsync without async string io when time-out + */ +static int test27(const void *opaque) +{ + int ret = -1; + virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL); + pid_t pid; + const struct testdata *data = opaque; + if (!data) + goto cleanup; + + virCommandSetTimeout(cmd, data->timeout); + if (virCommandRunAsync(cmd, &pid) < 0) { + printf("Cannot run child %s\n", virGetLastErrorMessage()); + goto cleanup; + } + + if (virCommandWait(cmd, NULL) != -1 || + virCommandGetErr(cmd) != ETIME) { + printf("Timeout doesn't work %s:%d\n", + virGetLastErrorMessage(), + virCommandGetErr(cmd)); + goto cleanup; + } + + ret = 0; + cleanup: + virCommandFree(cmd); + return ret; +} + + +/* + * synchronous mode: abort command when time-out + */ +static int test28(const void *opaque) +{ + const char *expect_msg1 = "internal error: timeout waiting for child io"; + const char *expect_msg2 = "internal error: invalid use of command API"; + virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL); + const struct testdata *data = opaque; + if (!data) { + printf("opaque arg NULL\n"); + virCommandFree(cmd); + return -1; + } + + virCommandSetTimeout(cmd, data->timeout); + + if (virCommandRun(cmd, NULL) != -1 || + virCommandGetErr(cmd) != ETIME || + STRNEQ(virGetLastErrorMessage(), expect_msg1)) { + printf("Timeout doesn't work %s (first)\n", virGetLastErrorMessage()); + virCommandFree(cmd); + return -1; + } + + if (virCommandRun(cmd, NULL) != -1 || + virCommandGetErr(cmd) != ETIME || + STRNEQ(virGetLastErrorMessage(), expect_msg2)) { + printf("Timeout doesn't work %s (second)\n", virGetLastErrorMessage()); + virCommandFree(cmd); + return -1; + } + + virCommandFree(cmd); + return 0; +} + + +/* + * asynchronous mode with async string io: + * abort command when time-out + */ +static int test29(const void *opaque) +{ + int ret = -1; + const char *wrbuf = "Hello world\n"; + char *outbuf = NULL; + char *errbuf = NULL; + const char *expect_msg = + "Error while processing command's IO: Timer expired:"; + virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL); + const struct testdata *data = opaque; + if (!data) { + printf("opaque arg NULL\n"); + ret = -1; + goto cleanup; + } + + virCommandSetTimeout(cmd, data->timeout); + + virCommandSetInputBuffer(cmd, wrbuf); + virCommandSetOutputBuffer(cmd, &outbuf); + virCommandSetErrorBuffer(cmd, &errbuf); + virCommandDoAsyncIO(cmd); + + if (virCommandRunAsync(cmd, NULL) < 0) { + printf("Cannot run child %s\n", virGetLastErrorMessage()); + ret = -1; + goto cleanup; + } + + if (virCommandWait(cmd, NULL) != -1 || + virCommandGetErr(cmd) != ETIME || + STRPREFIX(virGetLastErrorMessage(), expect_msg)) { + printf("Timeout doesn't work %s:%d\n", + virGetLastErrorMessage(), + virCommandGetErr(cmd)); + goto cleanup; + } + + ret = 0; + cleanup: + VIR_FREE(outbuf); + VIR_FREE(errbuf); + virCommandFree(cmd); + return ret; +} + + +/* + * asynchronous mode only with input buffer + * abort command when time-out + */ +static int test30(const void *opaque) +{ + int ret = -1; + const char *wrbuf = "Hello world\n"; + const char *expect_msg = + "Error while processing command's IO: Timer expired:"; + virCommandPtr cmd = virCommandNewArgList("sleep", "10", NULL); + const struct testdata *data = opaque; + if (!data) { + printf("opaque arg NULL\n"); + ret = -1; + goto cleanup; + } + + virCommandSetTimeout(cmd, data->timeout); + + virCommandSetInputBuffer(cmd, wrbuf); + virCommandDoAsyncIO(cmd); + + if (virCommandRunAsync(cmd, NULL) < 0) { + printf("Cannot run child %s\n", virGetLastErrorMessage()); + ret = -1; + goto cleanup; + } + + if (virCommandWait(cmd, NULL) != -1 || + virCommandGetErr(cmd) != ETIME || + STRPREFIX(virGetLastErrorMessage(), expect_msg)) { + printf("Timeout doesn't work %s:%d\n", + virGetLastErrorMessage(), + virCommandGetErr(cmd)); + goto cleanup; + } + + ret = 0; + cleanup: + virCommandFree(cmd); + return ret; +} + + static void virCommandThreadWorker(void *opaque) { virCommandTestDataPtr test = opaque; @@ -1161,6 +1471,7 @@ static void virCommandThreadWorker(void *opaque) return; } + static void virCommandTestFreeTimer(int timer ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) @@ -1168,6 +1479,7 @@ virCommandTestFreeTimer(int timer ATTRIBUTE_UNUSED, /* nothing to be done here */ } + static int mymain(void) { @@ -1176,6 +1488,7 @@ mymain(void) virCommandTestDataPtr test = NULL; int timer = -1; int virinitret; + struct testdata data; if (virThreadInitialize() < 0) return EXIT_FAILURE; @@ -1292,6 +1605,54 @@ mymain(void) DO_TEST(test24); DO_TEST(test25); + + /* tests for virCommandSetTimeout */ + /* 1) NO time-out */ + data.timeout = 3*1000; + +# define DO_TEST_SET_TIMEOUT(NAME) \ + if (virTestRun("Command Exec " #NAME " test", \ + NAME, &data) < 0) \ + ret = -1 + + /* Exclude test4, test18 and test24 for they're in daemon mode. + * Exclude test25, there's no meaning to set timeout + */ + DO_TEST_SET_TIMEOUT(test0); + DO_TEST_SET_TIMEOUT(test1); + DO_TEST_SET_TIMEOUT(test2); + DO_TEST_SET_TIMEOUT(test3); + DO_TEST_SET_TIMEOUT(test5); + DO_TEST_SET_TIMEOUT(test6); + DO_TEST_SET_TIMEOUT(test7); + DO_TEST_SET_TIMEOUT(test8); + DO_TEST_SET_TIMEOUT(test9); + DO_TEST_SET_TIMEOUT(test10); + DO_TEST_SET_TIMEOUT(test11); + DO_TEST_SET_TIMEOUT(test12); + DO_TEST_SET_TIMEOUT(test13); + DO_TEST_SET_TIMEOUT(test14); + DO_TEST_SET_TIMEOUT(test15); + DO_TEST_SET_TIMEOUT(test16); + DO_TEST_SET_TIMEOUT(test17); + DO_TEST_SET_TIMEOUT(test19); + DO_TEST_SET_TIMEOUT(test20); + DO_TEST_SET_TIMEOUT(test21); + DO_TEST_SET_TIMEOUT(test22); + DO_TEST_SET_TIMEOUT(test23); + + /* 2) unsupported usage */ + /* Failure: virCommandSetTimeout mixes with daemon */ + DO_TEST_SET_TIMEOUT(test26); + + /* 3) when time-out */ + data.timeout = 100; + DO_TEST_SET_TIMEOUT(test27); /* timeout in async mode without async string io */ + DO_TEST_SET_TIMEOUT(test28); /* timeout in sync mode */ + DO_TEST_SET_TIMEOUT(test29); /* timeout in async mode with async string io */ + DO_TEST_SET_TIMEOUT(test30); /* timeout in async mode with only input buffer */ + + virMutexLock(&test->lock); if (test->running) { test->quit = true; -- 2.7.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list