/*-
 * Copyright (c) 2003 Andrey Simonenko
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa_thresholds.c,v 1.1.4.3 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"
#include "parser.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_cmd.h"
#include "ipa_ctl.h"
#include "ipa_time.h"

#include "ipa_conf.h"
#include "ipa_log.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

u_int		nstatthresholds;	/* Number of static thresholds. */
u_int		ndynthresholds;		/* Number of dynamic thresholds. */

#ifdef WITH_THRESHOLDS

int		global_debug_threshold;	/* global { debug_threshold } */
int		global_debug_threshold_init; /* global { debug_threshold_init } */

int		global_threshold_type;	/* global { threshold_type } */
int		global_load_threshold;	/* global { load_threshold } */
u_int		global_threshold_time_width; /* global { threshold_time_width } */
const struct tevent *global_threshold_time_slice; /* global { threshold_time_slice } */

u_int		global_threshold_below_lim;	/* X:-:- global { threshold_balance } */
u_int		global_threshold_equal_lim;	/* -:X:- global { threshold_balance } */
u_int		global_threshold_above_lim;	/* -:-:X global { threshold_balance } */

ipa_mzone	*threshold_mzone;	/* Mzone for all struct threshold{}. */

const char *const threshold_event_msg[] = {
	"BELOW",	/* 0 */
	"EQUAL",	/* 1 */
	"ABOVE"		/* 2 */
};

void
set_thr_min_max(struct threshold *threshold)
{
	uint64_t thr, dev;

	thr = threshold->thr;

	dev = threshold->thr_dev_per_cent != 0 ?
	    uint64_per_cent(&thr, threshold->thr_dev_per_cent) :
	    threshold->thr_dev;

	threshold->thr_max = (thr > UINT64_MAX - dev) ?
	    UINT64_MAX : thr + dev;

	threshold->thr_min = thr < dev ?
	    UINT64_C(0) : thr - dev;
}

/*
 * Remove statistics from the latest slice, if positive counter or
 * negative counter overflows, then return -1, since this means
 * incorrect configuration.
 */
static int
threshold_flush_slice(const struct rule *rule, struct threshold *threshold)
{
	u_int	i;
	BITMAP_TYPE *bitword, bitmask;
	uint64_t chunk;

	i = threshold->cnt_slice_i;
	chunk = threshold->cnt_slice[i];
	threshold->cnt_slice[i] = UINT64_C(0);

	bitword = BIT_WORD(threshold->cnt_slice_sign, i);
	if (*bitword != 0) {
		/* Have to check bit. */
		bitmask = BIT_MASK(i);
		if (BIT_TEST(bitword, bitmask)) {
			BIT_CLEAR(bitword, bitmask);
			goto add_chunk;
		}
	}

	/* Subtract positive slice counter. */
	if (rule->debug_threshold)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: threshold_flush_slice: remove chunk %s from slice #%u",
		    rule->rule_name, threshold->threshold_name, cnt_to_buf(&chunk, threshold->cnt_type), i);
	if (threshold->cnt >= chunk)
		threshold->cnt -= chunk;
	else {
		chunk -= threshold->cnt;
		if (threshold->cnt_neg > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: threshold_flush_slice: negative counter overflowed",
			    rule->rule_name, threshold->threshold_name);
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: threshold_flush_slice: this means that something is wrong in configuration or in modules",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		threshold->cnt_neg += chunk;
		threshold->cnt = UINT64_C(0);
	}

	return 0;

add_chunk:
	/* Add absolute value of negative slice counter. */
	if (rule->debug_threshold)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: threshold_flush_slice: remove chunk -%s from slice #%u",
		    rule->rule_name, threshold->threshold_name, cnt_to_buf(&chunk, threshold->cnt_type), i);
	if (threshold->cnt_neg >= chunk)
		threshold->cnt_neg -= chunk;
	else {
		chunk -= threshold->cnt_neg;
		if (threshold->cnt > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: threshold_flush_slice: positive counter overflowed",
			    rule->rule_name, threshold->threshold_name);
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: threshold_flush_slice: this means that something is wrong in configuration or in modules",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		threshold->cnt += chunk;
		threshold->cnt_neg = UINT64_C(0);
	}

	return 0;
}

/*
 * Set threshold active or inactive in modules it uses.
 */
int
mod_set_threshold_active(const struct rule *rule, struct threshold *threshold,
    int active)
{
	if (threshold->is_active == active) {
		logmsgx(IPA_LOG_ERR, "internal error: mod_set_threshold_active(%s, %s, %d): threshold is already %s",
		    rule->rule_name, threshold->threshold_name, active, active_msg[active]);
		return -1;
	}

	if (debug_worktime)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: set threshold %s",
		    rule->rule_name, threshold->threshold_name, active_msg[active]);

	threshold->is_active = active;

	if (ac_set_threshold_active(rule, threshold, active) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: mod_set_threshold_active: ac_set_threshold_active(%d) failed",
		    rule->rule_name, threshold->threshold_name, active);
		return -1;
	}

	if (db_set_threshold_active(rule, threshold, active) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: mod_set_threshold_active: db_set_threshold_active(%d) failed",
		    rule->rule_name, threshold->threshold_name, active);
		return -1;
	}

	return 0;
}

static int
set_threshold_active(const struct rule *rule, struct threshold *threshold)
{
	u_int i, n;

	if (threshold->threshold_type & THRESHOLD_JUMP_OVER_INACTIVE) {
		i = ipa_tm_diff(&threshold->tm_updated, &threshold->tm_from);
		threshold->tm_from = curdate;
		ipa_tm_subtime(&threshold->tm_from, i);
		if (rule->debug_threshold)
			logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: set_threshold_active: jumping over inactive time interval",
			    rule->rule_name, threshold->threshold_name);
	} else {
		n = ipa_tm_diff(&curdate, &threshold->tm_updated) / threshold->time_slice->event_step;
		if (n >= threshold->cnt_slice_n)
			n = threshold->cnt_slice_n;

		if (rule->debug_threshold)
			logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: set_threshold_active: flushing %u slice%s, starting from slice #%u",
			    rule->rule_name, threshold->threshold_name, n, n == 1 ? "" : "s", threshold->cnt_slice_i);

		for (i = 0; i < n; ++i) {
			threshold->cnt_slice_i++;
			if (threshold->cnt_slice_i == threshold->cnt_slice_n)
				threshold->cnt_slice_i = 0;
			if (threshold_flush_slice(rule, threshold) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: set_threshold_active: threshold_flush_slice failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
		}
	}

	/* Set new time when to check this threshold again. */
	threshold->check_sec = threshold->time_slice->event_sec;

	return mod_set_threshold_active(rule, threshold, ACTIVE_FLAG);
}

/*
 * Add chunk to one threshold, if positive counter or slice counter
 * overflows, then return -1, since this means incorrect configuration.
 */
int
add_chunk_to_threshold(const struct rule *rule, struct threshold *threshold,
    const uint64_t *chunk_ptr)
{
	u_int	i;
	BITMAP_TYPE *bitword, bitmask;
	uint64_t chunk;

	chunk = *chunk_ptr;

	/* Add chunk to counter. */
	if (threshold->cnt_neg >= chunk)
		threshold->cnt_neg -= chunk;
	else {
		chunk -= threshold->cnt_neg;
		if (threshold->cnt > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: add_chunk_to_threshold: positive counter overflowed",
			    rule->rule_name, threshold->threshold_name);
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: add_chunk_to_threshold: this means that something is wrong in configuration or in modules",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		threshold->cnt += chunk;
		threshold->cnt_neg = UINT64_C(0);
		/* Restore chunk. */
		chunk = *chunk_ptr;
	}

	/* Add chunk to slice counter. */
	i = threshold->cnt_slice_i;
	if (rule->debug_threshold)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: add_chunk_to_threshold: add chunk %s to slice #%u",
		    rule->rule_name, threshold->threshold_name, cnt_to_buf(chunk_ptr, threshold->cnt_type), i);

	/*
	 * Usually statistics is positive, so let's make some
	 * optimization for usual situation.
	 */
	bitword = BIT_WORD(threshold->cnt_slice_sign, i);
	if (*bitword != 0) {
		/* Have to check bit. */
		bitmask = BIT_MASK(i);
		if (BIT_TEST(bitword, bitmask)) {
			/* Slice has negative statistics. */
			if (threshold->cnt_slice[i] > chunk)
				threshold->cnt_slice[i] -= chunk;
			else {
				threshold->cnt_slice[i] = chunk - threshold->cnt_slice[i];
				BIT_CLEAR(bitword, bitmask);
			}
			return 0;
		}
	}

	/* Slice has positive statistics. */
	if (threshold->cnt_slice[i] > UINT64_MAX - chunk) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: add_chunk_to_threshold: positive slice counter #%u overflowed",
		    rule->rule_name, threshold->threshold_name, i);
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: add_chunk_to_threshold: this means that something is wrong in configuration or in modules",
		    rule->rule_name, threshold->threshold_name);
		return -1;
	}
	threshold->cnt_slice[i] += chunk;

	return 0;
}

/*
 * Add chunk to every rule's threshold.
 */
int
add_chunk_to_thresholds(const struct rule *rule, const uint64_t *chunk_ptr)
{
	struct threshold *threshold;

	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (IS_ACTIVE(threshold))
			if (add_chunk_to_threshold(rule, threshold, chunk_ptr) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: add_chunk_to_thresholds: add_chunk_to_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
	return 0;
}

/*
 * Subtract chunk from one threshold.
 */
int
sub_chunk_from_threshold(const struct rule *rule, struct threshold *threshold,
    const uint64_t *chunk_ptr)
{
	u_int	i;
	BITMAP_TYPE *bitword, bitmask;
	uint64_t chunk;

	chunk = *chunk_ptr;

	/* Subtract chunk from counter. */
	if (threshold->cnt >= chunk)
		threshold->cnt -= chunk;
	else {
		chunk -= threshold->cnt;
		if (threshold->cnt_neg > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: sub_chunk_from_threshold: negative counter overflowed",
			    rule->rule_name, threshold->threshold_name);
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: sub_chunk_from_threshold: this means that something is wrong in configuration or in modules",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		threshold->cnt_neg += chunk;
		threshold->cnt = UINT64_C(0);
		/* Restore chunk. */
		chunk = *chunk_ptr;
	}

	/* Subtract chunk from slice counter. */
	i = threshold->cnt_slice_i;
	if (rule->debug_threshold)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: sub_chunk_from_threshold: subtract chunk %s from slice #%u",
		    rule->rule_name, threshold->threshold_name, cnt_to_buf(chunk_ptr, threshold->cnt_type), i);

	bitword = BIT_WORD(threshold->cnt_slice_sign, i);
	bitmask = BIT_MASK(i);
	if (BIT_TEST(bitword, bitmask)) {
		/* Slice has negative statistics. */
		if (threshold->cnt_slice[i] > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: sub_chunk_from_threshold: negative slice counter %u overflowed",
			    rule->rule_name, threshold->threshold_name, i);
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: sub_chunk_from_threshold: this means that something is wrong in configuration or in modules",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
		threshold->cnt_slice[i] += chunk;
	} else {
		/* Slice has positive statistics. */
		if (threshold->cnt_slice[i] >= chunk)
			threshold->cnt_slice[i] -= chunk;
		else {
			threshold->cnt_slice[i] = chunk - threshold->cnt_slice[i];
			BIT_SET(bitword, bitmask);
		}
	}

	return 0;
}

/*
 * Subtract chunk from every rule's threshold.
 */
int
sub_chunk_from_thresholds(const struct rule *rule, const uint64_t *chunk_ptr)
{
	struct threshold *threshold;

	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (IS_ACTIVE(threshold))
			if (sub_chunk_from_threshold(rule, threshold, chunk_ptr) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: sub_chunk_from_thresholds: sub_chunk_from_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
	return 0;
}

/*
 * This routine implementes "below_threshold", "equal_threshold",
 * "above_threshold" and part of "worktime" in "threshold" section.
 */
int
check_thresholds_events(const struct rule *rule, u_int *check_secp)
{
	u_int	i, check_sec;
	ipa_tm	tm_from;
	struct wpid *wpid;
	struct threshold *threshold;
	const struct worktime *wt;

	check_sec = SECONDS_IN_WEEK; /* Hint. */

	STAILQ_FOREACH(threshold, &rule->thresholds, link) {
		wt = threshold->worktime;
		if (IS_INACTIVE(threshold)) {
			/* Threshold is inactive. */
			if (wt->active_sec <= cursec) {
				/* It's time to make threshold active. */
				if (set_threshold_active(rule, threshold) < 0) {
					logmsgx(IPA_LOG_ERR, "check_thresholds_events: set_threshold_active failed");
					return -1;
				}
			} else {
				if (check_sec > wt->active_sec)
					check_sec = wt->active_sec;
				continue; /* and don't check any time events for threshold. */
			}
		}

		/* Here threshold is active. */
		if (threshold->check_sec <= cursec) {
			/* It's time to check threshold. */
			if (threshold->cnt > threshold->thr_max) {
				/* [  ] x */
				if (rule->debug_threshold)
					logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: above threshold, cnt %s, rest %u invocations",
					    rule->rule_name, threshold->threshold_name,
					    cnt_to_buf(&threshold->cnt, threshold->cnt_type),
					    threshold->above_cnt);
				if (threshold->above_cnt > 0) {
					/* Adjust number of invocations. */
					threshold->above_cnt--;
					threshold->equal_cnt = threshold->equal_lim;
					threshold->below_cnt = threshold->below_lim;

					/* Register event in modules. */
					if (ac_threshold_event(rule, threshold, IPA_THRESHOLD_EVENT_ABOVE) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: ac_threshold_event failed",
						    rule->rule_name, threshold->threshold_name);
						return -1;
					}

					/* Run commands. */
					if (!STAILQ_EMPTY(&threshold->above_threshold.list)) {
						if (rule->debug_exec)
							logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: above threshold: run commands (%s)",
							    rule->rule_name, threshold->threshold_name, sync_exec_msg[threshold->above_threshold.sync_exec]);
						wpid = &threshold->wpid;
						if (wpid->pid != 0)
							logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s (above_threshold): previously run process with PID %ld has not exited yet",
							    rule->rule_name, threshold->threshold_name, (long)wpid->pid);
						if (exec_cmd_list(rule, wpid, &threshold->above_threshold, "rule %s, threshold %s: above_threshold",
						    rule->rule_name, threshold->threshold_name) < 0) {
							logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: exec_cmd_list failed",
							    rule->rule_name, threshold->threshold_name);
							return -1;
						}
					}
				}
			} else if (threshold->cnt < threshold->thr_min) {
				/* x [  ] */
				if (rule->debug_threshold) {
					if (threshold->cnt_neg == UINT64_C(0))
						logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: below threshold, cnt %s, rest %u invocations",
						    rule->rule_name, threshold->threshold_name,
						    cnt_to_buf(&threshold->cnt, threshold->cnt_type),
						    threshold->below_cnt);
					else
						logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: below threshold, cnt -%s, rest %u invocations",
						    rule->rule_name, threshold->threshold_name,
						    cnt_to_buf(&threshold->cnt_neg, threshold->cnt_type),
						    threshold->below_cnt);
				}
				if (threshold->below_cnt > 0) {
					/* Adjust number of invocations. */
					threshold->below_cnt--;
					threshold->equal_cnt = threshold->equal_lim;
					threshold->above_cnt = threshold->above_lim;

					/* Register event in modules. */
					if (ac_threshold_event(rule, threshold, IPA_THRESHOLD_EVENT_BELOW) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: ac_threshold_event failed",
						    rule->rule_name, threshold->threshold_name);
						return -1;
					}

					/* Run commands. */
					if (!STAILQ_EMPTY(&threshold->below_threshold.list)) {
						if (rule->debug_exec)
							logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: below threshold: run commands (%s)",
							    rule->rule_name, threshold->threshold_name, sync_exec_msg[threshold->below_threshold.sync_exec]);
						wpid = &threshold->wpid;
						if (wpid->pid != 0)
							logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s (below_threshold): previously run process with PID %ld has not exited yet",
							    rule->rule_name, threshold->threshold_name, (long)wpid->pid);
						if (exec_cmd_list(rule, wpid, &threshold->below_threshold, "rule %s, threshold %s: below_threshold",
						    rule->rule_name, threshold->threshold_name) < 0) {
							logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: exec_cmd_list failed",
							    rule->rule_name, threshold->threshold_name);
							return -1;
						}
					}
				}
			} else {
				/* [ x ] */
				if (rule->debug_threshold)
					logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: equal threshold, cnt %s, rest %u invocations",
					    rule->rule_name, threshold->threshold_name,
					    cnt_to_buf(&threshold->cnt, threshold->cnt_type),
					    threshold->equal_cnt);
				if (threshold->equal_cnt) {
					/* Adjust number of invocations. */
					threshold->equal_cnt--;
					threshold->below_cnt = threshold->below_lim;
					threshold->above_cnt = threshold->above_lim;

					/* Register event in modules. */
					if (ac_threshold_event(rule, threshold, IPA_THRESHOLD_EVENT_EQUAL) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: ac_threshold_event failed",
						    rule->rule_name, threshold->threshold_name);
						return -1;
					}

					/* Run commands. */
					if (!STAILQ_EMPTY(&threshold->equal_threshold.list)) {
						if (rule->debug_exec)
							logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: equal threshold: run commands (%s)",
							    rule->rule_name, threshold->threshold_name, sync_exec_msg[threshold->equal_threshold.sync_exec]);
						wpid = &threshold->wpid;
						if (wpid->pid != 0)
							logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s (equal_threshold): previously run process with PID %ld has not exited yet",
							    rule->rule_name, threshold->threshold_name, (long)wpid->pid);
						if (exec_cmd_list(rule, wpid, &threshold->equal_threshold, "rule %s, threshold %s: equal_threshold",
						    rule->rule_name, threshold->threshold_name) < 0) {
							logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: exec_cmd_list failed",
							    rule->rule_name, threshold->threshold_name);
							return -1;
						}
					}
				}
			}

			/* Calculate next slice number, it is also the latest slice. */
			i = threshold->cnt_slice_i + 1;
			if (i == threshold->cnt_slice_n)
				i = 0;
			threshold->cnt_slice_i = i;

			/* Remove statistics from the latest slice. */
			if (threshold_flush_slice(rule, threshold) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: threshold_flush_slice failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}

			/* Set new time when to check this threshold again. */
			threshold->check_sec = threshold->time_slice->event_sec;

			/* Update threshold in the database. */
			tm_from = curdate;
			/* Threshold can be checked at 24:00:00. */
			if (newday_flag)
				fix_240000(&tm_from);
			ipa_tm_subtime(&tm_from, threshold->time_width);
			if (threshold->update_tm_from)
				threshold->tm_from = tm_from;
			else if (cmp_ipa_tm(&tm_from, &threshold->tm_from) > 0) {
				threshold->update_tm_from = 1;
				threshold->tm_from = tm_from;
			}

			threshold->tm_updated = curdate;
			if (db_update_threshold(rule, threshold) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: db_update_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
		} else if (need_check_flag) {
			if (db_update_threshold(rule, threshold) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: check_thresholds_events: db_update_threshold failed",
				    rule->rule_name, threshold->threshold_name);
				return -1;
			}
		}

		if (IS_INACTIVE(wt)) {
			/* Threshold became inactive. */
			if (!newday_flag) {
				if (set_threshold_inactive(rule, threshold) < 0) {
					logmsgx(IPA_LOG_ERR, "check_thresholds_events: set_threshold_inactive failed");
					return -1;
				}
				if (check_sec > wt->active_sec)
					check_sec = wt->active_sec;
			}
		} else {
			/* Threshold is still active. */
			if (check_sec > wt->inactive_sec)
				check_sec = wt->inactive_sec;
			if (check_sec > threshold->check_sec)
				check_sec = threshold->check_sec;
		}
	}

	*check_secp = check_sec;

	return 0;
}

/*
 * Init one threshold.
 */
static int
init_threshold(const struct rule *rule, struct threshold *threshold)
{
	int	error;
	u_int	i, n, old_slice_n, old_time_width;
	size_t	size;
	uint64_t chunk, cnt;
	const char *db_name;
	ipa_tm	tm_from;
	struct ipa_threshold_state oldstate, newstate;

	/*
	 * If init_threshold() is called second, third... time,
	 * then threshold can be inactive.
	 */
	if (IS_INACTIVE(threshold))
		if (set_threshold_active(rule, threshold) < 0) {
			logmsgx(IPA_LOG_ERR, "init_threshold: set_threshold_active failed");
			return -1;
		}

	threshold->cnt_neg = UINT64_C(0);
	threshold->update_tm_from = 0;

	/* Get number of statistics slices. */
	n = threshold->cnt_slice_n = threshold->time_width / threshold->time_slice->event_step;

	/* Init statistics slices. */
	size = n * sizeof *threshold->cnt_slice;
	if (threshold->cnt_slice == NULL)
		if ( (threshold->cnt_slice = mem_malloc(size, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_threshold: mem_malloc for statistics slices failed",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
	memset(threshold->cnt_slice, 0, size);

	/* Init statistics slices signs. */
	size = BITMAP_SIZE(n);
	if (threshold->cnt_slice_sign == NULL)
		if ( (threshold->cnt_slice_sign = mem_malloc(size, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_threshold: mem_malloc for statistics slices signs failed",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}
	memset(threshold->cnt_slice_sign, 0, size);

	error = 0;

	if (rule->debug_threshold_init)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: allocated %u statistics slices",
		    rule->rule_name, threshold->threshold_name, n);

	/*
	 * If we can't get current threshold state from the database,
	 * then let's use current "threshold" value.
	 */
	newstate.thr = threshold->thr;

	if (STAILQ_EMPTY(threshold->db_list)) {
		/* "null" database is used. */
		if (rule->debug_threshold_init)
			logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: create threshold with no database",
			    rule->rule_name, threshold->threshold_name);
		goto new_threshold_state;
	}

	/* Get threshold state from databases. */
	if (db_get_threshold_state(rule, threshold, &oldstate, &db_name) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_threshold: db_get_threshold_state failed",
		    rule->rule_name, threshold->threshold_name);
		return -1;
	}
	if (db_name == NULL) {
		logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: init_threshold: any database used for this threshold does not support db_get_threshold_state function",
		    rule->rule_name, threshold->threshold_name);
		goto new_threshold_state;
	}
	if (rule->debug_threshold_init)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: got threshold state from %s database",
		    rule->rule_name, threshold->threshold_name, db_name);

	/* Check if threshold has state in database. */
	if (oldstate.thr == UINT64_C(0)) {
		/* There isn't the old state of the threshold. */
		if (rule->debug_threshold_init)
			logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: database does not has threshold state, probably this is a new threshold",
			    rule->rule_name, threshold->threshold_name);
		goto new_threshold_state;
	}

	/* Check and init "threshold" parameter. */
	if (rule->debug_threshold_init && threshold->thr != oldstate.thr) {
		char buf[BUF_CNT_SIZE];

		strcpy(buf, cnt_to_buf(&threshold->thr, threshold->cnt_type));
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: config threshold %s, database threshold %s (will use %s value)",
		    rule->rule_name, threshold->threshold_name,
		    buf, cnt_to_buf(&oldstate.thr, threshold->cnt_type),
		    threshold->load_threshold ? "database" : "config");
	}

	if (threshold->load_threshold)
		newstate.thr = threshold->thr = oldstate.thr;

	/* Check tm_from and tm_updated. */
	if (check_ipa_tm(&oldstate.tm_from) < 0) {
		logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: init_threshold: wrong value of tm_from date: %s",
		    rule->rule_name, threshold->threshold_name, ipa_tm_to_buf(&oldstate.tm_from));
		goto new_threshold_state_error;
	}
	/* A threshold could be updated at 24:00:00, fix this date. */
	fix_240000(&oldstate.tm_updated);
	if (check_ipa_tm(&oldstate.tm_updated) < 0) {
		logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: init_threshold: wrong value of tm_updated date: %s",
		    rule->rule_name, threshold->threshold_name, ipa_tm_to_buf(&oldstate.tm_updated));
		goto new_threshold_state_error;
	}

	/* tm_from should be <= tm_updated. */
	if (cmp_ipa_tm(&oldstate.tm_updated, &oldstate.tm_from) < 0) {
		char *buf;

		buf = mem_strdup(ipa_tm_to_buf(&oldstate.tm_updated), m_tmp);
		logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: init_threshold: tm_updated date %s is less than tm_from date %s",
		    rule->rule_name, threshold->threshold_name,
		    buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&oldstate.tm_from));
		mem_free(buf, m_tmp);
		goto new_threshold_state_error;
	}

	if (cmp_ipa_tm(&oldstate.tm_updated, &curdate) > 0) {
		logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: init_threshold: tm_updated %s is greater than current time",
		    rule->rule_name, threshold->threshold_name, ipa_tm_to_buf(&oldstate.tm_updated));
		goto new_threshold_state_error;
	}

	old_time_width = ipa_tm_diff(&oldstate.tm_updated, &oldstate.tm_from);

	if (old_time_width != threshold->time_width) {
		char buf[BUF_TIME_SIZE];

		strcpy(buf, time_to_buf(old_time_width));
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: it seems that threshold_time_width is changed: %s -> %s",
		    rule->rule_name, threshold->threshold_name, buf, time_to_buf(threshold->time_width));
	}

	/* Old number of slices between tm_from and tm_updated. */
	old_slice_n = old_time_width / threshold->time_slice->event_step;

	/*
	 * Let it be one slice between tm_from and tm_updated,
	 * it is better, than simply forget about that statistics.
	 */
	if (old_slice_n == 0)
		old_slice_n = 1;

	/*
	 * One chunk between tm_from and tm_updated.
	 */
	chunk = oldstate.cnt / old_slice_n;

	if (threshold->threshold_type & THRESHOLD_JUMP_OVER_STOPPED) {
		/* Jump over time interval when ipa was stopped. */
		if (threshold->cnt_slice_n <= old_slice_n) {
			threshold->cnt_slice_i = 0;
			n = threshold->cnt_slice_n;
		} else {
			threshold->cnt_slice_i = old_slice_n;
			n = old_slice_n;
		}

		/* Calculate tm_from. */
		tm_from = curdate;
		if (old_time_width < threshold->time_width)
			ipa_tm_subtime(&tm_from, old_time_width);
		else
			ipa_tm_subtime(&tm_from, threshold->time_width);
		newstate.tm_from = tm_from;
	} else {
		/* Slide threshold's statistics over time interval. */
		if (ipa_tm_diff(&curdate, &oldstate.tm_updated) < threshold->time_width) {
			/* tm_from tm_updated curdate */

			/*
			 * Get number of slices between curdate and tm_updated.
			 * n is always less than cnt_slice_n, since curdate -
			 * oldstate.tm_updated is less than time_width.
			 */
			n = ipa_tm_diff(&curdate, &oldstate.tm_updated) / threshold->time_slice->event_step;

			/* Remember number of slices between curdate and tm_updated. */
			threshold->cnt_slice_i = n;

			/* Max possible number of slices between tm_from and tm_updated. */
			n = threshold->cnt_slice_n - n;

			/* n = min(n, old_slice_n). */
			if (n > old_slice_n)
				n = old_slice_n;

			/* Get number of next slice to use. */
			threshold->cnt_slice_i += n;
			if (threshold->cnt_slice_i == threshold->cnt_slice_n)
				threshold->cnt_slice_i = 0;

			/* Calculate tm_from. */
			tm_from = curdate;
			ipa_tm_subtime(&tm_from, threshold->time_width);
			newstate.tm_from = cmp_ipa_tm(&tm_from, &oldstate.tm_from) > 0 ?
			    tm_from : oldstate.tm_from;
		} else {
			/* Don't use previous threshold state, because it is stale. */
			newstate.tm_from = curdate;
			threshold->cnt_slice_i = n = 0;
			cnt = UINT64_C(0);
			goto skip_chunk_fill;
		}
	}

	/* Fill slices. */
	if (chunk != UINT64_C(0)) {
		/* Fill equal chunks. */
		for (i = 0; i < n; ++i)
			threshold->cnt_slice[i] = chunk;
		cnt = n * chunk;
		chunk = oldstate.cnt - cnt;
	} else {
		/* Not enough statistics for equal chunks. */
		cnt = UINT64_C(0);
		chunk = oldstate.cnt;
	}

	if (chunk != UINT64_C(0)) {
		/*
		 * Fill the rest of statistics, this is very approximately
		 * as all threshold's initialization.
		 */
		for (i = 0; i < n; ++i) {
			threshold->cnt_slice[i] += UINT64_C(1);
			cnt++;
			if (--chunk == UINT64_C(0))
				break;
		}
	}

skip_chunk_fill:

	set_thr_min_max(threshold);

	if (rule->debug_threshold_init) {
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: %u slice%s inited from database, counter is %s, current slice #%u",
		    rule->rule_name, threshold->threshold_name, n, n == 1 ? "" : "s", cnt_to_buf(&cnt, threshold->cnt_type), threshold->cnt_slice_i);
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: thr %"PRIu64", thr_min %"PRIu64", thr_max %"PRIu64,
		    rule->rule_name, threshold->threshold_name, threshold->thr,
		    threshold->thr_min, threshold->thr_max);
	}

	threshold->cnt = newstate.cnt = cnt;
	threshold->tm_from = newstate.tm_from;
	threshold->tm_updated = newstate.tm_updated = curdate;

	/* Remove statistics from the latest slice. */
	threshold_flush_slice(rule, threshold);

	if (rule->debug_threshold_init)
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: set threshold state",
		    rule->rule_name, threshold->threshold_name);

	if (db_set_threshold_state(rule, threshold, &newstate) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_threshold: db_set_threshold_state failed",
		    rule->rule_name, threshold->threshold_name);
		return -1;
	}

	return 0;

new_threshold_state_error:
	error = 1;

new_threshold_state:

	set_thr_min_max(threshold);

	threshold->tm_from = threshold->tm_updated = newstate.tm_from = newstate.tm_updated = curdate;
	threshold->cnt = newstate.cnt = UINT64_C(0);

	threshold->cnt_slice_i = 0;

	if (rule->debug_threshold_init) {
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: init_threshold: set new threshold state %s",
		    rule->rule_name, threshold->threshold_name, error ? "(fixing errors found in database)" : "");
		logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: thr %"PRIu64", thr_min %"PRIu64", thr_max %"PRIu64,
		    rule->rule_name, threshold->threshold_name, threshold->thr,
		    threshold->thr_min, threshold->thr_max);
	}

	if (db_set_threshold_state(rule, threshold, &newstate) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_threshold: db_set_threshold_state failed",
		    rule->rule_name, threshold->threshold_name);
		return -1;
	}
	return 0;
}

/*
 * Init all thresholds in one rule.
 */
int
init_thresholds(const struct rule *rule)
{
	struct threshold *threshold;

	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (init_threshold(rule, threshold) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, threshold %s: init_thresholds: init_threshold failed",
			    rule->rule_name, threshold->threshold_name);
			return -1;
		}

	return 0;
}

/*
 * Return pointer to threshold with the give name.
 */
struct threshold *
threshold_by_name(const struct rule *rule, const char *name)
{
	struct threshold *threshold;

	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (strcmp(name, threshold->threshold_name) == 0)
			return threshold;
	return NULL;
}

/*
 * This function is called from newday().
 */
int
thresholds_newday(struct rule *rule)
{
	u_int	check_sec;
	struct threshold *threshold;

	check_sec = SECONDS_IN_WEEK; /* Hint. */

	STAILQ_FOREACH(threshold, &rule->thresholds, link) {
		if (IS_INACTIVE(threshold->worktime)) {
			if (IS_ACTIVE(threshold))
				if (set_threshold_inactive(rule, threshold) < 0) {
					logmsgx(IPA_LOG_ERR, "thresholds_newday: set_threshold_inactive failed");
					return -1;
				}
			if (check_sec > threshold->worktime->active_sec)
				check_sec = threshold->worktime->active_sec;
		} else {
			if (IS_INACTIVE(threshold)) {
				if (set_threshold_active(rule, threshold) < 0) {
					logmsgx(IPA_LOG_ERR, "thresholds_newday: set_threshold_active failed");
					return -1;
				}
				/* threshold->check_sec was updated in set_threshold_active(). */
			} else
				threshold->check_sec = threshold->time_slice->event_sec;
			if (check_sec > threshold->worktime->inactive_sec)
				check_sec = threshold->worktime->inactive_sec;
			if (check_sec > threshold->check_sec)
				check_sec = threshold->check_sec;
		}
		if (rule->check_sec > check_sec)
			rule->check_sec = check_sec;
	}

	return 0;
}

/*
 * Copy all thresholds from list_src to rule.
 */
int
copy_thresholds(struct rule *rule, const struct thresholds_list *list_src, int dyn_flag)
{
	struct threshold *threshold_dst;
	const struct threshold *threshold_src;

	STAILQ_FOREACH(threshold_src, list_src, link) {
		if ( (threshold_dst = mzone_alloc(threshold_mzone)) == NULL) {
			xlogmsgx(IPA_LOG_ERR, "copy_thresholds: mzone_alloc failed");
			return -1;
		}

		/* Copy settings from source threshold. */
		*threshold_dst = *threshold_src;

		/* Init fields which are pointers to memory, which can't be shared. */
		init_cmds_in_threshold(threshold_dst);

		threshold_dst->rule = rule;

		/* Link just allocated threshold to rule. */
		STAILQ_INSERT_TAIL(&rule->thresholds, threshold_dst, link);

		if (dyn_flag)
			++ndynthresholds;
		else
			++nstatthresholds;

		/*
		 * If something goes wrong with memory allocation,
		 * previous settings will allow to deinit current rule.
		 */

		if (copy_cmd_list(rule, &threshold_dst->below_threshold, &threshold_src->below_threshold) < 0)
			return -1;

		if (copy_cmd_list(rule, &threshold_dst->equal_threshold, &threshold_src->equal_threshold) < 0)
			return -1;

		if (copy_cmd_list(rule, &threshold_dst->above_threshold, &threshold_src->above_threshold) < 0)
			return -1;

		if (copy_cmd_list(rule, &threshold_dst->rc[RC_STARTUP], &threshold_src->rc[RC_STARTUP]) < 0)
			return -1;

		if (copy_cmd_list(rule, &threshold_dst->rc[RC_SHUTDOWN], &threshold_src->rc[RC_SHUTDOWN]) < 0)
			return -1;

		threshold_dst->wpid.u.threshold = threshold_dst;
	}

	return 0;
}

/*
 * Release memory used by a list of thresholds, free_mask determines
 * which part of threshold{} structure to free.
 */
void
free_thresholds(u_int free_mask, struct thresholds_list *thresholds, int dyn_flag)
{
	struct threshold *threshold, *threshold_next;

	for (threshold = STAILQ_FIRST(thresholds); threshold != NULL; threshold = threshold_next) {
		threshold_next = STAILQ_NEXT(threshold, link);
		if (dyn_flag)
			--ndynthresholds;
		if (free_mask & RULE_FREE_THRESHOLDS) {
			mem_free(threshold->threshold_name, m_anon);
			mem_free(threshold->threshold_info, m_parser);
		}
		mem_free(threshold->cnt_slice, m_anon);
		mem_free(threshold->cnt_slice_sign, m_anon);
		free_cmd_list(&threshold->below_threshold);
		free_cmd_list(&threshold->equal_threshold);
		free_cmd_list(&threshold->above_threshold);
		free_cmd_list(&threshold->rc[RC_STARTUP]);
		free_cmd_list(&threshold->rc[RC_SHUTDOWN]);
		mzone_free(threshold_mzone, threshold);
	}
}

void
init_cmds_in_threshold(struct threshold *threshold)
{
	init_cmd_list(&threshold->below_threshold);
	init_cmd_list(&threshold->equal_threshold);
	init_cmd_list(&threshold->above_threshold);
	init_cmd_list(&threshold->rc[RC_STARTUP]);
	init_cmd_list(&threshold->rc[RC_SHUTDOWN]);
}

void
set_sync_exec_in_threshold(struct threshold *threshold)
{
	if (threshold->below_threshold.sync_exec < 0)
		threshold->below_threshold.sync_exec = 0;
	if (threshold->equal_threshold.sync_exec < 0)
		threshold->equal_threshold.sync_exec = 0;
	if (threshold->above_threshold.sync_exec < 0)
		threshold->above_threshold.sync_exec = 0;
	if (threshold->rc[RC_STARTUP].sync_exec < 0)
		threshold->rc[RC_STARTUP].sync_exec = 1;
	if (threshold->rc[RC_SHUTDOWN].sync_exec < 0)
		threshold->rc[RC_SHUTDOWN].sync_exec = 1;
}
#endif /* WITH_THRESHOLDS */


syntax highlighted by Code2HTML, v. 0.9.1