mirror of
https://github.com/libvirt/libvirt.git
synced 2025-02-25 18:55:26 -06:00
command: add virCommandAbort for cleanup paths
Sometimes, an asynchronous helper is started (such as a compressor or iohelper program), but a later error means that we want to abort that child. Make this easier. Note that since daemons and virCommandRunAsync can't mix, the only time virCommandFree can reap a process is if someone did virCommandRunAsync for a non-daemon and didn't stash the pid. * src/util/command.h (virCommandAbort): New prototype. * src/util/command.c (_virCommand): Add new field. (virCommandRunAsync, virCommandWait): Track whether pid was used. (virCommandFree): Reap child if caller did not request pid. (virCommandAbort): New function. * src/libvirt_private.syms (command.h): Export it. * tests/commandtest.c (test19): New test.
This commit is contained in:
parent
4e808602f1
commit
9ed545185f
@ -91,6 +91,7 @@ virCgroupSetMemSwapHardLimit;
|
|||||||
|
|
||||||
|
|
||||||
# command.h
|
# command.h
|
||||||
|
virCommandAbort;
|
||||||
virCommandAddArg;
|
virCommandAddArg;
|
||||||
virCommandAddArgBuffer;
|
virCommandAddArgBuffer;
|
||||||
virCommandAddArgFormat;
|
virCommandAddArgFormat;
|
||||||
|
@ -81,6 +81,7 @@ struct _virCommand {
|
|||||||
|
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
char *pidfile;
|
char *pidfile;
|
||||||
|
bool reap;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1222,6 +1223,8 @@ virCommandRunAsync(virCommandPtr cmd, pid_t *pid)
|
|||||||
|
|
||||||
if (ret == 0 && pid)
|
if (ret == 0 && pid)
|
||||||
*pid = cmd->pid;
|
*pid = cmd->pid;
|
||||||
|
else
|
||||||
|
cmd->reap = true;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1267,6 +1270,7 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd->pid = -1;
|
cmd->pid = -1;
|
||||||
|
cmd->reap = false;
|
||||||
|
|
||||||
if (exitstatus == NULL) {
|
if (exitstatus == NULL) {
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
@ -1287,6 +1291,65 @@ virCommandWait(virCommandPtr cmd, int *exitstatus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
cmd->pid = -1;
|
||||||
|
cmd->reap = false;
|
||||||
|
errno = saved_errno;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release all resources
|
* Release all resources
|
||||||
*/
|
*/
|
||||||
@ -1320,5 +1383,8 @@ virCommandFree(virCommandPtr cmd)
|
|||||||
|
|
||||||
VIR_FREE(cmd->pidfile);
|
VIR_FREE(cmd->pidfile);
|
||||||
|
|
||||||
|
if (cmd->reap)
|
||||||
|
virCommandAbort(cmd);
|
||||||
|
|
||||||
VIR_FREE(cmd);
|
VIR_FREE(cmd);
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,17 @@ int virCommandWait(virCommandPtr cmd,
|
|||||||
int *exitstatus) ATTRIBUTE_RETURN_CHECK;
|
int *exitstatus) ATTRIBUTE_RETURN_CHECK;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release all resources
|
* 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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
void virCommandFree(virCommandPtr cmd);
|
void virCommandFree(virCommandPtr cmd);
|
||||||
|
|
||||||
|
@ -696,6 +696,14 @@ static int test18(const void *unused ATTRIBUTE_UNUSED)
|
|||||||
printf("cannot read pidfile\n");
|
printf("cannot read pidfile\n");
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virCommandFree(cmd);
|
||||||
|
cmd = NULL;
|
||||||
|
if (kill(pid, 0) != 0) {
|
||||||
|
printf("daemon should still be running\n");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
while (kill(pid, SIGINT) != -1)
|
while (kill(pid, SIGINT) != -1)
|
||||||
usleep(100*1000);
|
usleep(100*1000);
|
||||||
|
|
||||||
@ -708,6 +716,42 @@ cleanup:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Asynchronously run long-running daemon, to ensure no hang.
|
||||||
|
*/
|
||||||
|
static int test19(const void *unused ATTRIBUTE_UNUSED)
|
||||||
|
{
|
||||||
|
virCommandPtr cmd = virCommandNewArgList("sleep", "100", NULL);
|
||||||
|
pid_t pid;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
alarm(5);
|
||||||
|
if (virCommandRunAsync(cmd, &pid) < 0) {
|
||||||
|
virErrorPtr err = virGetLastError();
|
||||||
|
printf("Cannot run child %s\n", err->message);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kill(pid, 0) != 0) {
|
||||||
|
printf("Child should still be running");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
virCommandAbort(cmd);
|
||||||
|
|
||||||
|
if (kill(pid, 0) == 0) {
|
||||||
|
printf("Child should be aborted");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm(0);
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
virCommandFree(cmd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mymain(int argc, char **argv)
|
mymain(int argc, char **argv)
|
||||||
@ -781,6 +825,7 @@ mymain(int argc, char **argv)
|
|||||||
DO_TEST(test16);
|
DO_TEST(test16);
|
||||||
DO_TEST(test17);
|
DO_TEST(test17);
|
||||||
DO_TEST(test18);
|
DO_TEST(test18);
|
||||||
|
DO_TEST(test19);
|
||||||
|
|
||||||
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user