When using virCommandRunAsync and saving the pid for later, it
is useful to be able to reap that pid in the same way that it
would have been auto-reaped by virCommand if we had passed
NULL for the pid argument in the first place.
* src/util/command.c (virPidWait, virPidAbort): New functions,
created from...
(virCommandWait, virCommandAbort): ...bodies of these.
(includes): Drop duplicate <stdlib.h>. Ensure that our pid_t
assumptions hold.
(virCommandRunAsync): Improve documentation.
* src/util/command.h (virPidWait, virPidAbort): New prototypes.
* src/libvirt_private.syms: Export them.
* docs/internals/command.html.in: Document them.
---
docs/internals/command.html.in | 17 +++++
src/libvirt_private.syms | 2 +
src/util/command.c | 140 ++++++++++++++++++++++++++++------------
src/util/command.h | 28 ++++++++
4 files changed, 146 insertions(+), 41 deletions(-)
diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in
index 27dcf9c..8a194ec 100644
--- a/docs/internals/command.html.in
+++ b/docs/internals/command.html.in
@@ -497,6 +497,23 @@
error if not.
</p>
+ <p>
+ There are two approaches to child process cleanup, determined by
+ how long you want to keep the virCommand object in scope.
+ </p>
+
+ <p>1. If the virCommand object will outlast the child process,
+ then pass NULL for the pid argument, and the child process will
+ automatically be reaped at virCommandFree, unless you reap it
+ sooner via virCommandWait or virCommandAbort.
+ </p>
+
+ <p>2. If the child process must exist on at least one code path
+ after virCommandFree, then pass a pointer for the pid argument.
+ Later, to clean up the child, call virPidWait or virPidAbort.
+ Before virCommandFree, you can still use virCommandWait or
+ virCommandAbort to reap the process.
+ </p>
<h3><a name="release">Releasing resources</a></h3>
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 3237d18..f95d341 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -134,6 +134,8 @@ virCommandTranslateStatus;
virCommandWait;
virCommandWriteArgLog;
virFork;
+virPidAbort;
+virPidWait;
virRun;
diff --git a/src/util/command.c b/src/util/command.c
index eae58b2..c194fb1 100644
--- a/src/util/command.c
+++ b/src/util/command.c
@@ -41,8 +41,7 @@
#include "files.h"
#include "buf.h"
#include "ignore-value.h"
-
-#include <stdlib.h>
+#include "verify.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -50,6 +49,9 @@
virReportErrorHelper(VIR_FROM_NONE, code, __FILE__, \
__FUNCTION__, __LINE__, __VA_ARGS__)
+/* We have quite a bit of changes to make if this doesn't hold. */
+verify(sizeof(pid_t) <= sizeof(int));
+
/* Flags for virExecWithHook */
enum {
VIR_EXEC_NONE = 0,
@@ -1897,6 +1899,48 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
/*
+ * Wait for a child process to complete.
+ * Return -1 on any error waiting for
+ * completion. Returns 0 if the command
+ * finished with the exit status set
+ */
+int
+virPidWait(pid_t pid, int *exitstatus)
+{
+ int ret;
+ int status;
+
+ if (pid <= 0) {
+ virReportSystemError(EINVAL, _("unable to wait for process %d"), pid);
+ return -1;
+ }
+
+ /* Wait for intermediate process to exit */
+ while ((ret = waitpid(pid, &status, 0)) == -1 &&
+ errno == EINTR);
+
+ if (ret == -1) {
+ virReportSystemError(errno, _("unable to wait for process %d"), pid);
+ return -1;
+ }
+
+ if (exitstatus == NULL) {
+ if (status != 0) {
+ char *st = virCommandTranslateStatus(status);
+ virCommandError(VIR_ERR_INTERNAL_ERROR,
+ _("Child process (%d) status unexpected: %s"),
+ pid, NULLSTR(st));
+ VIR_FREE(st);
+ return -1;
+ }
+ } else {
+ *exitstatus = status;
+ }
+
+ return 0;
+}
+
+/*
* Wait for the async command to complete.
* Return -1 on any error waiting for
* completion. Returns 0 if the command
@@ -1906,7 +1950,7 @@ int
virCommandWait(virCommandPtr cmd, int *exitstatus)
{
int ret;
- int status;
+ int status = 0;
if (!cmd ||cmd->has_error == ENOMEM) {
virReportOOMError();
@@ -1924,22 +1968,17 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
return -1;
}
-
- /* Wait for intermediate process to exit */
- while ((ret = waitpid(cmd->pid, &status, 0)) == -1 &&
- errno == EINTR);
-
- if (ret == -1) {
- virReportSystemError(errno, _("unable to wait for process %d"),
- cmd->pid);
- return -1;
- }
-
- cmd->pid = -1;
- cmd->reap = false;
-
- if (exitstatus == NULL) {
- if (status != 0) {
+ /* If virPidWait reaps pid but then returns failure because
+ * exitstatus was NULL, then a second virCommandWait would risk
+ * calling waitpid on an unrelated process. Besides, that error
+ * message is not as detailed as what we can provide. So, we
+ * guarantee that virPidWait only fails due to failure to wait,
+ * and repeat the exitstatus check code ourselves. */
+ ret = virPidWait(cmd->pid, exitstatus ? exitstatus : &status);
+ if (ret == 0) {
+ cmd->pid = -1;
+ cmd->reap = false;
+ if (status) {
char *str = virCommandToString(cmd);
char *st = virCommandTranslateStatus(status);
virCommandError(VIR_ERR_INTERNAL_ERROR,
@@ -1949,75 +1988,94 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
VIR_FREE(st);
return -1;
}
- } else {
- *exitstatus = status;
}
- return 0;
+ return ret;
}
#ifndef WIN32
/*
- * Abort an async command if it is running, without issuing
- * any errors or affecting errno. Designed for error paths
- * where some but not all paths to the cleanup code might
- * have started the child process.
+ * Abort a child process if PID is positive and that child is still
+ * running, without issuing any errors or affecting errno. Designed
+ * for error paths where some but not all paths to the cleanup code
+ * might have started the child process.
*/
void
-virCommandAbort(virCommandPtr cmd)
+virPidAbort(pid_t pid)
{
int saved_errno;
int ret;
int status;
char *tmp = NULL;
- if (!cmd || cmd->pid == -1)
+ if (pid <= 0)
return;
/* See if intermediate process has exited; if not, try a nice
* SIGTERM followed by a more severe SIGKILL.
*/
saved_errno = errno;
- VIR_DEBUG("aborting child process %d", cmd->pid);
- while ((ret = waitpid(cmd->pid, &status, WNOHANG)) == -1 &&
+ VIR_DEBUG("aborting child process %d", pid);
+ while ((ret = waitpid(pid, &status, WNOHANG)) == -1 &&
errno == EINTR);
- if (ret == cmd->pid) {
+ if (ret == pid) {
tmp = virCommandTranslateStatus(status);
VIR_DEBUG("process has ended: %s", tmp);
goto cleanup;
} else if (ret == 0) {
- VIR_DEBUG("trying SIGTERM to child process %d", cmd->pid);
- kill(cmd->pid, SIGTERM);
+ VIR_DEBUG("trying SIGTERM to child process %d", pid);
+ kill(pid, SIGTERM);
usleep(10 * 1000);
- while ((ret = waitpid(cmd->pid, &status, WNOHANG)) == -1 &&
+ while ((ret = waitpid(pid, &status, WNOHANG)) == -1 &&
errno == EINTR);
- if (ret == cmd->pid) {
+ if (ret == pid) {
tmp = virCommandTranslateStatus(status);
VIR_DEBUG("process has ended: %s", tmp);
goto cleanup;
} else if (ret == 0) {
- VIR_DEBUG("trying SIGKILL to child process %d", cmd->pid);
- kill(cmd->pid, SIGKILL);
- while ((ret = waitpid(cmd->pid, &status, 0)) == -1 &&
+ VIR_DEBUG("trying SIGKILL to child process %d", pid);
+ kill(pid, SIGKILL);
+ while ((ret = waitpid(pid, &status, 0)) == -1 &&
errno == EINTR);
- if (ret == cmd->pid) {
+ if (ret == pid) {
tmp = virCommandTranslateStatus(status);
VIR_DEBUG("process has ended: %s", tmp);
goto cleanup;
}
}
}
- VIR_DEBUG("failed to reap child %d, abandoning it", cmd->pid);
+ VIR_DEBUG("failed to reap child %d, abandoning it", pid);
cleanup:
VIR_FREE(tmp);
+ errno = saved_errno;
+}
+
+/*
+ * Abort an async command if it is running, without issuing
+ * any errors or affecting errno. Designed for error paths
+ * where some but not all paths to the cleanup code might
+ * have started the child process.
+ */
+void
+virCommandAbort(virCommandPtr cmd)
+{
+ if (!cmd || cmd->pid == -1)
+ return;
+ virPidAbort(cmd->pid);
cmd->pid = -1;
cmd->reap = false;
- errno = saved_errno;
}
#else /* WIN32 */
void
+virPidAbort(pid_t pid)
+{
+ /* Not yet ported to mingw. Any volunteers? */
+ VIR_DEBUG("failed to reap child %d, abandoning it", pid);
+}
+
+void
virCommandAbort(virCommandPtr cmd ATTRIBUTE_UNUSED)
{
/* Mingw lacks WNOHANG and kill(). But since we haven't ported
diff --git a/src/util/command.h b/src/util/command.h
index e9f4227..c8a04f1 100644
--- a/src/util/command.h
+++ b/src/util/command.h
@@ -292,11 +292,31 @@ int virCommandRun(virCommandPtr cmd,
* Run the command asynchronously
* Returns -1 on any error executing the
* command. Returns 0 if the command executed.
+ *
+ * There are two approaches to child process cleanup.
+ * 1. Use auto-cleanup, by passing NULL for pid. The child will be
+ * auto-reaped by virCommandFree, unless you reap it earlier via
+ * virCommandWait or virCommandAbort. Good for where cmd is in
+ * scope for the duration of the child process.
+ * 2. Use manual cleanup, by passing the address of a pid_t variable
+ * for pid. While cmd is still in scope, you may reap the child via
+ * virCommandWait or virCommandAbort. But after virCommandFree, if
+ * you have not yet reaped the child, then it continues to run until
+ * you call virPidWait or virPidAbort.
*/
int virCommandRunAsync(virCommandPtr cmd,
pid_t *pid) ATTRIBUTE_RETURN_CHECK;
/*
+ * Wait for a child process to complete.
+ * Return -1 on any error waiting for
+ * completion. Returns 0 if the command
+ * finished with the exit status set.
+ */
+int virPidWait(pid_t pid,
+ int *exitstatus) ATTRIBUTE_RETURN_CHECK;
+
+/*
* Wait for the async command to complete.
* Return -1 on any error waiting for
* completion. Returns 0 if the command
@@ -328,6 +348,14 @@ int virCommandHandshakeNotify(virCommandPtr cmd)
ATTRIBUTE_RETURN_CHECK;
/*
+ * Abort a child process if PID is positive and that child is still
+ * running, without issuing any errors or affecting errno. Designed
+ * for error paths where some but not all paths to the cleanup code
+ * might have started the child process.
+ */
+void virPidAbort(pid_t pid);
+
+/*
* Abort an async command if it is running, without issuing
* any errors or affecting errno. Designed for error paths
* where some but not all paths to the cleanup code might
--
1.7.4.4