/*
 * 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;
}

