summaryrefslogtreecommitdiffstats
path: root/lib/clplumbing/proctrack.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/clplumbing/proctrack.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/lib/clplumbing/proctrack.c b/lib/clplumbing/proctrack.c
new file mode 100644
index 0000000..f6a9df2
--- /dev/null
+++ b/lib/clplumbing/proctrack.c
@@ -0,0 +1,515 @@
+/*
+ * 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 <sys/types.h>
+#include <signal.h>
+#include <time.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;
+}