/*****************************************************************************
 *
 * EVENTS.C - Timed event functions for Icinga
 *
 * Copyright (c) 1999-2010 Ethan Galstad (egalstad@nagios.org)
 * Copyright (c) 2009-2013 Nagios Core Development Team and Community Contributors
 * Copyright (c) 2009-2015 Icinga Development Team (http://www.icinga.org)
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *****************************************************************************/

#include "../include/config.h"
#include "../include/common.h"
#include "../include/downtime.h"
#include "../include/comments.h"
#include "../include/statusdata.h"
#include "../include/icinga.h"
#include "../include/broker.h"
#include "../include/sretention.h"

extern char	*config_file;

extern int      test_scheduling;

extern time_t   program_start;
extern time_t   event_start;
extern time_t   last_command_check;

extern int      sigshutdown;
extern int      sigrestart;

extern double   sleep_time;
extern int      interval_length;
extern int      service_inter_check_delay_method;
extern int      host_inter_check_delay_method;
extern int      service_interleave_factor_method;
extern int      max_host_check_spread;
extern int      max_service_check_spread;

extern int      command_check_interval;
extern int      check_reaper_interval;
extern int      service_freshness_check_interval;
extern int      host_freshness_check_interval;
extern int      auto_rescheduling_interval;
extern int      auto_rescheduling_window;

extern int      check_external_commands;
extern int      check_orphaned_services;
extern int      check_orphaned_hosts;
extern int      check_service_freshness;
extern int      check_host_freshness;
extern int      auto_reschedule_checks;

extern int      retain_state_information;
extern int      retention_update_interval;

extern int      max_parallel_service_checks;
extern int      currently_running_service_checks;

extern int      aggregate_status_updates;
extern int      status_update_interval;

extern int      log_rotation_method;

extern int      service_check_timeout;

extern int      execute_service_checks;
extern int      execute_host_checks;

extern int      child_processes_fork_twice;

extern int      time_change_threshold;

extern time_t	disable_notifications_expire_time;

timed_event *event_list_low = NULL;
timed_event *event_list_low_tail = NULL;
timed_event *event_list_high = NULL;
timed_event *event_list_high_tail = NULL;

extern host     *host_list;
extern service  *service_list;

sched_info scheduling_info;

pthread_mutex_t icinga_eventloop_lock = PTHREAD_MUTEX_INITIALIZER;



/******************************************************************/
/************ EVENT SCHEDULING/HANDLING FUNCTIONS *****************/
/******************************************************************/

/* initialize the event timing loop before we start monitoring */
void init_timing_loop(void) {
	host *temp_host = NULL;
	service *temp_service = NULL;
	time_t current_time = 0L;
	unsigned long interval_to_use = 0L;
	int total_interleave_blocks = 0;
	int current_interleave_block = 1;
	int interleave_block_index = 0;
	int mult_factor = 0;
	int is_valid_time = 0;
	time_t next_valid_time = 0L;
	int schedule_check = 0;
	double max_inter_check_delay = 0.0;
	struct timeval tv[9];
	double runtime[9];


	log_debug_info(DEBUGL_FUNCTIONS, 0, "init_timing_loop() start\n");

	/* get the time right now */
	time(&current_time);


	/******** GET BASIC HOST/SERVICE INFO  ********/

	scheduling_info.total_services = 0;
	scheduling_info.total_scheduled_services = 0;
	scheduling_info.total_hosts = 0;
	scheduling_info.total_scheduled_hosts = 0;
	scheduling_info.average_services_per_host = 0.0;
	scheduling_info.average_scheduled_services_per_host = 0.0;
	scheduling_info.average_service_execution_time = 0.0;
	scheduling_info.service_check_interval_total = 0;
	scheduling_info.average_service_inter_check_delay = 0.0;
	scheduling_info.host_check_interval_total = 0;
	scheduling_info.average_host_inter_check_delay = 0.0;

	if (test_scheduling == TRUE)
		gettimeofday(&tv[0], NULL);

	/* get info on service checks to be scheduled */
	for (temp_service = service_list; temp_service != NULL; temp_service = temp_service->next) {

		schedule_check = TRUE;

		/* service has no check interval */
		if (temp_service->check_interval == 0)
			schedule_check = FALSE;

		/* active checks are disabled */
		if (temp_service->checks_enabled == FALSE)
			schedule_check = FALSE;

		/* are there any valid times this service can be checked? */
		is_valid_time = check_time_against_period(current_time, temp_service->check_period_ptr);
		if (is_valid_time == ERROR) {
			get_next_valid_time(current_time, &next_valid_time, temp_service->check_period_ptr);
			if (current_time == next_valid_time)
				schedule_check = FALSE;
		}

		if (schedule_check == TRUE) {

			scheduling_info.total_scheduled_services++;

			/* used later in inter-check delay calculations */
			scheduling_info.service_check_interval_total += temp_service->check_interval;

			/* calculate rolling average execution time (available from retained state information) */
			scheduling_info.average_service_execution_time = (double)(((scheduling_info.average_service_execution_time * (scheduling_info.total_scheduled_services - 1)) + temp_service->execution_time) / (double)scheduling_info.total_scheduled_services);
		} else {
			temp_service->should_be_scheduled = FALSE;

			log_debug_info(DEBUGL_EVENTS, 1, "Service '%s' on host '%s' should not be scheduled.\n", temp_service->description, temp_service->host_name);
		}

		scheduling_info.total_services++;
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[1], NULL);

	/* get info on host checks to be scheduled */
	for (temp_host = host_list; temp_host != NULL; temp_host = temp_host->next) {

		schedule_check = TRUE;

		/* host has no check interval */
		if (temp_host->check_interval == 0)
			schedule_check = FALSE;

		/* active checks are disabled */
		if (temp_host->checks_enabled == FALSE)
			schedule_check = FALSE;

		/* are there any valid times this host can be checked? */
		is_valid_time = check_time_against_period(current_time, temp_host->check_period_ptr);
		if (is_valid_time == ERROR) {
			get_next_valid_time(current_time, &next_valid_time, temp_host->check_period_ptr);
			if (current_time == next_valid_time)
				schedule_check = FALSE;
		}

		if (schedule_check == TRUE) {

			scheduling_info.total_scheduled_hosts++;

			/* this is used later in inter-check delay calculations */
			scheduling_info.host_check_interval_total += temp_host->check_interval;
		} else {
			temp_host->should_be_scheduled = FALSE;

			log_debug_info(DEBUGL_EVENTS, 1, "Host '%s' should not be scheduled.\n", temp_host->name);
		}

		scheduling_info.total_hosts++;
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[2], NULL);

	scheduling_info.average_services_per_host = (double)((double)scheduling_info.total_services / (double)scheduling_info.total_hosts);
	scheduling_info.average_scheduled_services_per_host = (double)((double)scheduling_info.total_scheduled_services / (double)scheduling_info.total_hosts);

	/* adjust the check interval total to correspond to the interval length */
	scheduling_info.service_check_interval_total = (scheduling_info.service_check_interval_total * interval_length);

	/* calculate the average check interval for services */
	scheduling_info.average_service_check_interval = (double)((double)scheduling_info.service_check_interval_total / (double)scheduling_info.total_scheduled_services);


	/******** DETERMINE SERVICE SCHEDULING PARAMS  ********/

	log_debug_info(DEBUGL_EVENTS, 2, "Determining service scheduling parameters...");

	/* default max service check spread (in minutes) */
	scheduling_info.max_service_check_spread = max_service_check_spread;

	/* how should we determine the service inter-check delay to use? */
	switch (service_inter_check_delay_method) {

	case ICD_NONE:

		/* don't spread checks out - useful for testing parallelization code */
		scheduling_info.service_inter_check_delay = 0.0;
		break;

	case ICD_DUMB:

		/* be dumb and just schedule checks 1 second apart */
		scheduling_info.service_inter_check_delay = 1.0;
		break;

	case ICD_USER:

		/* the user specified a delay, so don't try to calculate one */
		break;

	case ICD_SMART:
	default:

		/* be smart and calculate the best delay to use to minimize local load... */
		if (scheduling_info.total_scheduled_services > 0 && scheduling_info.service_check_interval_total > 0) {

			/* calculate the average inter check delay (in seconds) needed to evenly space the service checks out */
			scheduling_info.average_service_inter_check_delay = (double)(scheduling_info.average_service_check_interval / (double)scheduling_info.total_scheduled_services);

			/* set the global inter check delay value */
			scheduling_info.service_inter_check_delay = scheduling_info.average_service_inter_check_delay;

			/* calculate max inter check delay and see if we should use that instead */
			max_inter_check_delay = (double)((scheduling_info.max_service_check_spread * 60.0) / (double)scheduling_info.total_scheduled_services);
			if (scheduling_info.service_inter_check_delay > max_inter_check_delay)
				scheduling_info.service_inter_check_delay = max_inter_check_delay;
		} else
			scheduling_info.service_inter_check_delay = 0.0;

		log_debug_info(DEBUGL_EVENTS, 1, "Total scheduled service checks:  %d\n", scheduling_info.total_scheduled_services);
		log_debug_info(DEBUGL_EVENTS, 1, "Average service check interval:  %0.2f sec\n", scheduling_info.average_service_check_interval);
		log_debug_info(DEBUGL_EVENTS, 1, "Service inter-check delay:       %0.2f sec\n", scheduling_info.service_inter_check_delay);
	}

	/* how should we determine the service interleave factor? */
	switch (service_interleave_factor_method) {

	case ILF_USER:

		/* the user supplied a value, so don't do any calculation */
		break;

	case ILF_SMART:
	default:

		/* protect against a divide by zero problem - shouldn't happen, but just in case... */
		if (scheduling_info.total_hosts == 0)
			scheduling_info.total_hosts = 1;

		scheduling_info.service_interleave_factor = (int)(ceil(scheduling_info.average_scheduled_services_per_host));

		log_debug_info(DEBUGL_EVENTS, 1, "Total scheduled service checks: %d\n", scheduling_info.total_scheduled_services);
		log_debug_info(DEBUGL_EVENTS, 1, "Total hosts:                    %d\n", scheduling_info.total_hosts);
		log_debug_info(DEBUGL_EVENTS, 1, "Service Interleave factor:      %d\n", scheduling_info.service_interleave_factor);
	}

	/* calculate number of service interleave blocks */
	if (scheduling_info.service_interleave_factor == 0)
		total_interleave_blocks = scheduling_info.total_scheduled_services;
	else
		total_interleave_blocks = (int)ceil((double)scheduling_info.total_scheduled_services / (double)scheduling_info.service_interleave_factor);

	scheduling_info.first_service_check = (time_t)0L;
	scheduling_info.last_service_check = (time_t)0L;

	log_debug_info(DEBUGL_EVENTS, 1, "Total scheduled services: %d\n", scheduling_info.total_scheduled_services);
	log_debug_info(DEBUGL_EVENTS, 1, "Service Interleave factor: %d\n", scheduling_info.service_interleave_factor);
	log_debug_info(DEBUGL_EVENTS, 1, "Total service interleave blocks: %d\n", total_interleave_blocks);
	log_debug_info(DEBUGL_EVENTS, 1, "Service inter-check delay: %2.1f\n", scheduling_info.service_inter_check_delay);


	if (test_scheduling == TRUE)
		gettimeofday(&tv[3], NULL);

	/******** SCHEDULE SERVICE CHECKS  ********/

	log_debug_info(DEBUGL_EVENTS, 2, "Scheduling service checks...");

	/* determine check times for service checks (with interleaving to minimize remote load) */
	current_interleave_block = 0;
	for (temp_service = service_list; temp_service != NULL && scheduling_info.service_interleave_factor > 0;) {

		log_debug_info(DEBUGL_EVENTS, 2, "Current Interleave Block: %d\n", current_interleave_block);

		for (interleave_block_index = 0; interleave_block_index < scheduling_info.service_interleave_factor && temp_service != NULL; temp_service = temp_service->next) {

			log_debug_info(DEBUGL_EVENTS, 2, "Service '%s' on host '%s'\n", temp_service->description, temp_service->host_name);
			/* skip this service if it shouldn't be scheduled */
			if (temp_service->should_be_scheduled == FALSE) {
				log_debug_info(DEBUGL_EVENTS, 2, "Service check should not be scheduled.\n");
				continue;
			}

			/* skip services that are already scheduled for the future (from retention data), but reschedule ones that were supposed to happen while we weren't running... */
			if (temp_service->next_check > current_time) {
				if (log_level(DEBUGL_EVENTS, 2))
					log_debug_info(DEBUGL_EVENTS, 2, "Service is already scheduled to be checked in the future: %s\n", ctime(&temp_service->next_check));
				continue;
			}

			/* interleave block index should only be increased when we find a schedulable service */
			/* moved from for() loop 11/05/05 EG */
			interleave_block_index++;

			mult_factor = current_interleave_block + (interleave_block_index * total_interleave_blocks);

			log_debug_info(DEBUGL_EVENTS, 2, "CIB (current_interleave_block): %d, IBI (interleave_block_index): %d, TIB (total_interleave_blocks): %d, SIF (service_interleave_factor): %d\n",
					current_interleave_block, interleave_block_index, total_interleave_blocks, scheduling_info.service_interleave_factor);
			log_debug_info(DEBUGL_EVENTS, 2, "Mult factor: %d\n", mult_factor);

			/* set the preferred next check time for the service */
			temp_service->next_check = (time_t)(current_time + (mult_factor * scheduling_info.service_inter_check_delay));

			if (log_level(DEBUGL_EVENTS, 2))
				log_debug_info(DEBUGL_EVENTS, 2, "Preferred Check Time: %lu --> %s", (unsigned long)temp_service->next_check, ctime(&temp_service->next_check));


			/* make sure the service can actually be scheduled when we want */
			is_valid_time = check_time_against_period(temp_service->next_check, temp_service->check_period_ptr);
			if (is_valid_time == ERROR) {
				if (log_level(DEBUGL_EVENTS, 2))
					log_debug_info(DEBUGL_EVENTS, 2, "Preferred Time is Invalid In Timeperiod '%s': %lu --> %s", temp_service->check_period_ptr->name, (unsigned long)temp_service->next_check, ctime(&temp_service->next_check));
				get_next_valid_time(temp_service->next_check, &next_valid_time, temp_service->check_period_ptr);
				temp_service->next_check = next_valid_time;
			}

			if (log_level(DEBUGL_EVENTS, 2))
				log_debug_info(DEBUGL_EVENTS, 2, "Actual Check Time: %lu --> %s", (unsigned long)temp_service->next_check, ctime(&temp_service->next_check));

			if (scheduling_info.first_service_check == (time_t)0 || (temp_service->next_check < scheduling_info.first_service_check))
				scheduling_info.first_service_check = temp_service->next_check;
			if (temp_service->next_check > scheduling_info.last_service_check)
				scheduling_info.last_service_check = temp_service->next_check;
		}

		current_interleave_block++;
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[4], NULL);

	/* add scheduled service checks to event queue */
	for (temp_service = service_list; temp_service != NULL; temp_service = temp_service->next) {

		/* update status of all services (scheduled or not) */
		update_service_status(temp_service,FALSE);

		/* skip most services that shouldn't be scheduled */
		if (temp_service->should_be_scheduled == FALSE) {

			/* passive checks are an exception if a forced check was scheduled before Icinga was restarted */
			if (!(temp_service->checks_enabled == FALSE && temp_service->next_check != (time_t)0L && (temp_service->check_options & CHECK_OPTION_FORCE_EXECUTION)))
				continue;
		}

		/* create a new service check event */
		schedule_new_event(EVENT_SERVICE_CHECK, FALSE, temp_service->next_check, FALSE, 0, NULL, TRUE, (void *)temp_service, NULL, temp_service->check_options);
	}


	if (test_scheduling == TRUE)
		gettimeofday(&tv[5], NULL);

	/******** DETERMINE HOST SCHEDULING PARAMS  ********/

	log_debug_info(DEBUGL_EVENTS, 2, "Determining host scheduling parameters...");

	scheduling_info.first_host_check = (time_t)0L;
	scheduling_info.last_host_check = (time_t)0L;

	/* default max host check spread (in minutes) */
	scheduling_info.max_host_check_spread = max_host_check_spread;

	/* how should we determine the host inter-check delay to use? */
	switch (host_inter_check_delay_method) {

	case ICD_NONE:

		/* don't spread checks out */
		scheduling_info.host_inter_check_delay = 0.0;
		break;

	case ICD_DUMB:

		/* be dumb and just schedule checks 1 second apart */
		scheduling_info.host_inter_check_delay = 1.0;
		break;

	case ICD_USER:

		/* the user specified a delay, so don't try to calculate one */
		break;

	case ICD_SMART:
	default:

		/* be smart and calculate the best delay to use to minimize local load... */
		if (scheduling_info.total_scheduled_hosts > 0 && scheduling_info.host_check_interval_total > 0) {

			/* adjust the check interval total to correspond to the interval length */
			scheduling_info.host_check_interval_total = (scheduling_info.host_check_interval_total * interval_length);

			/* calculate the average check interval for hosts */
			scheduling_info.average_host_check_interval = (double)((double)scheduling_info.host_check_interval_total / (double)scheduling_info.total_scheduled_hosts);

			/* calculate the average inter check delay (in seconds) needed to evenly space the host checks out */
			scheduling_info.average_host_inter_check_delay = (double)(scheduling_info.average_host_check_interval / (double)scheduling_info.total_scheduled_hosts);

			/* set the global inter check delay value */
			scheduling_info.host_inter_check_delay = scheduling_info.average_host_inter_check_delay;

			/* calculate max inter check delay and see if we should use that instead */
			max_inter_check_delay = (double)((scheduling_info.max_host_check_spread * 60.0) / (double)scheduling_info.total_scheduled_hosts);
			if (scheduling_info.host_inter_check_delay > max_inter_check_delay)
				scheduling_info.host_inter_check_delay = max_inter_check_delay;
		} else
			scheduling_info.host_inter_check_delay = 0.0;

		log_debug_info(DEBUGL_EVENTS, 2, "Total scheduled host checks:  %d\n", scheduling_info.total_scheduled_hosts);
		log_debug_info(DEBUGL_EVENTS, 2, "Host check interval total:    %lu\n", scheduling_info.host_check_interval_total);
		log_debug_info(DEBUGL_EVENTS, 2, "Average host check interval:  %0.2f sec\n", scheduling_info.average_host_check_interval);
		log_debug_info(DEBUGL_EVENTS, 2, "Host inter-check delay:       %0.2f sec\n", scheduling_info.host_inter_check_delay);
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[6], NULL);


	/******** SCHEDULE HOST CHECKS  ********/

	log_debug_info(DEBUGL_EVENTS, 2, "Scheduling host checks...");

	/* determine check times for host checks */
	mult_factor = 0;
	for (temp_host = host_list; temp_host != NULL; temp_host = temp_host->next) {

		log_debug_info(DEBUGL_EVENTS, 2, "Host '%s'\n", temp_host->name);

		/* skip hosts that shouldn't be scheduled */
		if (temp_host->should_be_scheduled == FALSE) {
			log_debug_info(DEBUGL_EVENTS, 2, "Host check should not be scheduled.\n");
			continue;
		}

		/* skip hosts that are already scheduled for the future (from retention data), but reschedule ones that were supposed to be checked before we started */
		if (temp_host->next_check > current_time) {
			if (log_level(DEBUGL_EVENTS, 2))
				log_debug_info(DEBUGL_EVENTS, 2, "Host is already scheduled to be checked in the future: %s\n", ctime(&temp_host->next_check));
			continue;
		}

		/* calculate preferred host check time */
		temp_host->next_check = (time_t)(current_time + (mult_factor * scheduling_info.host_inter_check_delay));

		if (log_level(DEBUGL_EVENTS, 2))
			log_debug_info(DEBUGL_EVENTS, 2, "Preferred Check Time: %lu --> %s", (unsigned long)temp_host->next_check, ctime(&temp_host->next_check));

		/* make sure the host can actually be scheduled at this time */
		is_valid_time = check_time_against_period(temp_host->next_check, temp_host->check_period_ptr);
		if (is_valid_time == ERROR) {
			get_next_valid_time(temp_host->next_check, &next_valid_time, temp_host->check_period_ptr);
			temp_host->next_check = next_valid_time;
		}

		if (log_level(DEBUGL_EVENTS, 2))
			log_debug_info(DEBUGL_EVENTS, 2, "Actual Check Time: %lu --> %s", (unsigned long)temp_host->next_check, ctime(&temp_host->next_check));

		if (scheduling_info.first_host_check == (time_t)0 || (temp_host->next_check < scheduling_info.first_host_check))
			scheduling_info.first_host_check = temp_host->next_check;
		if (temp_host->next_check > scheduling_info.last_host_check)
			scheduling_info.last_host_check = temp_host->next_check;

		mult_factor++;
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[7], NULL);

	/* add scheduled host checks to event queue */
	for (temp_host = host_list; temp_host != NULL; temp_host = temp_host->next) {

		/* update status of all hosts (scheduled or not) */
		update_host_status(temp_host,FALSE);

		/* skip most hosts that shouldn't be scheduled */
		if (temp_host->should_be_scheduled == FALSE) {

			/* passive checks are an exception if a forced check was scheduled before Icinga was restarted */
			if (!(temp_host->checks_enabled == FALSE && temp_host->next_check != (time_t)0L && (temp_host->check_options & CHECK_OPTION_FORCE_EXECUTION)))
				continue;
		}

		/* schedule a new host check event */
		schedule_new_event(EVENT_HOST_CHECK, FALSE, temp_host->next_check, FALSE, 0, NULL, TRUE, (void *)temp_host, NULL, temp_host->check_options);
	}

	if (test_scheduling == TRUE)
		gettimeofday(&tv[8], NULL);


	/******** SCHEDULE MISC EVENTS ********/

	/* add a host and service check rescheduling event */
	if (auto_reschedule_checks == TRUE)
		schedule_new_event(EVENT_RESCHEDULE_CHECKS, TRUE, current_time + auto_rescheduling_interval, TRUE, auto_rescheduling_interval, NULL, TRUE, NULL, NULL, 0);

	/* add a check result reaper event */
	schedule_new_event(EVENT_CHECK_REAPER, TRUE, current_time + check_reaper_interval, TRUE, check_reaper_interval, NULL, TRUE, NULL, NULL, 0);

	/* add an orphaned check event */
	if (check_orphaned_services == TRUE || check_orphaned_hosts == TRUE)
		schedule_new_event(EVENT_ORPHAN_CHECK, TRUE, current_time + DEFAULT_ORPHAN_CHECK_INTERVAL, TRUE, DEFAULT_ORPHAN_CHECK_INTERVAL, NULL, TRUE, NULL, NULL, 0);

	/* add a service result "freshness" check event */
	if (check_service_freshness == TRUE)
		schedule_new_event(EVENT_SFRESHNESS_CHECK, TRUE, current_time + service_freshness_check_interval, TRUE, service_freshness_check_interval, NULL, TRUE, NULL, NULL, 0);

	/* add a host result "freshness" check event */
	if (check_host_freshness == TRUE)
		schedule_new_event(EVENT_HFRESHNESS_CHECK, TRUE, current_time + host_freshness_check_interval, TRUE, host_freshness_check_interval, NULL, TRUE, NULL, NULL, 0);

	/* add a status save event */
	if (aggregate_status_updates == TRUE)
		schedule_new_event(EVENT_STATUS_SAVE, TRUE, current_time + status_update_interval, TRUE, status_update_interval, NULL, TRUE, NULL, NULL, 0);

	/* add an external command check event if needed */
	if (check_external_commands == TRUE) {
		if (command_check_interval == -1)
			interval_to_use = (unsigned long)DEFAULT_EXTERNAL_COMMAND_CHECK_INTERVAL;
		else
			interval_to_use = (unsigned long)command_check_interval;
		schedule_new_event(EVENT_COMMAND_CHECK, TRUE, current_time + interval_to_use, TRUE, interval_to_use, NULL, TRUE, NULL, NULL, 0);
	}

	/* add a log rotation event if necessary */
	if (log_rotation_method != LOG_ROTATION_NONE)
		schedule_new_event(EVENT_LOG_ROTATION, TRUE, get_next_log_rotation_time(), TRUE, 0, (void *)get_next_log_rotation_time, TRUE, NULL, NULL, 0);

	/* add a retention data save event if needed */
	if (retain_state_information == TRUE && retention_update_interval > 0)
		schedule_new_event(EVENT_RETENTION_SAVE, TRUE, current_time + (retention_update_interval * 60), TRUE, (retention_update_interval * 60), NULL, TRUE, NULL, NULL, 0);

	if (test_scheduling == TRUE) {

		runtime[0] = (double)((double)(tv[1].tv_sec - tv[0].tv_sec) + (double)((tv[1].tv_usec - tv[0].tv_usec) / 1000.0) / 1000.0);
		runtime[1] = (double)((double)(tv[2].tv_sec - tv[1].tv_sec) + (double)((tv[2].tv_usec - tv[1].tv_usec) / 1000.0) / 1000.0);
		runtime[2] = (double)((double)(tv[3].tv_sec - tv[2].tv_sec) + (double)((tv[3].tv_usec - tv[2].tv_usec) / 1000.0) / 1000.0);
		runtime[3] = (double)((double)(tv[4].tv_sec - tv[3].tv_sec) + (double)((tv[4].tv_usec - tv[3].tv_usec) / 1000.0) / 1000.0);
		runtime[4] = (double)((double)(tv[5].tv_sec - tv[4].tv_sec) + (double)((tv[5].tv_usec - tv[4].tv_usec) / 1000.0) / 1000.0);
		runtime[5] = (double)((double)(tv[6].tv_sec - tv[5].tv_sec) + (double)((tv[6].tv_usec - tv[5].tv_usec) / 1000.0) / 1000.0);
		runtime[6] = (double)((double)(tv[7].tv_sec - tv[6].tv_sec) + (double)((tv[7].tv_usec - tv[6].tv_usec) / 1000.0) / 1000.0);
		runtime[7] = (double)((double)(tv[8].tv_sec - tv[7].tv_sec) + (double)((tv[8].tv_usec - tv[7].tv_usec) / 1000.0) / 1000.0);

		runtime[8] = (double)((double)(tv[8].tv_sec - tv[0].tv_sec) + (double)((tv[8].tv_usec - tv[0].tv_usec) / 1000.0) / 1000.0);

		printf("EVENT SCHEDULING TIMES\n");
		printf("-------------------------------------\n");
		printf("Get service info:        %.6lf sec\n", runtime[0]);
		printf("Get host info info:      %.6lf sec\n", runtime[1]);
		printf("Get service params:      %.6lf sec\n", runtime[2]);
		printf("Schedule service times:  %.6lf sec\n", runtime[3]);
		printf("Schedule service events: %.6lf sec\n", runtime[4]);
		printf("Get host params:         %.6lf sec\n", runtime[5]);
		printf("Schedule host times:     %.6lf sec\n", runtime[6]);
		printf("Schedule host events:    %.6lf sec\n", runtime[7]);
		printf("                         ============\n");
		printf("TOTAL:                   %.6lf sec\n", runtime[8]);
		printf("\n\n");
	}

	log_debug_info(DEBUGL_FUNCTIONS, 0, "init_timing_loop() end\n");

	return;
}


/*gives information about a particular event*/
void display_event_data(timed_event* event, int priority) {

	service *temp_service = NULL;
	host *temp_host = NULL;

	printf("\t\tEvent:");

	switch (event->event_type) {

	case EVENT_SERVICE_CHECK:
		printf("\t\t(service check)\n");
		temp_service = (service *)event->event_data;
		printf("\t\tService Description: %s\n", temp_service->description);
		printf("\t\tAssociated Host:     %s\n", temp_service->host_name);
		break;

	case EVENT_HOST_CHECK:
		printf("\t\t(host check)\n");
		temp_host = (host *)event->event_data;
		printf("\t\tHost:     %s\n", temp_host->name);
		/* run a host check */
		break;

	case EVENT_COMMAND_CHECK:
		printf("\t\t(external command check)\n");
		break;

	case EVENT_LOG_ROTATION:
		printf("\t\t(log file rotation)\n");
		break;

	case EVENT_PROGRAM_SHUTDOWN:
		printf("\t\t(program shutdown)\n");
		break;

	case EVENT_PROGRAM_RESTART:
		printf("\t\t(program restart)\n");
		break;

	case EVENT_EXPIRE_DISABLED_NOTIFICATIONS:
		printf("\t\t((expire disabled notifications)\n");
		break;

	case EVENT_CHECK_REAPER:
		printf("\t\t(service check reaper)\n");
		break;

	case EVENT_ORPHAN_CHECK:
		printf("\t\t(orphaned service check)\n");
		break;

	case EVENT_RETENTION_SAVE:
		printf("\t\t(retention save)\n");
		break;

	case EVENT_STATUS_SAVE:
		printf("\t\t(status save)\n");
		break;

	case EVENT_SCHEDULED_DOWNTIME:
		printf("\t\t(scheduled downtime)\n");
		break;

	case EVENT_SFRESHNESS_CHECK:
		printf("\t\t(service result freshness check)\n");
		break;

	case EVENT_HFRESHNESS_CHECK:
		printf("\t\t(host result freshness check)\n");
		break;

	case EVENT_EXPIRE_DOWNTIME:
		printf("\t\t(expire downtime)\n");
		break;

	case EVENT_EXPIRE_ACKNOWLEDGEMENT:
		printf("\t\t(expire acknowledgement)\n");
		break;

	case EVENT_RESCHEDULE_CHECKS:
		printf("\t\t(reschedule checks)\n");
		break;

	case EVENT_EXPIRE_COMMENT:
		printf("\t\t(expire comment)\n");
		break;

	case EVENT_USER_FUNCTION:
		printf("\t\t(user function)\n");
		break;

	default:
		printf("\t\t(Unknown!!!)\n");
		break;

	}

	printf("\t\tPriority %s\n", (priority == 0 ? "Low" : "High"));
	printf("\t\tInterval: %lu\n", event->event_interval);
	printf("\t\tEvent Time: %s\n", ctime(&event->run_time));
	printf("\n\n");
}


/* displays the service check scheduling queue */
void display_schedule(void) {
	timed_event* high_event = NULL;
	timed_event* low_event = NULL;
	timed_event* event = NULL;
	int priority = 0;

	printf("*** The current scheduling queue is listed below ***\n");

	high_event = event_list_high;
	low_event = event_list_low;

	while (high_event || low_event) {
		if (low_event && high_event) {
			if (low_event->run_time < high_event->run_time) {
				event = low_event;
				low_event = low_event->next;
				priority = 0;
			} else {
				event = high_event;
				high_event = high_event->next;
				priority = 1;
			}
		} else {
			if (high_event) {
				event = high_event;
				high_event = high_event->next;
				priority = 1;
			}

			if (low_event) {
				event = low_event;
				low_event = low_event->next;
				priority = 0;
			}
		}

		display_event_data(event, priority);
	}
}


/* displays service check scheduling information */
void display_scheduling_info(void) {
	float minimum_concurrent_checks1 = 0.0;
	float minimum_concurrent_checks2 = 0.0;
	float minimum_concurrent_checks = 0.0;
	float max_reaper_interval = 0.0;
	int suggestions = 0;

	printf("Projected scheduling information for host and service checks\n");
	printf("is listed below.  This information assumes that you are going\n");
	printf("to start running %s with your current config files.\n\n", PROGRAM_NAME);

	printf("HOST SCHEDULING INFORMATION\n");
	printf("---------------------------\n");
	printf("Total hosts:                     %d\n", scheduling_info.total_hosts);
	printf("Total scheduled hosts:           %d\n", scheduling_info.total_scheduled_hosts);

	printf("Host inter-check delay method:   ");
	if (host_inter_check_delay_method == ICD_NONE)
		printf("NONE\n");
	else if (host_inter_check_delay_method == ICD_DUMB)
		printf("DUMB\n");
	else if (host_inter_check_delay_method == ICD_SMART) {
		printf("SMART\n");
		printf("Average host check interval:     %.2f sec\n", scheduling_info.average_host_check_interval);
	} else
		printf("USER-SUPPLIED VALUE\n");
	printf("Host inter-check delay:          %.2f sec\n", scheduling_info.host_inter_check_delay);
	printf("Max host check spread:           %d min\n", scheduling_info.max_host_check_spread);
	printf("First scheduled check:           %s", (scheduling_info.total_scheduled_hosts == 0) ? "N/A\n" : ctime(&scheduling_info.first_host_check));
	printf("Last scheduled check:            %s", (scheduling_info.total_scheduled_hosts == 0) ? "N/A\n" : ctime(&scheduling_info.last_host_check));
	printf("\n\n");

	printf("SERVICE SCHEDULING INFORMATION\n");
	printf("-------------------------------\n");
	printf("Total services:                     %d\n", scheduling_info.total_services);
	printf("Total scheduled services:           %d\n", scheduling_info.total_scheduled_services);

	printf("Service inter-check delay method:   ");
	if (service_inter_check_delay_method == ICD_NONE)
		printf("NONE\n");
	else if (service_inter_check_delay_method == ICD_DUMB)
		printf("DUMB\n");
	else if (service_inter_check_delay_method == ICD_SMART) {
		printf("SMART\n");
		printf("Average service check interval:     %.2f sec\n", scheduling_info.average_service_check_interval);
	} else
		printf("USER-SUPPLIED VALUE\n");
	printf("Inter-check delay:                  %.2f sec\n", scheduling_info.service_inter_check_delay);

	printf("Interleave factor method:           %s\n", (service_interleave_factor_method == ILF_USER) ? "USER-SUPPLIED VALUE" : "SMART");
	if (service_interleave_factor_method == ILF_SMART)
		printf("Average services per host:          %.2f\n", scheduling_info.average_services_per_host);
	printf("Service interleave factor:          %d\n", scheduling_info.service_interleave_factor);

	printf("Max service check spread:           %d min\n", scheduling_info.max_service_check_spread);
	printf("First scheduled check:              %s", ctime(&scheduling_info.first_service_check));
	printf("Last scheduled check:               %s", ctime(&scheduling_info.last_service_check));
	printf("\n\n");

	printf("CHECK PROCESSING INFORMATION\n");
	printf("----------------------------\n");
	printf("Check result reaper interval:       %d sec\n", check_reaper_interval);
	printf("Max concurrent service checks:      ");
	if (max_parallel_service_checks == 0)
		printf("Unlimited\n");
	else
		printf("%d\n", max_parallel_service_checks);
	printf("\n\n");

	printf("PERFORMANCE SUGGESTIONS\n");
	printf("-----------------------\n");


	/***** MAX REAPER INTERVAL RECOMMENDATION *****/

	/* assume a 100% (2x) check burst for check reaper */
	/* assume we want a max of 2k files in the result queue at any given time */
	max_reaper_interval = floor(2000 * scheduling_info.service_inter_check_delay);
	if (max_reaper_interval < 2.0)
		max_reaper_interval = 2.0;
	if (max_reaper_interval > 30.0)
		max_reaper_interval = 30.0;
	if ((int)max_reaper_interval < check_reaper_interval) {
		printf("* Value for 'check_result_reaper_frequency' should be <= %d seconds\n", (int)max_reaper_interval);
		suggestions++;
	}
	if (check_reaper_interval < 2) {
		printf("* Value for 'check_result_reaper_frequency' should be >= 2 seconds\n");
		suggestions++;
	}

	/***** MINIMUM CONCURRENT CHECKS RECOMMENDATION *****/

	/* first method (old) - assume a 100% (2x) service check burst for max concurrent checks */
	if (scheduling_info.service_inter_check_delay == 0.0)
		minimum_concurrent_checks1 = ceil(check_reaper_interval * 2.0);
	else
		minimum_concurrent_checks1 = ceil((check_reaper_interval * 2.0) / scheduling_info.service_inter_check_delay);

	/* second method (new) - assume a 25% (1.25x) service check burst for max concurrent checks */
	minimum_concurrent_checks2 = ceil((((double)scheduling_info.total_scheduled_services) / scheduling_info.average_service_check_interval) * 1.25 * check_reaper_interval * scheduling_info.average_service_execution_time);

	/* use max of computed values */
	if (minimum_concurrent_checks1 > minimum_concurrent_checks2)
		minimum_concurrent_checks = minimum_concurrent_checks1;
	else
		minimum_concurrent_checks = minimum_concurrent_checks2;

	/* compare with configured value */
	if (((int)minimum_concurrent_checks > max_parallel_service_checks) && max_parallel_service_checks != 0) {
		printf("* Value for 'max_concurrent_checks' option should be >= %d\n", (int)minimum_concurrent_checks);
		suggestions++;
	}

	if (suggestions == 0)
		printf("I have no suggestions - things look okay.\n");

	printf("\n");

	return;
}


/* schedule a new timed event */
int schedule_new_event(int event_type, int high_priority, time_t run_time, int recurring, unsigned long event_interval, void *timing_func, int compensate_for_time_change, void *event_data, void *event_args, int event_options) {
	timed_event **event_list = NULL;
	timed_event **event_list_tail = NULL;
	timed_event *new_event = NULL;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "schedule_new_event()\n");

	if (high_priority == TRUE) {
		event_list = &event_list_high;
		event_list_tail = &event_list_high_tail;
	} else {
		event_list = &event_list_low;
		event_list_tail = &event_list_low_tail;
	}

	new_event = (timed_event *)malloc(sizeof(timed_event));
	if (new_event != NULL) {
		new_event->event_type = event_type;
		new_event->event_data = event_data;
		new_event->event_args = event_args;
		new_event->event_options = event_options;
		new_event->run_time = run_time;
		new_event->recurring = recurring;
		new_event->event_interval = event_interval;
		new_event->timing_func = timing_func;
		new_event->compensate_for_time_change = compensate_for_time_change;
		/*
		 * we need to keep the reverse link from the (service|host *)event_data->next_check_event
		 * to the new_event in order to stay sane on schedule_host|service_check() checks
		 * later on, already having a new event assigned to host/service, not rescheduling a new event.
		 * see #2993 for deeper analysis
		 */
		if (event_type == EVENT_SERVICE_CHECK) {
			service *temp_service = (service *)event_data;
			temp_service->next_check_event = new_event;
			log_debug_info(DEBUGL_CHECKS, 2, "Service '%s' on Host '%s' next_check_event populated\n", temp_service->description, temp_service->host_name);
		}
		if (event_type == EVENT_HOST_CHECK) {
			host *temp_host = (host *)event_data;
			temp_host->next_check_event = new_event;
			log_debug_info(DEBUGL_CHECKS, 2, "Host '%s' next_check_event populated\n", temp_host->name);
		}
	} else
		return ERROR;

	/* add the event to the event list */
	add_event(new_event, event_list, event_list_tail);

	return OK;
}

/* delete a scheduled timed event */
/*
	ATTENTION: at the moment only used for acknowledgements
	If needed for other cases, you have to adopt this function to fit all needs!!!
*/
int delete_scheduled_event(int event_type, int high_priority, time_t run_time, int recurring, unsigned long event_interval, void *timing_func, int compensate_for_time_change, void *event_data, void *event_args, int event_options) {
	timed_event **event_list = NULL;
	timed_event **event_list_tail = NULL;
	timed_event *temp_event = NULL;

	/* we support only acknowledgements at the moment */
	if (event_type != EVENT_EXPIRE_ACKNOWLEDGEMENT)
		return OK;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "delete_scheduled_event()\n");

	if (high_priority == TRUE) {
		event_list = &event_list_high;
		event_list_tail = &event_list_high_tail;
	} else {
		event_list = &event_list_low;
		event_list_tail = &event_list_low_tail;
	}

	/* start from the beginning */
	for (temp_event = *event_list; temp_event != NULL; temp_event = temp_event->next) {

		if (temp_event->event_type == event_type && temp_event->event_options == event_options && temp_event->event_data == event_data) {

			if (log_level(DEBUGL_EVENTS, 1))
				log_debug_info(DEBUGL_EVENTS, 1, "Removing event type %d @ %s", event_type, ctime(&run_time));

			/* remove the event from the event list */
			remove_event(temp_event, event_list, event_list_tail);

			return OK;
		}
	}

	return ERROR;
}


/* reschedule an event in order of execution time */
void reschedule_event(timed_event *event, timed_event **event_list, timed_event **event_list_tail) {
	time_t current_time = 0L;
	time_t (*timingfunc)(void);

	log_debug_info(DEBUGL_FUNCTIONS, 0, "reschedule_event()\n");

	/* reschedule recurring events... */
	if (event->recurring == TRUE) {

		/* use custom timing function */
		if (event->timing_func != NULL) {
			timingfunc = event->timing_func;
			event->run_time = (*timingfunc)();
		}

		/* normal recurring events */
		else {
			event->run_time = event->run_time + event->event_interval;
			time(&current_time);
			if (event->run_time < current_time)
				event->run_time = current_time;
		}
	}

	/* add the event to the event list */
	add_event(event, event_list, event_list_tail);

	return;
}


/* add an event to list ordered by execution time */
void add_event(timed_event *event, timed_event **event_list, timed_event **event_list_tail) {
	timed_event *temp_event = NULL;
	timed_event *first_event = NULL;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "add_event()\n");

	event->next = NULL;
	event->prev = NULL;

	first_event = *event_list;

	/* add the event to the head of the list if there are no other events */
	if (*event_list == NULL) {
		*event_list = event;
		*event_list_tail = event;
	}

	/* add event to head of the list if it should be executed first */
	else if (event->run_time < first_event->run_time) {
		event->prev = NULL;
		(*event_list)->prev = event;
		event->next = *event_list;
		*event_list = event;
	}

	/* else place the event according to next execution time */
	else {

		/* start from the end of the list, as new events are likely to be executed in the future, rather than now... */
		for (temp_event = *event_list_tail; temp_event != NULL; temp_event = temp_event->prev) {
			if (event->run_time >= temp_event->run_time) {
				event->next = temp_event->next;
				event->prev = temp_event;
				temp_event->next = event;
				if (event->next == NULL)
					*event_list_tail = event;
				else
					event->next->prev = event;
				break;
			} else if (temp_event->prev == NULL) {
				temp_event->prev = event;
				event->next = temp_event;
				*event_list = event;
				break;
			}
		}
	}

#ifdef USE_EVENT_BROKER
	/* send event data to broker */
	broker_timed_event(NEBTYPE_TIMEDEVENT_ADD, NEBFLAG_NONE, NEBATTR_NONE, event, NULL);
#endif

	return;
}



/* remove an event from the queue */
void remove_event(timed_event *event, timed_event **event_list, timed_event **event_list_tail) {
	timed_event *prev_event, *next_event;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "remove_event()\n");

	if (!event)
		return;

#ifdef USE_EVENT_BROKER
	/* send event data to broker */
	broker_timed_event(NEBTYPE_TIMEDEVENT_REMOVE, NEBFLAG_NONE, NEBATTR_NONE, event, NULL);
#endif

	if (*event_list == NULL)
		return;

	/*
	 * the event struct already holds next+prev ptrs
	 * for the doubly linked list, so fetch them
	 * and reassign previous with next, and reverse
	 * check #2183 for a detailed description
	 */
	prev_event = event->prev;
	next_event = event->next;

	if (prev_event) {
		prev_event->next = next_event;
	}
	if (next_event) {
		next_event->prev = prev_event;
	}

	/* re-assign head and tail */
	if (!prev_event) {
		/* no previous event, so "next" is now first in list */
		*event_list = next_event;
	}
	if (!next_event) {
		/* no following event, so "prev" is now last in list */
		*event_list_tail = prev_event;
	}

	/*
	 * If there was only one event in the list,
	 * it's done just as if there were events before
	 * and after the deleted event.
	 * head and tail pointers are now NULL in this case
	 */
}



/* this is the main event handler loop */
int event_execution_loop(void) {
	timed_event *temp_event = NULL;
	timed_event sleep_event;
	time_t last_time = 0L;
	time_t current_time = 0L;
	time_t last_status_update = 0L;
	int run_event = TRUE;
	int nudge_seconds;
	host *temp_host = NULL;
	service *temp_service = NULL;
	struct timespec delay;
	pid_t wait_result;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "event_execution_loop() start\n");

	time(&last_time);

	pthread_mutex_lock(&icinga_eventloop_lock);

	/* initialize fake "sleep" event */
	sleep_event.event_type = EVENT_SLEEP;
	sleep_event.run_time = last_time;
	sleep_event.recurring = FALSE;
	sleep_event.event_interval = 0L;
	sleep_event.compensate_for_time_change = FALSE;
	sleep_event.timing_func = NULL;
	sleep_event.event_data = NULL;
	sleep_event.event_args = NULL;
	sleep_event.event_options = 0;
	sleep_event.next = NULL;
	sleep_event.prev = NULL;

	while (1) {

		/* see if we should exit or restart (a signal was encountered) */
		if (sigshutdown == TRUE || sigrestart == TRUE)
			break;

		/* if we don't have any events to handle, exit */
		if (event_list_high == NULL && event_list_low == NULL) {
			log_debug_info(DEBUGL_EVENTS, 0, "There aren't any events that need to be handled! Exiting...\n");
			break;
		}

		/* get the current time */
		time(&current_time);

		/* hey, wait a second...  we traveled back in time! */
		if (current_time < last_time)
			compensate_for_system_time_change((unsigned long)last_time, (unsigned long)current_time);

		/* else if the time advanced over the specified threshold, try and compensate... */
		else if ((current_time - last_time) >= time_change_threshold)
			compensate_for_system_time_change((unsigned long)last_time, (unsigned long)current_time);

		/* keep track of the last time */
		last_time = current_time;

		log_debug_info(DEBUGL_EVENTS, 1, "** Event Check Loop\n");

		if (event_list_high != NULL) {
			if (log_level(DEBUGL_EVENTS, 1))
				log_debug_info(DEBUGL_EVENTS, 1, "Next High Priority Event Time: %s", ctime(&event_list_high->run_time));
		} else {
			log_debug_info(DEBUGL_EVENTS, 1, "No high priority events are scheduled...\n");
		}

		if (event_list_low != NULL) {
			if (log_level(DEBUGL_EVENTS, 1))
			log_debug_info(DEBUGL_EVENTS, 1, "Next Low Priority Event Time:  %s", ctime(&event_list_low->run_time));
		} else {
			log_debug_info(DEBUGL_EVENTS, 1, "No low priority events are scheduled...\n");
		}

		log_debug_info(DEBUGL_EVENTS, 1, "Current/Max Service Checks: %d/%d\n", currently_running_service_checks, max_parallel_service_checks);

		/* get rid of terminated child processes (zombies) */
		if (child_processes_fork_twice == FALSE) {
			while ((wait_result = waitpid(-1, NULL, WNOHANG)) > 0);
		}

		/* handle high priority events */
		if (event_list_high != NULL && (current_time >= event_list_high->run_time)) {

			/* remove the first event from the timing loop */
			temp_event = event_list_high;
			event_list_high = event_list_high->next;

			/* we may have just removed the only item from the list */
			if (event_list_high != NULL)
				event_list_high->prev = NULL;

			/* handle the event */
			handle_timed_event(temp_event);

			/* reschedule the event if necessary */
			if (temp_event->recurring == TRUE)
				reschedule_event(temp_event, &event_list_high, &event_list_high_tail);

			/* else free memory associated with the event */
			else
				my_free(temp_event);
		}

		/* 21-09-2010 NOTE MF:
			The logic on low priority events just as host and service checks work liks this:
			Set run_event=TRUE on looping start, check if next event is a serviecheck. If matched,
			check for several things, most important for disabled checks. If those are disabled,
			set run_event=FALSE (this flag will be checked at the end). The servicecheck event
			will be removed from the eventlist.
			Same goes for the hostcheck events, and afterwards, run_event is checked and no matter
			if service or host check event, it gets executed as a timed event, if run_event==TRUE.

			Previous code (21-09-2010) had a logical AND operation, servicecheck and hostcheck events
			could have been tested within one looping. If servicecheck was disabled, run_event=FALSE,
			and removed from event queue resulted in next event = hostcheck, which was matched by the
			2nd IF - also having run_event set to FALSE, causing the hostcheck being skipped too.
			By changing this to a logical OR operation, IF ... ELSE IF ..., only one check can be
			matched throughout a single looping and run_event is responsible for only one event at a
			time - service OR host check event.
		*/

		/* handle low priority events */
		else if (event_list_low != NULL && (current_time >= event_list_low->run_time)) {

			/* default action is to execute the event */
			run_event = TRUE;
			nudge_seconds = 0;

			/* run a few checks before executing a service check... */
			if (event_list_low->event_type == EVENT_SERVICE_CHECK) {

				temp_service = (service *)event_list_low->event_data;

				log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "Run a few checks before executing a service check for '%s'.\n", temp_service->description);

				/* don't run a service check if we're already maxed out on the number of parallel service checks...  */
				if (max_parallel_service_checks != 0 && (currently_running_service_checks >= max_parallel_service_checks)) {

					/* Move it at least 5 seconds (to overcome the current peak), with a random 10 seconds (to spread the load) */
					nudge_seconds = 5 + (rand() % 10);
					log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 0, "**WARNING** Max concurrent service checks (%d) has been reached!  Nudging %s:%s by %d seconds...\n", max_parallel_service_checks, temp_service->host_name, temp_service->description, nudge_seconds);

					logit(NSLOG_RUNTIME_WARNING, TRUE, "\tMax concurrent service checks (%d) has been reached.  Nudging %s:%s by %d seconds...\n", max_parallel_service_checks, temp_service->host_name, temp_service->description, nudge_seconds);
					run_event = FALSE;
				}

				/* don't run a service check if active checks are disabled */
				if (execute_service_checks == FALSE) {

					log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "We're not executing service checks right now, so we'll skip this event.\n");

					run_event = FALSE;
				}

				/* forced checks override normal check logic */
				if ((temp_service->check_options & CHECK_OPTION_FORCE_EXECUTION))
					run_event = TRUE;

				/* reschedule the check if we can't run it now */
				if (run_event == FALSE) {

					/* remove the service check from the event queue and reschedule it for a later time */
					/* 12/20/05 since event was not executed, it needs to be remove()'ed to maintain sync with event broker modules */
					log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "Skip event, removing service '%s' from list.\n", temp_service->description);
					temp_event = event_list_low;
					remove_event(temp_event, &event_list_low, &event_list_low_tail);
					/*
					event_list_low=event_list_low->next;
					*/
					if (nudge_seconds) {
						/* We nudge the next check time when it is due to too many concurrent service checks */
						temp_service->next_check = (time_t)(temp_service->next_check + nudge_seconds);
					} else {
						/* Otherwise reschedule (TODO: This should be smarter as it doesn't consider its timeperiod) */
						if (temp_service->state_type == SOFT_STATE && temp_service->current_state != STATE_OK)
							temp_service->next_check = (time_t)(temp_service->next_check + (temp_service->retry_interval * interval_length));
						else
							temp_service->next_check = (time_t)(temp_service->next_check + (temp_service->check_interval * interval_length));
					}

					temp_event->run_time = temp_service->next_check;
					reschedule_event(temp_event, &event_list_low, &event_list_low_tail);
					update_service_status(temp_service, FALSE);

					run_event = FALSE;
				}

			}

			/* run a few checks before executing a host check... */
			else if (event_list_low->event_type == EVENT_HOST_CHECK) {

				temp_host = (host *)event_list_low->event_data;

				log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "Run a few checks before executing a host check for '%s'.\n", temp_host->name);

				/* don't run a host check if active checks are disabled */
				if (execute_host_checks == FALSE) {

					log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "We're not executing host checks right now, so we'll skip this event.\n");

					run_event = FALSE;
				}

				/* forced checks override normal check logic */
				if ((temp_host->check_options & CHECK_OPTION_FORCE_EXECUTION))
					run_event = TRUE;

				/* reschedule the host check if we can't run it right now */
				if (run_event == FALSE) {

					/* remove the host check from the event queue and reschedule it for a later time */
					/* 12/20/05 since event was not executed, it needs to be remove()'ed to maintain sync with event broker modules */
					log_debug_info(DEBUGL_EVENTS | DEBUGL_CHECKS, 1, "Skip event, removing host '%s' from list.\n", temp_host->name);
					temp_event = event_list_low;
					remove_event(temp_event, &event_list_low, &event_list_low_tail);
					/*
					event_list_low=event_list_low->next;
					*/
					if (temp_host->state_type == SOFT_STATE && temp_host->current_state != STATE_OK)
						temp_host->next_check = (time_t)(temp_host->next_check + (temp_host->retry_interval * interval_length));
					else
						temp_host->next_check = (time_t)(temp_host->next_check + (temp_host->check_interval * interval_length));

					temp_event->run_time = temp_host->next_check;
					reschedule_event(temp_event, &event_list_low, &event_list_low_tail);
					update_host_status(temp_host, FALSE);

					run_event = FALSE;
				}
			}

			/* run the event */
			if (run_event == TRUE) {

				/* remove the first event from the timing loop */
				temp_event = event_list_low;
				event_list_low = event_list_low->next;
				/* we may have just removed the only item from the list */
				if (event_list_low != NULL)
					event_list_low->prev = NULL;

				log_debug_info(DEBUGL_EVENTS, 1, "Running event...\n");

				/* handle the event */
				handle_timed_event(temp_event);

				/* reschedule the event if necessary */
				if (temp_event->recurring == TRUE)
					reschedule_event(temp_event, &event_list_low, &event_list_low_tail);

				/* else free memory associated with the event */
				else
					my_free(temp_event);
			}

		}

		/* we don't have anything to do at this moment in time... */
		else if ((event_list_high == NULL || (current_time < event_list_high->run_time)) && (event_list_low == NULL || (current_time < event_list_low->run_time))) {

			log_debug_info(DEBUGL_EVENTS, 2, "No events to execute at the moment.  Idling for a bit...\n");

			/* check for external commands if we're supposed to check as often as possible */
			if (command_check_interval == -1)
				check_for_external_commands();

			/* set time to sleep so we don't hog the CPU... */
#ifdef USE_NANOSLEEP
			delay.tv_sec = (time_t)sleep_time;
			delay.tv_nsec = (long)((sleep_time - (double)delay.tv_sec) * 1000000000);
#else
			delay.tv_sec = (time_t)sleep_time;
			if (delay.tv_sec == 0L)
				delay.tv_sec = 1;
			delay.tv_nsec = 0L;
#endif

#ifdef USE_EVENT_BROKER
			/* populate fake "sleep" event */
			sleep_event.run_time = current_time;
			sleep_event.event_data = (void *)&delay;

			/* send event data to broker */
			broker_timed_event(NEBTYPE_TIMEDEVENT_SLEEP, NEBFLAG_NONE, NEBATTR_NONE, &sleep_event, NULL);
#endif

			pthread_mutex_unlock(&icinga_eventloop_lock);

			/* wait a while so we don't hog the CPU... */
#ifdef USE_NANOSLEEP
			nanosleep(&delay, NULL);
#else
			sleep((unsigned int)delay.tv_sec);
#endif

			pthread_mutex_lock(&icinga_eventloop_lock);
		}

		/* update status information occassionally - NagVis watches the NDOUtils DB to see if Icinga is alive */
		if ((unsigned long)(current_time - last_status_update) > 5) {
			last_status_update = current_time;
			update_program_status(FALSE);
		}
	}

	log_debug_info(DEBUGL_FUNCTIONS, 0, "event_execution_loop() end\n");

	pthread_mutex_unlock(&icinga_eventloop_lock);

	return OK;
}



/* handles a timed event */
int handle_timed_event(timed_event *event) {
	host *temp_host = NULL;
	service *temp_service = NULL;
	void (*userfunc)(void *);
	struct timeval tv;
	double latency = 0.0;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "handle_timed_event() start\n");

#ifdef USE_EVENT_BROKER
	/* send event data to broker */
	broker_timed_event(NEBTYPE_TIMEDEVENT_EXECUTE, NEBFLAG_NONE, NEBATTR_NONE, event, NULL);
#endif

	if (log_level(DEBUGL_EVENTS, 0))
		log_debug_info(DEBUGL_EVENTS, 0, "** Timed Event ** Type: %d, Run Time: %s", event->event_type, ctime(&event->run_time));

	/* how should we handle the event? */
	switch (event->event_type) {

	case EVENT_SERVICE_CHECK:

		temp_service = (service *)event->event_data;

		/* get check latency */
		gettimeofday(&tv, NULL);
		latency = (double)((double)(tv.tv_sec - event->run_time) + (double)(tv.tv_usec / 1000) / 1000.0);

		log_debug_info(DEBUGL_EVENTS, 0, "** Service Check Event ==> Host: '%s', Service: '%s', Options: %d, Latency: %f sec\n", temp_service->host_name, temp_service->description, event->event_options, latency);

		/* run the service check */
		temp_service = (service *)event->event_data;
		run_scheduled_service_check(temp_service, event->event_options, latency);
		break;

	case EVENT_HOST_CHECK:

		temp_host = (host *)event->event_data;

		/* get check latency */
		gettimeofday(&tv, NULL);
		latency = (double)((double)(tv.tv_sec - event->run_time) + (double)(tv.tv_usec / 1000) / 1000.0);

		log_debug_info(DEBUGL_EVENTS, 0, "** Host Check Event ==> Host: '%s', Options: %d, Latency: %f sec\n", temp_host->name, event->event_options, latency);

		/* run the host check */
		temp_host = (host *)event->event_data;
		perform_scheduled_host_check(temp_host, event->event_options, latency);
		break;

	case EVENT_COMMAND_CHECK:

		log_debug_info(DEBUGL_EVENTS, 0, "** External Command Check Event\n");

		/* check for external commands */
		check_for_external_commands();
		break;

	case EVENT_LOG_ROTATION:

		log_debug_info(DEBUGL_EVENTS, 0, "** Log File Rotation Event\n");

		/* rotate the log file */
		rotate_log_file(event->run_time);
		break;

	case EVENT_PROGRAM_SHUTDOWN:

		log_debug_info(DEBUGL_EVENTS, 0, "** Program Shutdown Event\n");

		/* set the shutdown flag */
		sigshutdown = TRUE;

		/* log the shutdown */
		logit(NSLOG_PROCESS_INFO, TRUE, "PROGRAM_SHUTDOWN event encountered, shutting down...\n");
		break;

	case EVENT_PROGRAM_RESTART:

		log_debug_info(DEBUGL_EVENTS, 0, "** Program Restart Event\n");

		/* set the restart flag */
		sigrestart = TRUE;

		/* log the restart */
		logit(NSLOG_PROCESS_INFO, TRUE, "PROGRAM_RESTART event encountered, restarting...\n");
		break;

	case EVENT_EXPIRE_DISABLED_NOTIFICATIONS:

		log_debug_info(DEBUGL_EVENTS, 0, "** Expire Disabled Notifications Event\n");

		/* check if this is a future event, and needs to be kept.
		 * otherwise, we would remove/reset events bound to happen
		 */
		if (disable_notifications_expire_time > event->run_time)
			break;
		/*
		 * only do it when we actually have a time set
		 * ENABLE_NOTIFICATIONS will set that to 0,
		 * indicating that expiry already happened
		 */
		if (disable_notifications_expire_time > 0) {

			/* reset the expire time before enabling notifications */
			disable_notifications_expire_time = 0L;

			/* re-enable all notifications (triggers programstatus update for neb modules too) */
			enable_all_notifications();

		        /* log that we will now expire disabled notifications */
		        logit(NSLOG_INFO_MESSAGE, TRUE, "Disabled Notifications expired. All Notifications re-enabled.\n");
		}

		break;

	case EVENT_CHECK_REAPER:

		log_debug_info(DEBUGL_EVENTS, 0, "** Check Result Reaper\n");

		/* reap host and service check results */
		reap_check_results();
		break;

	case EVENT_ORPHAN_CHECK:

		log_debug_info(DEBUGL_EVENTS, 0, "** Orphaned Host and Service Check Event\n");

		/* check for orphaned hosts and services */
		if (check_orphaned_hosts == TRUE)
			check_for_orphaned_hosts();
		if (check_orphaned_services == TRUE)
			check_for_orphaned_services();
		break;

	case EVENT_RETENTION_SAVE:

		log_debug_info(DEBUGL_EVENTS, 0, "** Retention Data Save Event\n");

		/* save state retention data */
		save_state_information(TRUE);
		break;

	case EVENT_STATUS_SAVE:

		log_debug_info(DEBUGL_EVENTS, 0, "** Status Data Save Event\n");

		/* save all status data (program, host, and service) */
		update_all_status_data();
		break;

	case EVENT_SCHEDULED_DOWNTIME:

		log_debug_info(DEBUGL_EVENTS, 0, "** Scheduled Downtime Event\n");

		/* process scheduled downtime info */
		if (event->event_data) {
			handle_scheduled_downtime_by_id(*(unsigned long *)event->event_data);
			free(event->event_data);
			event->event_data = NULL;
		}
		break;

	case EVENT_SFRESHNESS_CHECK:

		log_debug_info(DEBUGL_EVENTS, 0, "** Service Result Freshness Check Event\n");

		/* check service result freshness */
		check_service_result_freshness();
		break;

	case EVENT_HFRESHNESS_CHECK:

		log_debug_info(DEBUGL_EVENTS, 0, "** Host Result Freshness Check Event\n");

		/* check host result freshness */
		check_host_result_freshness();
		break;

	case EVENT_EXPIRE_DOWNTIME:

		log_debug_info(DEBUGL_EVENTS, 0, "** Expire Downtime Event\n");

		/* check for expired scheduled downtime entries */
		check_for_expired_downtime();
		break;

	case EVENT_EXPIRE_ACKNOWLEDGEMENT:

		log_debug_info(DEBUGL_EVENTS, 0, "** Expire Acknowledgement Event\n");

		/* Delete expired acknowledgements */
		if (event->event_options == SERVICE_ACKNOWLEDGEMENT) {
			temp_service = (service *)event->event_data;
			remove_service_acknowledgement(temp_service);
		} else if (event->event_options == HOST_ACKNOWLEDGEMENT) {
			temp_host = (host *)event->event_data;
			remove_host_acknowledgement(temp_host);
		} else {
			log_debug_info(DEBUGL_EVENTS, 0, "** Unknown Expire Acknowledgement event: %d\n", event->event_options);
		}

		break;

	case EVENT_RESCHEDULE_CHECKS:

		/* adjust scheduling of host and service checks */
		log_debug_info(DEBUGL_EVENTS, 0, "** Reschedule Checks Event\n");

		adjust_check_scheduling();
		break;

	case EVENT_EXPIRE_COMMENT:

		log_debug_info(DEBUGL_EVENTS, 0, "** Expire Comment Event\n");

		/* check for expired comment */
		check_for_expired_comment((unsigned long)event->event_data);
		break;

	case EVENT_USER_FUNCTION:

		log_debug_info(DEBUGL_EVENTS, 0, "** User Function Event\n");

		/* run a user-defined function */
		if (event->event_data != NULL) {
			userfunc = event->event_data;
			(*userfunc)(event->event_args);
		}
		break;

	default:

		break;
	}

	log_debug_info(DEBUGL_FUNCTIONS, 0, "handle_timed_event() end\n");

	return OK;
}



/* adjusts scheduling of host and service checks */
void adjust_check_scheduling(void) {
	timed_event *temp_event = NULL;
	service *temp_service = NULL;
	host *temp_host = NULL;
	double projected_host_check_overhead = 0.1;
	double projected_service_check_overhead = 0.1;
	time_t current_time = 0L;
	time_t first_window_time = 0L;
	time_t last_window_time = 0L;
	time_t last_check_time = 0L;
	time_t new_run_time = 0L;
	int total_checks = 0;
	int current_check = 0;
	double inter_check_delay = 0.0;
	double current_icd_offset = 0.0;
	double total_check_exec_time = 0.0;
	double last_check_exec_time = 0.0;
	int adjust_scheduling = FALSE;
	double exec_time_factor = 0.0;
	double current_exec_time = 0.0;
	double current_exec_time_offset = 0.0;
	double new_run_time_offset = 0.0;


	log_debug_info(DEBUGL_FUNCTIONS, 0, "adjust_check_scheduling() start\n");

	/* TODO:
	   - Track host check overhead on a per-host basis
	   - Figure out how to calculate service check overhead
	*/

	/* determine our adjustment window */
	time(&current_time);
	first_window_time = current_time;
	last_window_time = first_window_time + auto_rescheduling_window;

	/* get current scheduling data */
	for (temp_event = event_list_low; temp_event != NULL; temp_event = temp_event->next) {

		/* skip events outside of our current window */
		if (temp_event->run_time <= first_window_time)
			continue;
		if (temp_event->run_time > last_window_time)
			break;

		if (temp_event->event_type == EVENT_HOST_CHECK) {

			if ((temp_host = (host *)temp_event->event_data) == NULL)
				continue;

			/* ignore forced checks */
			if (temp_host->check_options & CHECK_OPTION_FORCE_EXECUTION)
				continue;

			/* does the last check "bump" into this one? */
			if ((unsigned long)(last_check_time + last_check_exec_time) > temp_event->run_time)
				adjust_scheduling = TRUE;

			last_check_time = temp_event->run_time;

			/* calculate time needed to perform check */
			/* NOTE: host check execution time is not taken into account, as scheduled host checks are run in parallel */
			last_check_exec_time = projected_host_check_overhead;
			total_check_exec_time += last_check_exec_time;
		}

		else if (temp_event->event_type == EVENT_SERVICE_CHECK) {

			if ((temp_service = (service *)temp_event->event_data) == NULL)
				continue;

			/* ignore forced checks */
			if (temp_service->check_options & CHECK_OPTION_FORCE_EXECUTION)
				continue;

			/* does the last check "bump" into this one? */
			if ((unsigned long)(last_check_time + last_check_exec_time) > temp_event->run_time)
				adjust_scheduling = TRUE;

			last_check_time = temp_event->run_time;

			/* calculate time needed to perform check */
			/* NOTE: service check execution time is not taken into account, as service checks are run in parallel */
			last_check_exec_time = projected_service_check_overhead;
			total_check_exec_time += last_check_exec_time;
		}

		else
			continue;

		total_checks++;
	}


	/* nothing to do... */
	if (total_checks == 0 || adjust_scheduling == FALSE) {

		/*
		printf("\n\n");
		printf("NOTHING TO DO!\n");
		printf("# CHECKS:    %d\n",total_checks);
		printf("WINDOW TIME: %d\n",auto_rescheduling_window);
		printf("EXEC TIME:   %.3f\n",total_check_exec_time);
		*/

		return;
	}

	if ((unsigned long)total_check_exec_time > auto_rescheduling_window) {
		inter_check_delay = 0.0;
		exec_time_factor = (double)((double)auto_rescheduling_window / total_check_exec_time);
	} else {
		inter_check_delay = (double)((((double)auto_rescheduling_window) - total_check_exec_time) / (double)(total_checks * 1.0));
		exec_time_factor = 1.0;
	}

	/*
	printf("\n\n");
	printf("TOTAL CHECKS: %d\n",total_checks);
	printf("WINDOW TIME:  %d\n",auto_rescheduling_window);
	printf("EXEC TIME:    %.3f\n",total_check_exec_time);
	printf("ICD:          %.3f\n",inter_check_delay);
	printf("EXEC FACTOR:  %.3f\n",exec_time_factor);
	*/

	/* adjust check scheduling */
	current_icd_offset = (inter_check_delay / 2.0);
	for (temp_event = event_list_low; temp_event != NULL; temp_event = temp_event->next) {

		/* skip events outside of our current window */
		if (temp_event->run_time <= first_window_time)
			continue;
		if (temp_event->run_time > last_window_time)
			break;

		if (temp_event->event_type == EVENT_HOST_CHECK) {

			if ((temp_host = (host *)temp_event->event_data) == NULL)
				continue;

			/* ignore forced checks */
			if (temp_host->check_options & CHECK_OPTION_FORCE_EXECUTION)
				continue;

			current_exec_time = ((temp_host->execution_time + projected_host_check_overhead) * exec_time_factor);
		}

		else if (temp_event->event_type == EVENT_SERVICE_CHECK) {

			if ((temp_service = (service *)temp_event->event_data) == NULL)
				continue;

			/* ignore forced checks */
			if (temp_service->check_options & CHECK_OPTION_FORCE_EXECUTION)
				continue;

			/* NOTE: service check execution time is not taken into account, as service checks are run in parallel */
			current_exec_time = (projected_service_check_overhead * exec_time_factor);
		}

		else
			continue;

		current_check++;
		new_run_time_offset = current_exec_time_offset + current_icd_offset;
		new_run_time = (time_t)(first_window_time + (unsigned long)new_run_time_offset);

		/*
		printf("  CURRENT CHECK #:      %d\n",current_check);
		printf("  CURRENT ICD OFFSET:   %.3f\n",current_icd_offset);
		printf("  CURRENT EXEC TIME:    %.3f\n",current_exec_time);
		printf("  CURRENT EXEC OFFSET:  %.3f\n",current_exec_time_offset);
		printf("  NEW RUN TIME:         %lu\n",new_run_time);
		*/

		if (temp_event->event_type == EVENT_HOST_CHECK) {
			temp_event->run_time = new_run_time;
			temp_host->next_check = new_run_time;
			update_host_status(temp_host, FALSE);
		} else {
			temp_event->run_time = new_run_time;
			temp_service->next_check = new_run_time;
			update_service_status(temp_service, FALSE);
		}

		current_icd_offset += inter_check_delay;
		current_exec_time_offset += current_exec_time;
	}

	/* resort event list (some events may be out of order at this point) */
	resort_event_list(&event_list_low, &event_list_low_tail);

	log_debug_info(DEBUGL_FUNCTIONS, 0, "adjust_check_scheduling() end\n");

	return;
}



/* attempts to compensate for a change in the system time */
void compensate_for_system_time_change(unsigned long last_time, unsigned long current_time) {
	unsigned long time_difference = 0L;
	timed_event *temp_event = NULL;
	service *temp_service = NULL;
	host *temp_host = NULL;
	int days = 0;
	int hours = 0;
	int minutes = 0;
	int seconds = 0;
	time_t (*timingfunc)(void);


	log_debug_info(DEBUGL_FUNCTIONS, 0, "compensate_for_system_time_change() start\n");

	/* we moved back in time... */
	if (last_time > current_time) {
		time_difference = last_time - current_time;
		get_time_breakdown(time_difference, &days, &hours, &minutes, &seconds);
		log_debug_info(DEBUGL_EVENTS, 0, "Detected a backwards time change of %dd %dh %dm %ds.\n", days, hours, minutes, seconds);
	}

	/* we moved into the future... */
	else {
		time_difference = current_time - last_time;
		get_time_breakdown(time_difference, &days, &hours, &minutes, &seconds);
		log_debug_info(DEBUGL_EVENTS, 0, "Detected a forwards time change of %dd %dh %dm %ds.\n", days, hours, minutes, seconds);
	}

	/* log the time change */
	logit(NSLOG_PROCESS_INFO | NSLOG_RUNTIME_WARNING, TRUE, "Warning: A system time change of %dd %dh %dm %ds (%s in time) has been detected.  Compensating...\n", days, hours, minutes, seconds, (last_time > current_time) ? "backwards" : "forwards");

	/* adjust the next run time for all high priority timed events */
	for (temp_event = event_list_high; temp_event != NULL; temp_event = temp_event->next) {

		/* skip special events that occur at specific times... */
		if (temp_event->compensate_for_time_change == FALSE)
			continue;

		/* use custom timing function */
		if (temp_event->timing_func != NULL) {
			timingfunc = temp_event->timing_func;
			temp_event->run_time = (*timingfunc)();
		}

		/* else use standard adjustment */
		else
			adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_event->run_time);
	}

	/* resort event list (some events may be out of order at this point) */
	resort_event_list(&event_list_high, &event_list_high_tail);

	/* adjust the next run time for all low priority timed events */
	for (temp_event = event_list_low; temp_event != NULL; temp_event = temp_event->next) {

		/* skip special events that occur at specific times... */
		if (temp_event->compensate_for_time_change == FALSE)
			continue;

		/* use custom timing function */
		if (temp_event->timing_func != NULL) {
			timingfunc = temp_event->timing_func;
			temp_event->run_time = (*timingfunc)();
		}

		/* else use standard adjustment */
		else
			adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_event->run_time);
	}

	/* resort event list (some events may be out of order at this point) */
	resort_event_list(&event_list_low, &event_list_low_tail);

	/* adjust service timestamps */
	for (temp_service = service_list; temp_service != NULL; temp_service = temp_service->next) {

		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_service->last_notification);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_service->last_check);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_service->next_check);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_service->last_state_change);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_service->last_hard_state_change);

		/* recalculate next re-notification time */
		temp_service->next_notification = get_next_service_notification_time(temp_service, temp_service->last_notification);

		/* update the status data */
		update_service_status(temp_service, FALSE);
	}

	/* adjust host timestamps */
	for (temp_host = host_list; temp_host != NULL; temp_host = temp_host->next) {

		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->last_host_notification);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->last_check);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->next_check);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->last_state_change);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->last_hard_state_change);
		adjust_timestamp_for_time_change(last_time, current_time, time_difference, &temp_host->last_state_history_update);

		/* recalculate next re-notification time */
		temp_host->next_host_notification = get_next_host_notification_time(temp_host, temp_host->last_host_notification);

		/* update the status data */
		update_host_status(temp_host, FALSE);
	}

	/* adjust program timestamps */
	adjust_timestamp_for_time_change(last_time, current_time, time_difference, &program_start);
	adjust_timestamp_for_time_change(last_time, current_time, time_difference, &event_start);
	adjust_timestamp_for_time_change(last_time, current_time, time_difference, &last_command_check);

	/* update the status data */
	update_program_status(FALSE);

	return;
}



/* resorts an event list by event execution time - needed when compensating for system time changes */
void resort_event_list(timed_event **event_list, timed_event **event_list_tail) {
	timed_event *temp_event_list = NULL;
	timed_event *temp_event = NULL;
	timed_event *next_event = NULL;

	log_debug_info(DEBUGL_FUNCTIONS, 0, "resort_event_list()\n");

	/* move current event list to temp list */
	temp_event_list = *event_list;
	*event_list = NULL;

	/* move all events to the new event list */
	for (temp_event = temp_event_list; temp_event != NULL; temp_event = next_event) {
		next_event = temp_event->next;

		/* add the event to the newly created event list so it will be resorted */
		temp_event->next = NULL;
		temp_event->prev = NULL;
		add_event(temp_event, event_list, event_list_tail);
	}

	return;
}



/* adjusts a timestamp variable in accordance with a system time change */
void adjust_timestamp_for_time_change(time_t last_time, time_t current_time, unsigned long time_difference, time_t *ts) {

	log_debug_info(DEBUGL_FUNCTIONS, 0, "adjust_timestamp_for_time_change()\n");

	/* we shouldn't do anything with epoch values */
	if (*ts == (time_t)0)
		return;

	/* we moved back in time... */
	if (last_time > current_time) {

		/* we can't precede the UNIX epoch */
		if (time_difference > (unsigned long)*ts)
			*ts = (time_t)0;
		else
			*ts = (time_t)(*ts - time_difference);
	}

	/* we moved into the future... */
	else
		*ts = (time_t)(*ts + time_difference);

	return;
}
