blob: a6ae107fbdfc87d425b8d979062793b3876489ae [file] [log] [blame]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "config.h"
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include "monit.h"
#include "alert.h"
#include "event.h"
#include "process.h"
/**
* Implementation of the event interface.
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
EventTable_T Event_Table[]= {
{Event_Action, "Action done", "Action done", "Action done", "Action done"},
{Event_Checksum, "Checksum failed", "Checksum succeeded", "Checksum changed", "Checksum not changed"},
{Event_Connection, "Connection failed", "Connection succeeded", "Connection changed", "Connection not changed"},
{Event_Content, "Content failed", "Content succeeded", "Content match", "Content doesn't match"},
{Event_Data, "Data access error", "Data access succeeded", "Data access changed", "Data access not changed"},
{Event_Exec, "Execution failed", "Execution succeeded", "Execution changed", "Execution not changed"},
{Event_Fsflag, "Filesystem flags failed", "Filesystem flags succeeded", "Filesystem flags changed", "Filesystem flags not changed"},
{Event_Gid, "GID failed", "GID succeeded", "GID changed", "GID not changed"},
{Event_Heartbeat, "Heartbeat failed", "Heartbeat succeeded", "Heartbeat changed", "Heartbeat not changed"},
{Event_Icmp, "ICMP failed", "ICMP succeeded", "ICMP changed", "ICMP not changed"},
{Event_Instance, "Monit instance failed", "Monit instance succeeded", "Monit instance changed", "Monit instance not changed"},
{Event_Invalid, "Invalid type", "Type succeeded", "Type changed", "Type not changed"},
{Event_Nonexist, "Does not exist", "Exists", "Existence changed", "Existence not changed"},
{Event_Permission, "Permission failed", "Permission succeeded", "Permission changed", "Permission not changed"},
{Event_Pid, "PID failed", "PID succeeded", "PID changed", "PID not changed"},
{Event_PPid, "PPID failed", "PPID succeeded", "PPID changed", "PPID not changed"},
{Event_Resource, "Resource limit matched", "Resource limit succeeded", "Resource limit changed", "Resource limit not changed"},
{Event_Size, "Size failed", "Size succeeded", "Size changed", "Size not changed"},
{Event_Status, "Status failed", "Status succeeded", "Status changed", "Status not changed"},
{Event_Timeout, "Timeout", "Timeout recovery", "Timeout changed", "Timeout not changed"},
{Event_Timestamp, "Timestamp failed", "Timestamp succeeded", "Timestamp changed", "Timestamp not changed"},
{Event_Uid, "UID failed", "UID succeeded", "UID changed", "UID not changed"},
{Event_Uptime, "Uptime failed", "Uptime succeeded", "Uptime changed", "Uptime not changed"},
/* Virtual events */
{Event_Null, "No Event", "No Event", "No Event", "No Event"}
};
/* -------------------------------------------------------------- Prototypes */
static void handle_event(Event_T);
static void handle_action(Event_T, Action_T);
static void Event_queue_add(Event_T);
static void Event_queue_update(Event_T, const char *);
/* ------------------------------------------------------------------ Public */
/**
* Post a new Event
* @param service The Service the event belongs to
* @param id The event identification
* @param state The event state
* @param action Description of the event action
* @param s Optional message describing the event
*/
void Event_post(Service_T service, long id, short state, EventAction_T action, char *s, ...) {
Event_T e;
ASSERT(service);
ASSERT(action);
ASSERT(state == STATE_FAILED || state == STATE_SUCCEEDED || state == STATE_CHANGED || state == STATE_CHANGEDNOT);
if ((e = service->eventlist) == NULL) {
/* Only first failed/changed event can initialize the queue for given event type,
* thus succeeded events are ignored until first error. */
if (state == STATE_SUCCEEDED || state == STATE_CHANGEDNOT)
return;
/* Initialize event list and add first event. The manadatory informations
* are cloned so the event is as standalone as possible and may be saved
* to the queue without the dependency on the original service, thus
* persistent and managable across monit restarts */
NEW(e);
e->id = id;
gettimeofday(&e->collected, NULL);
e->source = Str_dup(service->name);
e->mode = service->mode;
e->type = service->type;
e->state = STATE_INIT;
e->state_map = 1;
e->action = action;
if (s) {
va_list ap;
va_start(ap, s);
e->message = Str_vcat(s, ap);
va_end(ap);
}
service->eventlist = e;
} else {
/* Try to find the event with the same origin and type identification.
* Each service and each test have its own custom actions object, so
* we share actions object address to identify event source. */
do {
if (e->action == action && e->id == id) {
gettimeofday(&e->collected, NULL);
/* Shift the existing event flags to the left
* and set the first bit based on actual state */
e->state_map <<= 1;
e->state_map |= ((state == STATE_SUCCEEDED || state == STATE_CHANGEDNOT) ? 0 : 1);
/* Update the message */
if (s) {
va_list ap;
FREE(e->message);
va_start(ap, s);
e->message = Str_vcat(s, ap);
va_end(ap);
}
break;
}
e = e->next;
} while (e);
if (!e) {
/* Only first failed/changed event can initialize the queue for given event type,
* thus succeeded events are ignored until first error. */
if (state == STATE_SUCCEEDED || state == STATE_CHANGEDNOT)
return;
/* Event was not found in the pending events list, we will add it.
* The manadatory informations are cloned so the event is as standalone
* as possible and may be saved to the queue without the dependency on
* the original service, thus persistent and managable across monit
* restarts */
NEW(e);
e->id = id;
gettimeofday(&e->collected, NULL);
e->source = Str_dup(service->name);
e->mode = service->mode;
e->type = service->type;
e->state = STATE_INIT;
e->state_map = 1;
e->action = action;
if (s) {
va_list ap;
va_start(ap, s);
e->message = Str_vcat(s, ap);
va_end(ap);
}
e->next = service->eventlist;
service->eventlist = e;
}
}
e->state_changed = Event_check_state(e, state);
/* In the case that the state changed, update it and reset the counter */
if (e->state_changed) {
e->state = state;
e->count = 1;
} else
e->count++;
handle_event(e);
}
/* -------------------------------------------------------------- Properties */
/**
* Get the Service where the event orginated
* @param E An event object
* @return The Service where the event orginated
*/
Service_T Event_get_source(Event_T E) {
Service_T s = NULL;
ASSERT(E);
if (!(s = Util_getService(E->source)))
LogError("Service %s not found in monit configuration\n", E->source);
return s;
}
/**
* Get the Service name where the event orginated
* @param E An event object
* @return The Service name where the event orginated
*/
char *Event_get_source_name(Event_T E) {
ASSERT(E);
return (E->source);
}
/**
* Get the service type of the service where the event orginated
* @param E An event object
* @return The service type of the service where the event orginated
*/
int Event_get_source_type(Event_T E) {
ASSERT(E);
return (E->type);
}
/**
* Get the Event timestamp
* @param E An event object
* @return The Event timestamp
*/
struct timeval *Event_get_collected(Event_T E) {
ASSERT(E);
return &E->collected;
}
/**
* Get the Event raw state
* @param E An event object
* @return The Event raw state
*/
short Event_get_state(Event_T E) {
ASSERT(E);
return E->state;
}
/**
* Return the actual event state based on event state bitmap
* and event ratio needed to trigger the state change
* @param E An event object
* @param S Actual posted state
* @return The Event raw state
*/
short Event_check_state(Event_T E, short S) {
int i;
int count = 0;
short state = (S == STATE_SUCCEEDED || S == STATE_CHANGEDNOT) ? 0 : 1; /* translate to 0/1 class */
Action_T action;
Service_T service;
long long flag;
ASSERT(E);
if (!(service = Event_get_source(E)))
return TRUE;
/* Only true failed/changed state condition can change the initial state */
if (!state && E->state == STATE_INIT && !(service->error & E->id))
return FALSE;
action = !state ? E->action->succeeded : E->action->failed;
/* Compare as many bits as cycles able to trigger the action */
for (i = 0; i < action->cycles; i++) {
/* Check the state of the particular cycle given by the bit position */
flag = (E->state_map >> i) & 0x1;
/* Count occurences of the posted state */
if (flag == state)
count++;
}
/* the internal instance and action events are handled as changed any time since we need to deliver alert whenever it occurs */
if (E->id == Event_Instance || E->id == Event_Action || (count >= action->count && (S != E->state || S == STATE_CHANGED)))
return TRUE;
return FALSE;
}
/**
* Get the Event type
* @param E An event object
* @return The Event type
*/
long Event_get_id(Event_T E) {
ASSERT(E);
return E->id;
}
/**
* Get the optionally Event message describing why the event was
* fired.
* @param E An event object
* @return The Event message. May be NULL
*/
const char *Event_get_message(Event_T E) {
ASSERT(E);
return E->message;
}
/**
* Get a textual description of actual event type.
* @param E An event object
* @return A string describing the event type in clear text. If the
* event type is not found NULL is returned.
*/
const char *Event_get_description(Event_T E) {
EventTable_T *et= Event_Table;
ASSERT(E);
while ((*et).id) {
if (E->id == (*et).id) {
switch (E->state) {
case STATE_SUCCEEDED:
return (*et).description_succeeded;
case STATE_FAILED:
return (*et).description_failed;
case STATE_INIT:
return (*et).description_failed;
case STATE_CHANGED:
return (*et).description_changed;
case STATE_CHANGEDNOT:
return (*et).description_changednot;
default:
break;
}
}
et++;
}
return NULL;
}
/**
* Get an event action id.
* @param E An event object
* @return An action id
*/
short Event_get_action(Event_T E) {
Action_T A = NULL;
ASSERT(E);
switch (E->state) {
case STATE_SUCCEEDED:
case STATE_CHANGEDNOT:
A = E->action->succeeded;
break;
case STATE_FAILED:
case STATE_CHANGED:
case STATE_INIT:
A = E->action->failed;
break;
default:
break;
}
if (! A)
return ACTION_IGNORE;
/* In the case of passive mode we replace the description of start, stop
* or restart action for alert action, because these actions are passive in
* this mode */
return (E->mode == MODE_PASSIVE && ((A->id == ACTION_START) || (A->id == ACTION_STOP) || (A->id == ACTION_RESTART))) ? ACTION_ALERT : A->id;
}
/**
* Get a textual description of actual event action. For instance if the
* event type is possitive Event_Nonexist, the textual description of
* failed state related action is "restart". Likewise if the event type is
* negative Event_Checksumthe textual description of recovery related action
* is "alert" and so on.
* @param E An event object
* @return A string describing the event type in clear text. If the
* event type is not found NULL is returned.
*/
const char *Event_get_action_description(Event_T E) {
ASSERT(E);
return actionnames[Event_get_action(E)];
}
/**
* Reprocess the partially handled event queue
*/
void Event_queue_process() {
DIR *dir = NULL;
FILE *file = NULL;
struct dirent *de = NULL;
EventAction_T ea = NULL;
Action_T a = NULL;
/* return in the case that the eventqueue is not enabled or empty */
if (! Run.eventlist_dir || (! Run.handler_init && ! Run.handler_queue[HANDLER_ALERT] && ! Run.handler_queue[HANDLER_MMONIT]))
return;
if (! (dir = opendir(Run.eventlist_dir)) ) {
if (errno != ENOENT)
LogError("%s: cannot open the directory %s -- %s\n", prog, Run.eventlist_dir, STRERROR);
return;
}
if ((de = readdir(dir)))
DEBUG("Processing postponed events queue\n");
NEW(ea);
NEW(a);
while (de) {
size_t size;
int handlers_passed = 0;
int *version = NULL;
short *action = NULL;
Event_T e = NULL;
struct stat st;
char file_name[STRLEN];
/* In the case that all handlers failed, skip the further processing in
* this cycle. Alert handler is currently defined anytime (either
* explicitly or localhost by default) */
if ( (Run.mmonits && FLAG(Run.handler_flag, HANDLER_MMONIT) && FLAG(Run.handler_flag, HANDLER_ALERT)) || FLAG(Run.handler_flag, HANDLER_ALERT))
break;
snprintf(file_name, STRLEN, "%s/%s", Run.eventlist_dir, de->d_name);
if (!stat(file_name, &st) && S_ISREG(st.st_mode)) {
DEBUG("%s: processing queued event %s\n", prog, file_name);
if (! (file = fopen(file_name, "r")) ) {
LogError("%s: queued event processing failed - cannot open the file %s -- %s\n", prog, file_name, STRERROR);
goto error1;
}
/* read event structure version */
if (!(version = file_readQueue(file, &size))) {
LogError("skipping queued event %s - unknown data format\n", file_name);
goto error2;
}
if (size != sizeof(int)) {
LogError("Aborting queued event %s - invalid size %d\n", file_name, size);
goto error3;
}
if (*version != EVENT_VERSION) {
LogError("Aborting queued event %s - incompatible data format version %d\n", file_name, *version);
goto error3;
}
/* read event structure */
if (!(e = file_readQueue(file, &size)))
goto error3;
if (size != sizeof(*e))
goto error4;
/* read source */
if (!(e->source = file_readQueue(file, &size)))
goto error4;
/* read message */
if (!(e->message = file_readQueue(file, &size)))
goto error5;
/* read event action */
if (!(action = file_readQueue(file, &size)))
goto error6;
if (size != sizeof(short))
goto error7;
a->id = *action;
if (e->state == STATE_FAILED)
ea->failed = a;
else
ea->succeeded = a;
e->action = ea;
/* Retry all remaining handlers */
/* alert */
if (e->flag & HANDLER_ALERT) {
if (Run.handler_init)
Run.handler_queue[HANDLER_ALERT]++;
if ((Run.handler_flag & HANDLER_ALERT) != HANDLER_ALERT) {
if ( handle_alert(e) != HANDLER_ALERT ) {
e->flag &= ~HANDLER_ALERT;
Run.handler_queue[HANDLER_ALERT]--;
handlers_passed++;
} else {
LogError("Alert handler failed, retry scheduled for next cycle\n");
Run.handler_flag |= HANDLER_ALERT;
}
}
}
/* mmonit */
if (e->flag & HANDLER_MMONIT) {
if (Run.handler_init)
Run.handler_queue[HANDLER_MMONIT]++;
if ((Run.handler_flag & HANDLER_MMONIT) != HANDLER_MMONIT) {
if ( handle_mmonit(e) != HANDLER_MMONIT ) {
e->flag &= ~HANDLER_MMONIT;
Run.handler_queue[HANDLER_MMONIT]--;
handlers_passed++;
} else {
LogError("M/Monit handler failed, retry scheduled for next cycle\n");
Run.handler_flag |= HANDLER_MMONIT;
}
}
}
/* If no error persists, remove it from the queue */
if (e->flag == HANDLER_SUCCEEDED) {
DEBUG("Removing queued event %s\n", file_name);
if (unlink(file_name) < 0)
LogError("Failed to remove queued event file '%s' -- %s\n", file_name, STRERROR);
} else if (handlers_passed > 0) {
DEBUG("Updating queued event %s (some handlers passed)\n", file_name);
Event_queue_update(e, file_name);
}
error7:
FREE(action);
error6:
FREE(e->message);
error5:
FREE(e->source);
error4:
FREE(e);
error3:
FREE(version);
error2:
fclose(file);
}
error1:
de = readdir(dir);
}
Run.handler_init = FALSE;
closedir(dir);
FREE(a);
FREE(ea);
return;
}
/* ----------------------------------------------------------------- Private */
/*
* Handle the event
* @param E An event
*/
static void handle_event(Event_T E) {
Service_T S;
ASSERT(E);
ASSERT(E->action);
ASSERT(E->action->failed);
ASSERT(E->action->succeeded);
/* We will handle only first succeeded event, recurrent succeeded events
* or insufficient succeeded events during failed service state are
* ignored. Failed events are handled each time. */
if (!E->state_changed && (E->state == STATE_SUCCEEDED || E->state == STATE_CHANGEDNOT || ((E->state_map & 0x1) ^ 0x1)))
return;
S = Event_get_source(E);
if (!S) {
LogError("Event handling aborted\n");
return;
}
if (E->message) {
/* In the case that the service state is initializing yet and error
* occured, log it and exit. Succeeded events in init state are not
* logged. Instance and action events are logged always with priority
* info. */
if (E->state != STATE_INIT || E->state_map & 0x1) {
if (E->state == STATE_SUCCEEDED || E->state == STATE_CHANGEDNOT || E->id == Event_Instance || E->id == Event_Action)
LogInfo("'%s' %s\n", S->name, E->message);
else
LogError("'%s' %s\n", S->name, E->message);
}
if (E->state == STATE_INIT)
return;
}
if (E->state == STATE_FAILED || E->state == STATE_CHANGED) {
if (E->id != Event_Instance && E->id != Event_Action) { // We are not interested in setting error flag for instance and action events
S->error |= E->id;
/* The error hint provides second dimension for error bitmap and differentiates between failed/changed event states (failed=0, chaged=1) */
if (E->state == STATE_CHANGED)
S->error_hint |= E->id;
else
S->error_hint &= ~E->id;
}
handle_action(E, E->action->failed);
} else {
S->error &= ~E->id;
handle_action(E, E->action->succeeded);
}
/* Possible event state change was handled so we will reset the flag. */
E->state_changed = FALSE;
}
static void handle_action(Event_T E, Action_T A) {
Service_T s;
ASSERT(E);
ASSERT(A);
E->flag = HANDLER_SUCCEEDED;
if (A->id == ACTION_IGNORE)
return;
/* Alert and mmonit event notification are common actions */
E->flag |= handle_mmonit(E);
E->flag |= handle_alert(E);
/* In the case that some subhandler failed, enqueue the event for
* partial reprocessing */
if (E->flag != HANDLER_SUCCEEDED) {
if (Run.eventlist_dir)
Event_queue_add(E);
else
LogError("Aborting event\n");
}
if (!(s = Event_get_source(E))) {
LogError("Event action handling aborted\n");
return;
}
/* Action event is handled already. For Instance events
* we don't want actions like stop to be executed
* to prevent the disabling of system service monitoring */
if (A->id == ACTION_ALERT || E->id == Event_Instance) {
return;
} else if (A->id == ACTION_EXEC) {
LogInfo("'%s' exec: %s\n", s->name, A->exec->arg[0]);
spawn(s, A->exec, E);
return;
} else {
if (s->actionratelist && (A->id == ACTION_START || A->id == ACTION_RESTART))
s->nstart++;
if (s->mode == MODE_PASSIVE && (A->id == ACTION_START || A->id == ACTION_STOP || A->id == ACTION_RESTART))
return;
control_service(s->name, A->id);
}
}
/**
* Add the partialy handled event to the global queue
* @param E An event object
*/
static void Event_queue_add(Event_T E) {
FILE *file = NULL;
char file_name[STRLEN];
int version = EVENT_VERSION;
short action = Event_get_action(E);
int rv = FALSE;
mode_t mask;
ASSERT(E);
ASSERT(E->flag != HANDLER_SUCCEEDED);
if (!file_checkQueueDirectory(Run.eventlist_dir, 0700)) {
LogError("%s: Aborting event - cannot access the directory %s\n", prog, Run.eventlist_dir);
return;
}
if (!file_checkQueueLimit(Run.eventlist_dir, Run.eventlist_slots)) {
LogError("%s: Aborting event - queue over quota\n", prog);
return;
}
/* compose the file name of actual timestamp and service name */
snprintf(file_name, STRLEN, "%s/%ld_%lx", Run.eventlist_dir, (long int)time(NULL), (long unsigned)E->source);
DEBUG("%s: Adding event to the queue file %s for later delivery\n", prog, file_name);
mask = umask(QUEUEMASK);
file = fopen(file_name, "w");
umask(mask);
if (! file) {
LogError("%s: Aborting event - cannot open the event file %s -- %s\n", prog, file_name, STRERROR);
return;
}
/* write event structure version */
if (!(rv = file_writeQueue(file, &version, sizeof(int))))
goto error;
/* write event structure */
if (!(rv = file_writeQueue(file, E, sizeof(*E))))
goto error;
/* write source */
if (!(rv = file_writeQueue(file, E->source, E->source ? strlen(E->source)+1 : 0)))
goto error;
/* write message */
if (!(rv = file_writeQueue(file, E->message, E->message ? strlen(E->message)+1 : 0)))
goto error;
/* write event action */
if (!(rv = file_writeQueue(file, &action, sizeof(short))))
goto error;
error:
fclose(file);
if (!rv) {
LogError("%s: Aborting event - unable to save event information to %s\n", prog, file_name);
if (unlink(file_name) < 0)
LogError("Failed to remove event file '%s' -- %s\n", file_name, STRERROR);
} else {
if (!Run.handler_init && E->flag & HANDLER_ALERT)
Run.handler_queue[HANDLER_ALERT]++;
if (!Run.handler_init && E->flag & HANDLER_MMONIT)
Run.handler_queue[HANDLER_MMONIT]++;
}
return;
}
/**
* Update the partialy handled event in the global queue
* @param E An event object
* @param file_name File name
*/
static void Event_queue_update(Event_T E, const char *file_name) {
FILE *file = NULL;
int version = EVENT_VERSION;
short action = Event_get_action(E);
int rv = FALSE;
mode_t mask;
ASSERT(E);
ASSERT(E->flag != HANDLER_SUCCEEDED);
if (!file_checkQueueDirectory(Run.eventlist_dir, 0700)) {
LogError("%s: Aborting event - cannot access the directory %s\n", prog, Run.eventlist_dir);
return;
}
DEBUG("%s: Updating event in the queue file %s for later delivery\n", prog, file_name);
mask = umask(QUEUEMASK);
file = fopen(file_name, "w");
umask(mask);
if (! file)
{
LogError("%s: Aborting event - cannot open the event file %s -- %s\n", prog, file_name, STRERROR);
return;
}
/* write event structure version */
if (!(rv = file_writeQueue(file, &version, sizeof(int))))
goto error;
/* write event structure */
if (!(rv = file_writeQueue(file, E, sizeof(*E))))
goto error;
/* write source */
if (!(rv = file_writeQueue(file, E->source, E->source ? strlen(E->source)+1 : 0)))
goto error;
/* write message */
if (!(rv = file_writeQueue(file, E->message, E->message ? strlen(E->message)+1 : 0)))
goto error;
/* write event action */
if (!(rv = file_writeQueue(file, &action, sizeof(short))))
goto error;
error:
fclose(file);
if (!rv) {
LogError("%s: Aborting event - unable to update event information to %s\n", prog, file_name);
if (unlink(file_name) < 0)
LogError("Failed to remove event file '%s' -- %s\n", file_name, STRERROR);
}
return;
}