diff --git a/docs/internals/command.html.in b/docs/internals/command.html.in
index 27dcf9c4d8..8a194ec957 100644
--- a/docs/internals/command.html.in
+++ b/docs/internals/command.html.in
@@ -497,6 +497,23 @@
error if not.
+
+ There are two approaches to child process cleanup, determined by
+ how long you want to keep the virCommand object in scope.
+
+
+ 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.
+
+
+ 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.
+
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index e06c7fcad4..3e3b1dd8d8 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 6c19cd14ae..e2ece788d2 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
+#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,
@@ -1382,8 +1384,8 @@ virCommandToString(virCommandPtr cmd)
/*
* Translate an exit status into a malloc'd string. Generic helper
- * for virCommandRun and virCommandWait status argument, as well as
- * raw waitpid and older virRun status.
+ * for virCommandRun, virCommandWait, and virPidWait status argument,
+ * as well as raw waitpid and older virRun status.
*/
char *
virCommandTranslateStatus(int status)
@@ -1805,6 +1807,17 @@ virCommandHook(void *data)
* 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)
@@ -1896,6 +1909,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
@@ -1906,7 +1961,7 @@ int
virCommandWait(virCommandPtr cmd, int *exitstatus)
{
int ret;
- int status;
+ int status = 0;
if (!cmd ||cmd->has_error == ENOMEM) {
virReportOOMError();
@@ -1924,22 +1979,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,15 +1999,70 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
VIR_FREE(st);
return -1;
}
- } else {
- *exitstatus = status;
}
- return 0;
+ return ret;
}
#ifndef WIN32
+/*
+ * 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)
+{
+ int saved_errno;
+ int ret;
+ int status;
+ char *tmp = NULL;
+
+ 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", pid);
+ while ((ret = waitpid(pid, &status, WNOHANG)) == -1 &&
+ errno == EINTR);
+ 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", pid);
+ kill(pid, SIGTERM);
+ usleep(10 * 1000);
+ while ((ret = waitpid(pid, &status, WNOHANG)) == -1 &&
+ errno == EINTR);
+ 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", pid);
+ kill(pid, SIGKILL);
+ while ((ret = waitpid(pid, &status, 0)) == -1 &&
+ errno == EINTR);
+ if (ret == pid) {
+ tmp = virCommandTranslateStatus(status);
+ VIR_DEBUG("process has ended: %s", tmp);
+ goto cleanup;
+ }
+ }
+ }
+ 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
@@ -1967,56 +2072,20 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
void
virCommandAbort(virCommandPtr cmd)
{
- int saved_errno;
- int ret;
- int status;
- char *tmp = NULL;
-
if (!cmd || cmd->pid == -1)
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 &&
- errno == EINTR);
- if (ret == cmd->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);
- usleep(10 * 1000);
- while ((ret = waitpid(cmd->pid, &status, WNOHANG)) == -1 &&
- errno == EINTR);
- if (ret == cmd->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 &&
- errno == EINTR);
- if (ret == cmd->pid) {
- tmp = virCommandTranslateStatus(status);
- VIR_DEBUG("process has ended: %s", tmp);
- goto cleanup;
- }
- }
- }
- VIR_DEBUG("failed to reap child %d, abandoning it", cmd->pid);
-
-cleanup:
- VIR_FREE(tmp);
+ 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)
{
@@ -2140,7 +2209,9 @@ int virCommandHandshakeNotify(virCommandPtr cmd)
/*
- * Release all resources
+ * Release all resources. The only exception is that if you called
+ * virCommandRunAsync with a non-null pid, then the asynchronous child
+ * is not reaped, and you must call virPidWait() yourself.
*/
void
virCommandFree(virCommandPtr cmd)
diff --git a/src/util/command.h b/src/util/command.h
index e9f422758a..f76683998a 100644
--- a/src/util/command.h
+++ b/src/util/command.h
@@ -292,10 +292,30 @@ 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
@@ -327,6 +347,14 @@ int virCommandHandshakeWait(virCommandPtr cmd)
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
@@ -338,7 +366,7 @@ void virCommandAbort(virCommandPtr cmd);
/*
* Release all resources. The only exception is that if you called
* virCommandRunAsync with a non-null pid, then the asynchronous child
- * is not reaped, and you must call waitpid() yourself.
+ * is not reaped, and you must call virPidWait() yourself.
*/
void virCommandFree(virCommandPtr cmd);