/*
* virprocess.c: interaction with processes
*
* Copyright (C) 2010-2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
*/
#include
#include
#include
#include
#include "virprocess.h"
#include "virterror_internal.h"
#include "memory.h"
#include "logging.h"
#include "util.h"
#define VIR_FROM_THIS VIR_FROM_NONE
/**
* virProcessTranslateStatus:
* @status: child exit status to translate
*
* Translate an exit status into a malloc'd string. Generic helper
* for virCommandRun(), virCommandWait(), virRun(), and virProcessWait()
* status argument, as well as raw waitpid().
*/
char *
virProcessTranslateStatus(int status)
{
char *buf;
if (WIFEXITED(status)) {
ignore_value(virAsprintf(&buf, _("exit status %d"),
WEXITSTATUS(status)));
} else if (WIFSIGNALED(status)) {
ignore_value(virAsprintf(&buf, _("fatal signal %d"),
WTERMSIG(status)));
} else {
ignore_value(virAsprintf(&buf, _("invalid value %d"), status));
}
return buf;
}
#ifndef WIN32
/**
* virProcessAbort:
* @pid: child process to kill
*
* 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. If @pid is 0 or negative,
* this does nothing.
*/
void
virProcessAbort(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 = virProcessTranslateStatus(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 = virProcessTranslateStatus(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 = virProcessTranslateStatus(status);
VIR_DEBUG("process has ended: %s", tmp);
goto cleanup;
}
}
}
VIR_DEBUG("failed to reap child %lld, abandoning it", (long long) pid);
cleanup:
VIR_FREE(tmp);
errno = saved_errno;
}
#else
void
virProcessAbort(pid_t pid)
{
/* Not yet ported to mingw. Any volunteers? */
VIR_DEBUG("failed to reap child %lld, abandoning it", (long long)pid);
}
#endif
/**
* virProcessWait:
* @pid: child to wait on
* @exitstatus: optional status collection
*
* 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. If @exitstatus is NULL, then the
* child must exit with status 0 for this to succeed.
*/
int
virProcessWait(pid_t pid, int *exitstatus)
{
int ret;
int status;
if (pid <= 0) {
virReportSystemError(EINVAL, _("unable to wait for process %lld"),
(long long) 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 %lld"),
(long long) pid);
return -1;
}
if (exitstatus == NULL) {
if (status != 0) {
char *st = virProcessTranslateStatus(status);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Child process (%lld) unexpected %s"),
(long long) pid, NULLSTR(st));
VIR_FREE(st);
return -1;
}
} else {
*exitstatus = status;
}
return 0;
}
/* send signal to a single process */
int virProcessKill(pid_t pid, int sig)
{
if (pid <= 1) {
errno = ESRCH;
return -1;
}
#ifdef WIN32
/* Mingw / Windows don't have many signals (AFAIK) */
switch (sig) {
case SIGINT:
/* This does a Ctrl+C equiv */
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)) {
errno = ESRCH;
return -1;
}
break;
case SIGTERM:
/* Since TerminateProcess is closer to SIG_KILL, we do
* a Ctrl+Break equiv which is more pleasant like the
* good old unix SIGTERM/HUP
*/
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)) {
errno = ESRCH;
return -1;
}
break;
default:
{
HANDLE proc;
proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (!proc) {
errno = ESRCH; /* Not entirely accurate, but close enough */
return -1;
}
/*
* TerminateProcess is more or less equiv to SIG_KILL, in that
* a process can't trap / block it
*/
if (sig != 0 && !TerminateProcess(proc, sig)) {
errno = ESRCH;
return -1;
}
CloseHandle(proc);
}
}
return 0;
#else
return kill(pid, sig);
#endif
}