mirror of https://github.com/sipwise/heartbeat.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
11 KiB
516 lines
11 KiB
/*
|
|
* Process tracking object.
|
|
*
|
|
* Copyright (c) 2002 International Business Machines
|
|
* Author: Alan Robertson <alanr@unix.sh>
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <lha_internal.h>
|
|
#include <errno.h>
|
|
#include <sys/wait.h>
|
|
#include <heartbeat.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include <clplumbing/proctrack.h>
|
|
#include <clplumbing/cl_log.h>
|
|
#include <clplumbing/uids.h>
|
|
#include <clplumbing/cl_signal.h>
|
|
#include <clplumbing/Gmain_timeout.h>
|
|
|
|
#define DEBUGPROCTRACK debugproctrack
|
|
|
|
|
|
int debugproctrack = 0;
|
|
static int LoggingIsEnabled = 1;
|
|
static GHashTable* ProcessTable = NULL;
|
|
static void InitProcTable(void);
|
|
static void ForEachProcHelper(gpointer key, gpointer value
|
|
, void * helper);
|
|
static gboolean TrackedProcTimeoutFunction(gpointer p);
|
|
|
|
static void
|
|
InitProcTable()
|
|
{
|
|
if (ProcessTable) {
|
|
return;
|
|
}
|
|
|
|
ProcessTable = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
}
|
|
|
|
/* Create/Log a new tracked process */
|
|
void
|
|
NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel
|
|
, void * privatedata, ProcTrack_ops* ops)
|
|
{
|
|
ProcTrack* p = g_new(ProcTrack, 1);
|
|
|
|
InitProcTable();
|
|
p->pid = pid;
|
|
p->isapgrp = isapgrp;
|
|
p->loglevel = loglevel;
|
|
p->privatedata = privatedata;
|
|
p->ops = ops;
|
|
p->startticks = time_longclock();
|
|
p->starttime = time(NULL);
|
|
p->timerid = 0;
|
|
p->timeoutseq = -1;
|
|
p->killinfo = NULL;
|
|
|
|
g_hash_table_insert(ProcessTable, GINT_TO_POINTER(pid), p);
|
|
|
|
/* Tell them that someone registered a process */
|
|
if (p->ops->procregistered) {
|
|
p->ops->procregistered(p);
|
|
}
|
|
}
|
|
|
|
static struct signal_info_s {
|
|
int signo;
|
|
const char * sigdefine;
|
|
const char* sigwords;
|
|
} signal_info [] = {
|
|
|
|
#ifdef SIGHUP
|
|
{SIGHUP, "SIGHUP", "Hangup"},
|
|
#endif
|
|
#ifdef SIGINT
|
|
{SIGINT, "SIGINT", "Interrupt"},
|
|
#endif
|
|
#ifdef SIGQUIT
|
|
{SIGQUIT, "SIGQUIT", "Quit"},
|
|
#endif
|
|
#ifdef SIGILL
|
|
{SIGILL, "SIGILL", "Illegal instruction"},
|
|
#endif
|
|
#ifdef SIGTRAP
|
|
{SIGTRAP, "SIGTRAP", "Trace"},
|
|
#endif
|
|
#ifdef SIGABRT
|
|
{SIGABRT, "SIGABRT", "Abort"},
|
|
#endif
|
|
#ifdef SIGIOT
|
|
{SIGIOT, "SIGIOT", "IOT trap"},
|
|
#endif
|
|
#ifdef SIGBUS
|
|
{SIGBUS, "SIGBUS", "BUS error"},
|
|
#endif
|
|
#ifdef SIGFPE
|
|
{SIGFPE, "SIGFPE", "Floating-point exception"},
|
|
#endif
|
|
#ifdef SIGKILL
|
|
{SIGKILL, "SIGKILL", "Kill, unblockable"},
|
|
#endif
|
|
#ifdef SIGUSR1
|
|
{SIGUSR1, "SIGUSR1", "User-defined signal 1"},
|
|
#endif
|
|
#ifdef SIGSEGV
|
|
{SIGSEGV, "SIGSEGV", "Segmentation violation"},
|
|
#endif
|
|
#ifdef SIGUSR2
|
|
{SIGUSR2, "SIGUSR2", "User-defined signal 2"},
|
|
#endif
|
|
#ifdef SIGPIPE
|
|
{SIGPIPE, "SIGPIPE", "Broken pipe (POSIX)"},
|
|
#endif
|
|
#ifdef SIGALRM
|
|
{SIGALRM, "SIGALRM", "Alarm clock (POSIX)"},
|
|
#endif
|
|
#ifdef SIGTERM
|
|
{SIGTERM, "SIGTERM", "Termination (ANSI)"},
|
|
#endif
|
|
#ifdef SIGSTKFLT
|
|
{SIGSTKFLT, "SIGSTKFLT", "Stack fault"},
|
|
#endif
|
|
#ifdef SIGCHLD
|
|
{SIGCHLD, "SIGCHLD", "Child status has changed"},
|
|
#endif
|
|
#ifdef SIGCLD
|
|
{SIGCLD, "SIGCLD ", "Child status has changed"},
|
|
#endif
|
|
#ifdef SIGCONT
|
|
{SIGCONT, "SIGCONT", "Continue"},
|
|
#endif
|
|
#ifdef SIGSTOP
|
|
{SIGSTOP, "SIGSTOP", "Stop, unblockable"},
|
|
#endif
|
|
#ifdef SIGTSTP
|
|
{SIGTSTP, "SIGTSTP", "Keyboard stop"},
|
|
#endif
|
|
#ifdef SIGTTIN
|
|
{SIGTTIN, "SIGTTIN", "Background read from tty"},
|
|
#endif
|
|
#ifdef SIGTTOU
|
|
{SIGTTOU, "SIGTTOU", "Background write to tty"},
|
|
#endif
|
|
#ifdef SIGURG
|
|
{SIGURG, "SIGURG ", "Urgent condition on socket"},
|
|
#endif
|
|
#ifdef SIGXCPU
|
|
{SIGXCPU, "SIGXCPU", "CPU limit exceeded"},
|
|
#endif
|
|
#ifdef SIGXFSZ
|
|
{SIGXFSZ, "SIGXFSZ", "File size limit exceeded"},
|
|
#endif
|
|
#ifdef SIGVTALRM
|
|
{SIGVTALRM, "SIGVTALRM", "Virtual alarm clock"},
|
|
#endif
|
|
#ifdef SIGPROF
|
|
{SIGPROF, "SIGPROF", "Profiling alarm clock"},
|
|
#endif
|
|
#ifdef SIGWINCH
|
|
{SIGWINCH, "SIGWINCH", "Window size change"},
|
|
#endif
|
|
#ifdef SIGPOLL
|
|
{SIGPOLL, "SIGPOLL", "Pollable event occurred"},
|
|
#endif
|
|
#ifdef SIGIO
|
|
{SIGIO, "SIGIO", "I/O now possible"},
|
|
#endif
|
|
#ifdef SIGPWR
|
|
{SIGPWR, "SIGPWR", "Power failure restart"},
|
|
#endif
|
|
#ifdef SIGSYS
|
|
{SIGSYS, "SIGSYS", "Bad system call"},
|
|
#endif
|
|
};
|
|
static const char *
|
|
signal_name(int signo, const char ** sigdescription)
|
|
{
|
|
int j;
|
|
for (j=0; j < DIMOF(signal_info); ++j) {
|
|
if (signal_info[j].signo == signo) {
|
|
if (sigdescription) {
|
|
*sigdescription = signal_info[j].sigwords;
|
|
}
|
|
return signal_info[j].sigdefine;
|
|
}
|
|
}
|
|
if (sigdescription) {
|
|
*sigdescription = NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* returns TRUE if 'pid' was registered */
|
|
int
|
|
ReportProcHasDied(int pid, int status)
|
|
{
|
|
ProcTrack* p;
|
|
int signo=0;
|
|
int deathbyexit=0;
|
|
int deathbysig=0;
|
|
int exitcode=0;
|
|
int doreport = 0;
|
|
int debugreporting = 0;
|
|
const char * type;
|
|
ProcTrackLogType level;
|
|
#ifdef WCOREDUMP
|
|
int didcoredump = 0;
|
|
#endif
|
|
if ((p = GetProcInfo(pid)) == NULL) {
|
|
if (DEBUGPROCTRACK) {
|
|
cl_log(LOG_DEBUG
|
|
, "Process %d died (%d) but is not tracked."
|
|
, pid, status);
|
|
}
|
|
type = "untracked process";
|
|
level = PT_LOGNONE;
|
|
}else{
|
|
type = p->ops->proctype(p);
|
|
level = p->loglevel;
|
|
}
|
|
|
|
if (WIFEXITED(status)) {
|
|
deathbyexit=1;
|
|
exitcode = WEXITSTATUS(status);
|
|
}else if (WIFSIGNALED(status)) {
|
|
deathbysig=1;
|
|
signo = WTERMSIG(status);
|
|
doreport=1;
|
|
}
|
|
switch(level) {
|
|
case PT_LOGVERBOSE: doreport=1;
|
|
break;
|
|
|
|
case PT_LOGNONE: doreport = 0;
|
|
break;
|
|
|
|
case PT_LOGNORMAL: break;
|
|
}
|
|
|
|
if (!LoggingIsEnabled) {
|
|
doreport = 0;
|
|
}
|
|
#ifdef WCOREDUMP
|
|
if (WCOREDUMP(status)) {
|
|
/* Force a report on all core dumping processes */
|
|
didcoredump=1;
|
|
doreport=1;
|
|
}
|
|
#endif
|
|
if (DEBUGPROCTRACK && !doreport) {
|
|
doreport = 1;
|
|
debugreporting = 1;
|
|
}
|
|
|
|
if (doreport) {
|
|
if (deathbyexit) {
|
|
cl_log((exitcode == 0 ? LOG_INFO : LOG_WARNING)
|
|
, "Managed %s process %d exited with return code %d."
|
|
, type, pid, exitcode);
|
|
}else if (deathbysig) {
|
|
const char * signame = NULL;
|
|
const char * sigwords = NULL;
|
|
int logtype;
|
|
signame = signal_name(signo, &sigwords);
|
|
logtype = (debugreporting ? LOG_INFO : LOG_WARNING);
|
|
/*
|
|
* Processes being killed isn't an error if
|
|
* we're only logging because of debugging.
|
|
*/
|
|
if (signame && sigwords) {
|
|
cl_log(logtype
|
|
, "Managed %s process %d killed by"
|
|
" signal %d [%s - %s]."
|
|
, type, pid, signo
|
|
, signame, sigwords);
|
|
}else{
|
|
cl_log(logtype
|
|
, "Managed %s process %d killed by signal %d."
|
|
, type, pid, signo);
|
|
}
|
|
}else{
|
|
cl_log(LOG_ERR, "Managed %s process %d went away"
|
|
" strangely (!)"
|
|
, type, pid);
|
|
}
|
|
}
|
|
#ifdef WCOREDUMP
|
|
if (didcoredump) {
|
|
/* We report ALL core dumps without exception */
|
|
cl_log(LOG_ERR, "Managed %s process %d dumped core"
|
|
, type, pid);
|
|
}
|
|
#endif
|
|
|
|
if (p) {
|
|
RemoveTrackedProcTimeouts(pid);
|
|
/*
|
|
* From clplumbing/proctrack.h:
|
|
* (ProcTrack* p, int status, int signo, int exitcode
|
|
* , int waslogged);
|
|
*/
|
|
p->ops->procdied(p, status, signo, exitcode, doreport);
|
|
if (p->privatedata) {
|
|
/* They may have forgotten to free something... */
|
|
cl_log(LOG_ERR, "Managed %s process %d did not"
|
|
" clean up private data!"
|
|
, type, pid);
|
|
}
|
|
g_hash_table_remove(ProcessTable, GINT_TO_POINTER(pid));
|
|
g_free(p);
|
|
}
|
|
|
|
return doreport;
|
|
}
|
|
|
|
/* Return information associated with the given PID (or NULL) */
|
|
ProcTrack*
|
|
GetProcInfo(pid_t pid)
|
|
{
|
|
return (ProcessTable
|
|
? g_hash_table_lookup(ProcessTable, GINT_TO_POINTER(pid))
|
|
: NULL);
|
|
}
|
|
|
|
/* "info" is 0-terminated (terminated by a 0 signal) */
|
|
int
|
|
SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info)
|
|
{
|
|
long mstimeout;
|
|
ProcTrack* pinfo;
|
|
pinfo = GetProcInfo(pid);
|
|
|
|
if (pinfo == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
pinfo->timeoutseq = 0;
|
|
pinfo->killinfo = info;
|
|
mstimeout = pinfo->killinfo[0].mstimeout;
|
|
pinfo->timerid = Gmain_timeout_add(mstimeout
|
|
, TrackedProcTimeoutFunction
|
|
, GINT_TO_POINTER(pid));
|
|
return pinfo->timerid;
|
|
}
|
|
|
|
void
|
|
RemoveTrackedProcTimeouts(pid_t pid)
|
|
{
|
|
ProcTrack* pinfo;
|
|
pinfo = GetProcInfo(pid);
|
|
|
|
if (pinfo == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pinfo->killinfo && pinfo->timerid) {
|
|
Gmain_timeout_remove(pinfo->timerid);
|
|
}
|
|
pinfo->killinfo = NULL;
|
|
pinfo->timerid = 0;
|
|
}
|
|
|
|
static gboolean
|
|
TrackedProcTimeoutFunction(gpointer p)
|
|
{
|
|
/* This is safe - Pids are relatively small ints */
|
|
pid_t pid = POINTER_TO_SIZE_T(p); /*pointer cast as int*/
|
|
ProcTrack* pinfo;
|
|
int nsig;
|
|
long mstimeout;
|
|
int hadprivs;
|
|
|
|
pinfo = GetProcInfo(pid);
|
|
|
|
if (pinfo == NULL) {
|
|
cl_log(LOG_ERR, "%s: bad pinfo in call (pid %d)", __FUNCTION__, pid);
|
|
return FALSE;
|
|
}
|
|
if (pinfo->timeoutseq < 0 || pinfo->killinfo == NULL) {
|
|
cl_log(LOG_ERR
|
|
, "%s: bad call (pid %d): killinfo (%d, 0x%lx)"
|
|
, __FUNCTION__, pid
|
|
, pinfo->timeoutseq
|
|
, (unsigned long)POINTER_TO_SIZE_T(pinfo->killinfo));
|
|
return FALSE;
|
|
}
|
|
|
|
pinfo->timerid = 0;
|
|
nsig = pinfo->killinfo[pinfo->timeoutseq].signalno;
|
|
|
|
if (nsig == 0) {
|
|
if (CL_PID_EXISTS(pid)) {
|
|
cl_log(LOG_ERR
|
|
, "%s: %s process (PID %d) will not die!"
|
|
, __FUNCTION__
|
|
, pinfo->ops->proctype(pinfo)
|
|
, (int)pid);
|
|
}
|
|
return FALSE;
|
|
}
|
|
pinfo->timeoutseq++;
|
|
cl_log(LOG_WARNING, "%s process (PID %d) timed out (try %d)"
|
|
". Killing with signal %s (%d)."
|
|
, pinfo->ops->proctype(pinfo), (int)pid
|
|
, pinfo->timeoutseq
|
|
, signal_name(nsig, NULL)
|
|
, nsig);
|
|
|
|
if (pinfo->isapgrp && nsig > 0) {
|
|
pid = -pid;
|
|
}
|
|
|
|
if (!(hadprivs = cl_have_full_privs())) {
|
|
return_to_orig_privs();
|
|
}
|
|
if (kill(pid, nsig) < 0) {
|
|
if (errno == ESRCH) {
|
|
/* Mission accomplished! */
|
|
cl_log(LOG_INFO, "%s process (PID %d) died before killing (try %d)"
|
|
, pinfo->ops->proctype(pinfo), (int)pid
|
|
, pinfo->timeoutseq);
|
|
return FALSE;
|
|
}else{
|
|
cl_perror("%s: kill(%d,%d) failed"
|
|
, __FUNCTION__, pid, nsig);
|
|
}
|
|
}
|
|
if (!hadprivs) {
|
|
return_to_dropped_privs();
|
|
}
|
|
mstimeout = pinfo->killinfo[pinfo->timeoutseq].mstimeout;
|
|
pinfo->timerid = Gmain_timeout_add(mstimeout
|
|
, TrackedProcTimeoutFunction
|
|
, p);
|
|
if (pinfo->timerid <= 0) {
|
|
cl_log(LOG_ERR, "%s: Could not add new kill timer [%u]"
|
|
, __FUNCTION__, pinfo->timerid);
|
|
kill(pid, SIGKILL);
|
|
}
|
|
if (debugproctrack) {
|
|
cl_log(LOG_DEBUG, "%s process (PID %d) scheduled to be killed again"
|
|
" (try %d) in %ld ms [timerid %u]"
|
|
, pinfo->ops->proctype(pinfo), (int)pid
|
|
, pinfo->timeoutseq
|
|
, mstimeout
|
|
, pinfo->timerid);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Helper struct to allow us to stuff 3 args into one ;-) */
|
|
struct prochelper {
|
|
ProcTrack_ops* type;
|
|
ProcTrackFun fun;
|
|
void* data;
|
|
};
|
|
|
|
/* Helper function to call user's function with right args... */
|
|
static void
|
|
ForEachProcHelper(gpointer key, gpointer value, void * helper)
|
|
{
|
|
ProcTrack* p = value;
|
|
struct prochelper* ph = helper;
|
|
|
|
if (ph->type != NULL && ph->type != p->ops) {
|
|
return;
|
|
}
|
|
|
|
ph->fun(p, ph->data);
|
|
}
|
|
/*
|
|
* Iterate over the set of tracked processes.
|
|
* If proctype is NULL, then walk through them all, otherwise only those
|
|
* of the given type.
|
|
*/
|
|
void
|
|
ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data)
|
|
{
|
|
struct prochelper ph;
|
|
|
|
InitProcTable();
|
|
ph.fun = f;
|
|
ph.type = proctype;
|
|
ph.data = data;
|
|
g_hash_table_foreach(ProcessTable, ForEachProcHelper, &ph);
|
|
}
|
|
|
|
void
|
|
DisableProcLogging()
|
|
{
|
|
LoggingIsEnabled = 0;
|
|
}
|
|
|
|
void
|
|
EnableProcLogging()
|
|
{
|
|
LoggingIsEnabled = 1;
|
|
}
|