From nobody Mon May 6 14:28:06 2024
Delivered-To: importer@patchew.org
Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28
as permitted sender) client-ip=209.132.183.28;
envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com;
Authentication-Results: mx.zohomail.com;
spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as
permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com
Return-Path:
+ With virCommandSetTimeout
, commands can be set
+ timeout in milliseconds. It can be used under one of these condition=
s:
+
+ 1. In synchronous mode, using virCommandRun
NOT in
+ daemon mode. By default, virCommandRun
waits for the
+ command to complete & exit and then checks its exit status. If t=
he
+ caller uses virCommandSetTimeout
before it, the command=
will be
+ aborted internally when timeout and there is no need to use
+ virCommandAbort
to reap the child process.
+
+int timeout =3D 3000; /* in milliseconds */ +virCommandSetTimeout(cmd, timeout); +if (virCommandRun(cmd, NULL) < 0) { + if (virCommandGetErr(cmd) =3D=3D ETIME) + ... do something for timeout, e.g. report error ... + + return -1; +} ++ +
+ The argument timeout
of the virCommandSetTimeout<=
/code>
+ accepts a positive value. When timeout,
virCommandRun
r=
eturn
+ -1 and the caller can use virCommandGetErr
to check err=
orcode. And
+ ETIME
means command timeout.
+
+ Note: virCommandSetTimeout
cannot mix =
with
+ virCommandDaemonize
. Or virCommandRun
fail=
s directly.
+
+ 2. In asynchronous mode, the caller can also call virCommandSe=
tTimeout
+ before virCommandRunAsync
to limit the running time of =
the command.
+ The caller uses virCommandWait
to wait for the child pr=
ocess to complete
+ & exit. virCommandWait
returns -1 if timeout and th=
e caller can use
+ virCommandGetErr
to check the reason.
+
+char *outbuf =3D NULL; +char *errbuf =3D NULL; +int timeout =3D 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) =3D=3D ETIME) + ... do something for timeout, e.g. report error ... + + return -1; +} ++
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"
=20
#define VIR_FROM_THIS VIR_FROM_NONE
=20
@@ -136,6 +137,8 @@ struct _virCommand {
char *appArmorProfile;
#endif
int mask;
+
+ int timeout;
};
=20
/* See virCommandSetDryRun for description for this variable */
@@ -906,6 +909,8 @@ virCommandNewArgs(const char *const*args)
cmd->uid =3D -1;
cmd->gid =3D -1;
=20
+ cmd->timeout =3D -1;
+
virCommandAddArgSet(cmd, args);
=20
return cmd;
@@ -1077,6 +1082,13 @@ virCommandGetUID(virCommandPtr cmd)
}
=20
=20
+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 =3D 0, outlen =3D 0, errlen =3D 0;
size_t inoff =3D 0;
int ret =3D 0;
+ int timeout =3D -1;
+ unsigned long long start;
=20
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 =3D -1;
}
+ if (cmd->timeout > 0) {
+ timeout =3D cmd->timeout;
+ if (virTimeMillisNow(&start) < 0)
+ ret =3D -1;
+ }
if (ret =3D=3D -1)
goto cleanup;
ret =3D -1;
@@ -2069,17 +2088,45 @@ virCommandProcessIO(virCommandPtr cmd)
nfds++;
}
=20
- if (nfds =3D=3D 0)
+ if (nfds =3D=3D 0) {
+ /*
+ * Timeout can be changed during loops.
+ * Set cmd->timeout with rest of timeout,
+ * and virCommandWait may uses it later
+ */
+ if (timeout !=3D -1 && timeout !=3D cmd->timeout)
+ cmd->timeout =3D timeout;
break;
+ }
+
+ ret =3D poll(fds, nfds, timeout);
+ if (ret < 0) {
+ if (errno =3D=3D EAGAIN || errno =3D=3D EINTR) {
+ if (cmd->timeout > 0) {
+ unsigned long long now;
+ if (virTimeMillisNow(&now) < 0)
+ goto cleanup;
=20
- if (poll(fds, nfds, -1) < 0) {
- if (errno =3D=3D EAGAIN || errno =3D=3D EINTR)
+ if (timeout > (int)(now - start))
+ timeout -=3D (int)(now - start);
+ else
+ timeout =3D 0;
+ }
continue;
+ }
virReportSystemError(errno, "%s",
_("unable to poll on child"));
goto cleanup;
}
=20
+ if (ret =3D=3D 0) {
+ /* Timeout to abort command and return failure */
+ virCommandAbort(cmd);
+ cmd->has_error =3D ETIME;
+ ret =3D -1;
+ goto cleanup;
+ }
+
for (i =3D 0; i < nfds; i++) {
if (fds[i].revents & (POLLIN | POLLHUP | POLLERR) &&
(fds[i].fd =3D=3D errfd || fds[i].fd =3D=3D outfd)) {
@@ -2126,6 +2173,7 @@ virCommandProcessIO(virCommandPtr cmd)
=20
done =3D write(cmd->inpipe, cmd->inbuf + inoff,
inlen - inoff);
+
if (done < 0) {
if (errno =3D=3D EPIPE) {
VIR_DEBUG("child closed stdin early, ignoring EPIP=
E "
@@ -2348,7 +2396,9 @@ virCommandDoAsyncIOHelper(void *opaque)
virCommandPtr cmd =3D opaque;
if (virCommandProcessIO(cmd) < 0) {
/* If something went wrong, save errno or -1*/
- cmd->has_error =3D errno ? errno : -1;
+ /* except ETIME which means timeout */
+ if (cmd->has_error !=3D ETIME)
+ cmd->has_error =3D errno ? errno : -1;
}
}
=20
@@ -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 virCommandSetT=
imeout"));
+ goto cleanup;
+ }
+ }
=20
str =3D virCommandToString(cmd);
if (dryRunBuffer || dryRunCallback) {
@@ -2531,6 +2588,11 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
virReportOOMError();
return -1;
}
+ if (cmd->has_error =3D=3D 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 =3D virProcessWait(cmd->pid, &status, true);
+ if (cmd->timeout >=3D 0) {
+ ret =3D virProcessWaitTimeout(cmd->pid, &status, true, &(cmd->time=
out));
+ if (ret =3D=3D 1) {
+ virCommandAbort(cmd);
+ cmd->has_error =3D ETIME;
+ ret =3D -1;
+ }
+ } else {
+ ret =3D virProcessWait(cmd->pid, &status, true);
+ }
+
if (cmd->flags & VIR_EXEC_ASYNC_IO) {
cmd->flags &=3D ~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 <=3D 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("timeout can't be zero or negative"));
+ return;
+ }
+
+ cmd->timeout =3D 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);
=20
void virCommandSetUID(virCommandPtr cmd, uid_t uid);
=20
+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);
=20
+void virCommandSetTimeout(virCommandPtr cmd, int timeout);
+
VIR_DEFINE_AUTOPTR_FUNC(virCommand, virCommandFree)
=20
#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"
=20
#define VIR_FROM_THIS VIR_FROM_NONE
=20
@@ -211,32 +212,12 @@ virProcessAbort(pid_t pid)
#endif
=20
=20
-/**
- * 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 *time=
out)
{
int ret;
int status;
+ unsigned long long start;
VIR_AUTOFREE(char *) st =3D NULL;
=20
if (pid <=3D 0) {
@@ -246,9 +227,32 @@ virProcessWait(pid_t pid, int *exitstatus, bool raw)
return -1;
}
=20
+ if (virTimeMillisNow(&start) < 0)
+ return -1;
+
/* Wait for intermediate process to exit */
- while ((ret =3D waitpid(pid, &status, 0)) =3D=3D -1 &&
- errno =3D=3D EINTR);
+ if (timeout && *timeout >=3D 0) {
+ for (;;) {
+ while ((ret =3D waitpid(pid, &status, WNOHANG)) =3D=3D -1 &&
+ errno =3D=3D EINTR);
+
+ if (ret =3D=3D 0) {
+ unsigned long long now;
+ if (virTimeMillisNow(&now) < 0)
+ return -1;
+
+ if ((int)(now - start) >=3D *timeout)
+ return 1; /* Timeout */
+
+ usleep(100 * 1000);
+ continue;
+ }
+
+ break;
+ }
+ } else {
+ while ((ret =3D waitpid(pid, &status, 0)) =3D=3D -1 && errno =3D=
=3D EINTR);
+ }
=20
if (ret =3D=3D -1) {
virReportSystemError(errno, _("unable to wait for process %lld"),
@@ -278,6 +282,59 @@ virProcessWait(pid_t pid, int *exitstatus, bool raw)
}
=20
=20
+/**
+ * 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 *tim=
eout)
+{
+ 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;
=20
+int
+virProcessWaitTimeout(pid_t pid, int *exitstatus, bool raw, const int *tim=
eout)
+ ATTRIBUTE_RETURN_CHECK;
+
int virProcessKill(pid_t pid, int sig);
=20
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;
};
=20
+struct testdata {
+ int timeout; /* milliseconds */
+};
+
#ifdef WIN32
=20
int
@@ -113,17 +117,22 @@ static int checkoutput(const char *testname,
return ret;
}
=20
+
/*
* 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 =3D -1;
+ const struct testdata *data =3D opaque;
=20
cmd =3D virCommandNew(abs_builddir "/commandhelper-doesnotexist");
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
+
if (virCommandRun(cmd, NULL) =3D=3D 0)
goto cleanup;
=20
@@ -138,18 +147,23 @@ static int test0(const void *unused ATTRIBUTE_UNUSED)
return ret;
}
=20
+
/*
* 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 =3D -1;
int status;
+ const struct testdata *data =3D opaque;
=20
cmd =3D virCommandNew(abs_builddir "/commandhelper-doesnotexist");
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
+
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
if (status !=3D EXIT_ENOENT)
@@ -167,14 +181,18 @@ static int test1(const void *unused ATTRIBUTE_UNUSED)
return ret;
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
int ret;
+ virCommandPtr cmd =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
=20
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);
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
int newfd1 =3D dup(STDERR_FILENO);
int newfd2 =3D dup(STDERR_FILENO);
int newfd3 =3D dup(STDERR_FILENO);
@@ -214,6 +234,9 @@ static int test3(const void *unused ATTRIBUTE_UNUSED)
virCommandPassFD(cmd, newfd3,
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
=20
virCommandAddEnvPassCommon(cmd);
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
=20
virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL);
virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL);
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
=20
virCommandAddEnvPassCommon(cmd);
virCommandAddEnvPassBlockSUID(cmd, "DISPLAY", NULL);
virCommandAddEnvPassBlockSUID(cmd, "DOESNOTEXIST", NULL);
=20
+ 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);
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
=20
virCommandAddEnvString(cmd, "USER=3Dbogus");
virCommandAddEnvString(cmd, "LANG=3DC");
virCommandAddEnvPair(cmd, "USER", "also bogus");
virCommandAddEnvPair(cmd, "USER", "test");
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
const char* const args[] =3D { "arg1", "arg2", NULL };
virBuffer buf =3D VIR_BUFFER_INITIALIZER;
=20
@@ -396,6 +437,9 @@ static int test9(const void *unused ATTRIBUTE_UNUSED)
return -1;
}
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
const char *const args[] =3D {
"-version", "-log=3Dbar.log", NULL,
};
=20
virCommandAddArgSet(cmd, args);
=20
+ 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);
}
=20
+
/*
* 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[] =3D {
abs_builddir "/commandhelper",
"-version", "-log=3Dbar.log", NULL,
};
virCommandPtr cmd =3D virCommandNewArgs(args);
+ const struct testdata *data =3D opaque;
+
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
=20
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);
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
=20
virCommandSetInputBuffer(cmd, "Hello World\n");
=20
+ 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);
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
char *outactual =3D NULL;
const char *outexpect =3D "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);
=20
+ 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;
}
=20
+
/*
* 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
char *outactual =3D NULL;
const char *outexpect =3D "BEGIN STDOUT\n"
"Hello World\n"
@@ -544,6 +609,9 @@ static int test14(const void *unused ATTRIBUTE_UNUSED)
virCommandSetOutputBuffer(cmd, &outactual);
virCommandSetErrorBuffer(cmd, &erractual);
=20
+ 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 =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
char *cwd =3D NULL;
int ret =3D -1;
=20
@@ -603,6 +675,9 @@ static int test15(const void *unused ATTRIBUTE_UNUSED)
virCommandSetWorkingDirectory(cmd, cwd);
virCommandSetUmask(cmd, 002);
=20
+ 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;
}
=20
+
/*
* 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 =3D virCommandNew("true");
+ const struct testdata *data =3D opaque;
char *outactual =3D NULL;
const char *outexpect =3D "A=3DB C=3D'D E' true F 'G H'";
int ret =3D -1;
int fd =3D -1;
=20
+ 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;
}
=20
+
/*
* Test string handling when no output is present.
*/
-static int test17(const void *unused ATTRIBUTE_UNUSED)
+static int test17(const void *opaque)
{
virCommandPtr cmd =3D virCommandNew("true");
+ const struct testdata *data =3D opaque;
int ret =3D -1;
char *outbuf;
char *errbuf =3D NULL;
=20
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
+
virCommandSetOutputBuffer(cmd, &outbuf);
if (outbuf !=3D NULL) {
puts("buffer not sanitized at registration");
@@ -718,6 +803,7 @@ static int test17(const void *unused ATTRIBUTE_UNUSED)
return ret;
}
=20
+
/*
* Run long-running daemon, to ensure no hang.
*/
@@ -766,15 +852,20 @@ static int test18(const void *unused ATTRIBUTE_UNUSED)
return ret;
}
=20
+
/*
* 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 =3D opaque;
virCommandPtr cmd =3D virCommandNewArgList("sleep", "100", NULL);
pid_t pid;
int ret =3D -1;
=20
+ 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;
}
=20
+
/*
* 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 =3D virCommandNewArgList(abs_builddir "/commandhelpe=
r",
"--close-stdin", NULL);
+ const struct testdata *data =3D opaque;
char *buf;
int ret =3D -1;
=20
@@ -825,6 +918,9 @@ static int test20(const void *unused ATTRIBUTE_UNUSED)
goto cleanup;
virCommandSetInputBuffer(cmd, buf);
=20
+ 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;
}
=20
+
static const char *const newenv[] =3D {
"PATH=3D/usr/bin:/bin",
"HOSTNAME=3Dtest",
@@ -849,9 +946,11 @@ static const char *const newenv[] =3D {
NULL
};
=20
-static int test21(const void *unused ATTRIBUTE_UNUSED)
+
+static int test21(const void *opaque)
{
virCommandPtr cmd =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
int ret =3D -1;
const char *wrbuf =3D "Hello world\n";
char *outbuf =3D NULL, *errbuf =3D NULL;
@@ -867,6 +966,9 @@ static int test21(const void *unused ATTRIBUTE_UNUSED)
virCommandSetErrorBuffer(cmd, &errbuf);
virCommandDoAsyncIO(cmd);
=20
+ 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;
}
=20
-static int
-test22(const void *unused ATTRIBUTE_UNUSED)
+
+static int test22(const void *opaque)
{
int ret =3D -1;
virCommandPtr cmd;
int status =3D -1;
+ const struct testdata *data =3D opaque;
=20
cmd =3D virCommandNewArgList("/bin/sh", "-c", "exit 3", NULL);
+ if (data)
+ virCommandSetTimeout(cmd, data->timeout);
=20
if (virCommandRun(cmd, &status) < 0) {
printf("Cannot run child %s\n", virGetLastErrorMessage());
@@ -949,8 +1054,7 @@ test22(const void *unused ATTRIBUTE_UNUSED)
}
=20
=20
-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;
}
=20
+
static int test24(const void *unused ATTRIBUTE_UNUSED)
{
char *pidfile =3D virPidFileBuildPath(abs_builddir, "commandhelper");
@@ -1138,6 +1243,211 @@ static int test25(const void *unused ATTRIBUTE_UNUS=
ED)
}
=20
=20
+/*
+ * virCommandSetTimeout cannot mix with daemon
+ */
+static int test26(const void *opaque)
+{
+ const char *expect_msg =3D
+ "internal error: daemonized command cannot use virCommandSetTimeou=
t";
+ virCommandPtr cmd =3D virCommandNew(abs_builddir "/commandhelper");
+ const struct testdata *data =3D opaque;
+ char *pidfile =3D virPidFileBuildPath(abs_builddir, "commandhelper");
+ int ret =3D -1;
+
+ if (!data)
+ goto cleanup;
+ if (!pidfile)
+ goto cleanup;
+
+ virCommandSetPidFile(cmd, pidfile);
+ virCommandDaemonize(cmd);
+ virCommandSetTimeout(cmd, data->timeout);
+
+ if (virCommandRun(cmd, NULL) !=3D -1 ||
+ STRNEQ(virGetLastErrorMessage(), expect_msg)) {
+ printf("virCommandSetTimeout mixes with virCommandDaemonize %s\n",
+ virGetLastErrorMessage());
+ goto cleanup;
+ }
+
+ ret =3D 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 =3D -1;
+ virCommandPtr cmd =3D virCommandNewArgList("sleep", "100", NULL);
+ pid_t pid;
+ const struct testdata *data =3D 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) !=3D -1 ||
+ virCommandGetErr(cmd) !=3D ETIME) {
+ printf("Timeout doesn't work %s:%d\n",
+ virGetLastErrorMessage(),
+ virCommandGetErr(cmd));
+ goto cleanup;
+ }
+
+ ret =3D 0;
+ cleanup:
+ virCommandFree(cmd);
+ return ret;
+}
+
+
+/*
+ * synchronous mode: abort command when time-out
+ */
+static int test28(const void *opaque)
+{
+ const char *expect_msg1 =3D "internal error: timeout waiting for child=
io";
+ const char *expect_msg2 =3D "internal error: invalid use of command AP=
I";
+ virCommandPtr cmd =3D virCommandNewArgList("sleep", "100", NULL);
+ const struct testdata *data =3D opaque;
+ if (!data) {
+ printf("opaque arg NULL\n");
+ virCommandFree(cmd);
+ return -1;
+ }
+
+ virCommandSetTimeout(cmd, data->timeout);
+
+ if (virCommandRun(cmd, NULL) !=3D -1 ||
+ virCommandGetErr(cmd) !=3D ETIME ||
+ STRNEQ(virGetLastErrorMessage(), expect_msg1)) {
+ printf("Timeout doesn't work %s (first)\n", virGetLastErrorMessage=
());
+ virCommandFree(cmd);
+ return -1;
+ }
+
+ if (virCommandRun(cmd, NULL) !=3D -1 ||
+ virCommandGetErr(cmd) !=3D ETIME ||
+ STRNEQ(virGetLastErrorMessage(), expect_msg2)) {
+ printf("Timeout doesn't work %s (second)\n", virGetLastErrorMessag=
e());
+ 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 =3D -1;
+ const char *wrbuf =3D "Hello world\n";
+ char *outbuf =3D NULL;
+ char *errbuf =3D NULL;
+ const char *expect_msg =3D
+ "Error while processing command's IO: Timer expired:";
+ virCommandPtr cmd =3D virCommandNewArgList("sleep", "100", NULL);
+ const struct testdata *data =3D opaque;
+ if (!data) {
+ printf("opaque arg NULL\n");
+ ret =3D -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 =3D -1;
+ goto cleanup;
+ }
+
+ if (virCommandWait(cmd, NULL) !=3D -1 ||
+ virCommandGetErr(cmd) !=3D ETIME ||
+ STRPREFIX(virGetLastErrorMessage(), expect_msg)) {
+ printf("Timeout doesn't work %s:%d\n",
+ virGetLastErrorMessage(),
+ virCommandGetErr(cmd));
+ goto cleanup;
+ }
+
+ ret =3D 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 =3D -1;
+ const char *wrbuf =3D "Hello world\n";
+ const char *expect_msg =3D
+ "Error while processing command's IO: Timer expired:";
+ virCommandPtr cmd =3D virCommandNewArgList("sleep", "10", NULL);
+ const struct testdata *data =3D opaque;
+ if (!data) {
+ printf("opaque arg NULL\n");
+ ret =3D -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 =3D -1;
+ goto cleanup;
+ }
+
+ if (virCommandWait(cmd, NULL) !=3D -1 ||
+ virCommandGetErr(cmd) !=3D ETIME ||
+ STRPREFIX(virGetLastErrorMessage(), expect_msg)) {
+ printf("Timeout doesn't work %s:%d\n",
+ virGetLastErrorMessage(),
+ virCommandGetErr(cmd));
+ goto cleanup;
+ }
+
+ ret =3D 0;
+ cleanup:
+ virCommandFree(cmd);
+ return ret;
+}
+
+
static void virCommandThreadWorker(void *opaque)
{
virCommandTestDataPtr test =3D opaque;
@@ -1161,6 +1471,7 @@ static void virCommandThreadWorker(void *opaque)
return;
}
=20
+
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 */
}
=20
+
static int
mymain(void)
{
@@ -1176,6 +1488,7 @@ mymain(void)
virCommandTestDataPtr test =3D NULL;
int timer =3D -1;
int virinitret;
+ struct testdata data;
=20
if (virThreadInitialize() < 0)
return EXIT_FAILURE;
@@ -1292,6 +1605,54 @@ mymain(void)
DO_TEST(test24);
DO_TEST(test25);
=20
+
+ /* tests for virCommandSetTimeout */
+ /* 1) NO time-out */
+ data.timeout =3D 3*1000;
+
+# define DO_TEST_SET_TIMEOUT(NAME) \
+ if (virTestRun("Command Exec " #NAME " test", \
+ NAME, &data) < 0) \
+ ret =3D -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 =3D 100;
+ DO_TEST_SET_TIMEOUT(test27); /* timeout in async mode without async st=
ring io */
+ DO_TEST_SET_TIMEOUT(test28); /* timeout in sync mode */
+ DO_TEST_SET_TIMEOUT(test29); /* timeout in async mode with async strin=
g io */
+ DO_TEST_SET_TIMEOUT(test30); /* timeout in async mode with only input =
buffer */
+
+
virMutexLock(&test->lock);
if (test->running) {
test->quit =3D true;
--=20
2.7.4
--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
From nobody Mon May 6 14:28:06 2024
Delivered-To: importer@patchew.org
Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28
as permitted sender) client-ip=209.132.183.28;
envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com;
Authentication-Results: mx.zohomail.com;
spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as
permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com
Return-Path: