#include "lib/evloop/procmon.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/log/win32err.h"
#include "lib/malloc/malloc.h"
#include "lib/string/parse_int.h"
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#endif
#if (0 == SIZEOF_PID_T) && defined(_WIN32)
typedef int pid_t;
#define PID_T_FORMAT "%d"
#elif (SIZEOF_PID_T == SIZEOF_INT) || (SIZEOF_PID_T == SIZEOF_SHORT)
#define PID_T_FORMAT "%d"
#elif (SIZEOF_PID_T == SIZEOF_LONG)
#define PID_T_FORMAT "%ld"
#elif (SIZEOF_PID_T == 8)
#define PID_T_FORMAT "%"PRId64
#else
#error Unknown: SIZEOF_PID_T
#endif
#define PROCMON_POLLS 1
#ifdef PROCMON_POLLS
static void tor_process_monitor_poll_cb(periodic_timer_t *ev,
void *procmon_);
#endif
struct parsed_process_specifier_t {
pid_t pid;
};
static int
parse_process_specifier(const char *process_spec,
struct parsed_process_specifier_t *ppspec,
const char **msg)
{
long pid_l;
int pid_ok = 0;
char *pspec_next;
pid_l = tor_parse_long(process_spec, 10, 1, LONG_MAX, &pid_ok, &pspec_next);
if ((*pspec_next != 0) && (*pspec_next != ' ') && (*pspec_next != ':')) {
pid_ok = 0;
}
ppspec->pid = (pid_t)(pid_l);
if (!pid_ok || (pid_l != (long)(ppspec->pid))) {
*msg = "invalid PID";
goto err;
}
return 0;
err:
return -1;
}
struct tor_process_monitor_t {
log_domain_mask_t log_domain;
pid_t pid;
#ifdef _WIN32
int poll_hproc;
HANDLE hproc;
#endif
periodic_timer_t *e;
tor_procmon_callback_t cb;
void *cb_arg;
};
int
tor_validate_process_specifier(const char *process_spec,
const char **msg)
{
struct parsed_process_specifier_t ppspec;
tor_assert(msg != NULL);
*msg = NULL;
return parse_process_specifier(process_spec, &ppspec, msg);
}
static const struct timeval poll_interval_tv = {15, 0};
tor_process_monitor_t *
tor_process_monitor_new(struct event_base *base,
const char *process_spec,
log_domain_mask_t log_domain,
tor_procmon_callback_t cb, void *cb_arg,
const char **msg)
{
tor_process_monitor_t *procmon = tor_malloc_zero(
sizeof(tor_process_monitor_t));
struct parsed_process_specifier_t ppspec;
tor_assert(msg != NULL);
*msg = NULL;
if (procmon == NULL) {
*msg = "out of memory";
goto err;
}
procmon->log_domain = log_domain;
if (parse_process_specifier(process_spec, &ppspec, msg))
goto err;
procmon->pid = ppspec.pid;
#ifdef _WIN32
procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
FALSE,
procmon->pid);
if (procmon->hproc != NULL) {
procmon->poll_hproc = 1;
log_info(procmon->log_domain, "Successfully opened handle to process "
PID_T_FORMAT"; "
"monitoring it.",
procmon->pid);
} else {
log_info(procmon->log_domain, "Failed to open handle to process "
PID_T_FORMAT"; will "
"try again later.",
procmon->pid);
}
#endif
procmon->cb = cb;
procmon->cb_arg = cb_arg;
#ifdef PROCMON_POLLS
procmon->e = periodic_timer_new(base,
&poll_interval_tv,
tor_process_monitor_poll_cb, procmon);
#else
#error OOPS?
#endif
return procmon;
err:
tor_process_monitor_free(procmon);
return NULL;
}
#ifdef PROCMON_POLLS
static void
tor_process_monitor_poll_cb(periodic_timer_t *event, void *procmon_)
{
(void)event;
tor_process_monitor_t *procmon = (tor_process_monitor_t *)(procmon_);
int its_dead_jim;
tor_assert(procmon != NULL);
#ifdef _WIN32
if (procmon->poll_hproc) {
DWORD exit_code;
if (!GetExitCodeProcess(procmon->hproc, &exit_code)) {
char *errmsg = format_win32_error(GetLastError());
log_warn(procmon->log_domain, "Error \"%s\" occurred while polling "
"handle for monitored process "PID_T_FORMAT"; assuming "
"it's dead.",
errmsg, procmon->pid);
tor_free(errmsg);
its_dead_jim = 1;
} else {
its_dead_jim = (exit_code != STILL_ACTIVE);
}
} else {
procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
FALSE,
procmon->pid);
if (procmon->hproc != NULL) {
log_info(procmon->log_domain, "Successfully opened handle to monitored "
"process "PID_T_FORMAT".",
procmon->pid);
its_dead_jim = 0;
procmon->poll_hproc = 1;
} else {
DWORD err_code = GetLastError();
char *errmsg = format_win32_error(err_code);
its_dead_jim = (err_code == ERROR_INVALID_PARAMETER);
if (!its_dead_jim)
log_info(procmon->log_domain, "Failed to open handle to monitored "
"process "PID_T_FORMAT", and error code %lu (%s) is not "
"'invalid parameter' -- assuming the process is still alive.",
procmon->pid,
err_code, errmsg);
tor_free(errmsg);
}
}
#else
its_dead_jim = kill(procmon->pid, 0);
its_dead_jim = its_dead_jim && (errno == ESRCH);
#endif
tor_log(its_dead_jim ? LOG_NOTICE : LOG_INFO,
procmon->log_domain, "Monitored process "PID_T_FORMAT" is %s.",
procmon->pid,
its_dead_jim ? "dead" : "still alive");
if (its_dead_jim) {
procmon->cb(procmon->cb_arg);
}
}
#endif
void
tor_process_monitor_free_(tor_process_monitor_t *procmon)
{
if (procmon == NULL)
return;
#ifdef _WIN32
if (procmon->hproc != NULL)
CloseHandle(procmon->hproc);
#endif
if (procmon->e != NULL)
periodic_timer_free(procmon->e);
tor_free(procmon);
}