/*-
 * Copyright (c) 1999-2004 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_rules.c,v 1.2.2.3 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

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

#include "ipa_mod.h"

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

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_ctl.h"
#include "ipa_cmd.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"

#ifndef RULES_HASH_BUCKETS
# define RULES_HASH_BUCKETS 128		/* Must be power of 2. */
#endif

u_int		nstatrules;		/* Number of static rules. */

int		keep_rules_order;	/* keep_rules_order parameter. */

ipa_mzone	*rule_mzone;		/* Mzone for all struct rule{}. */

#ifdef WITH_RULES
int		has_ac_gather;		/* Non-zero if there is at least one "ac_gather_*" parameter. */
ipa_mzone	*ac_gather_rev_mzone;	/* Mzone for all struct ac_gather_rev{}. */
struct ac_gather_list ac_gather_list;	/* List of all rules with "ac_gather_*" parameters. */
#endif

ipa_mzone	*rulepat_mzone;		/* Mzone for all struct rulepat{}. */
struct rulepats_list rulepats_list;	/* List of all rulepats. */

u_int	rules_inactive_check_sec;	/* Time when to check inactive rules queue. */

struct rules_list rules_list =
	TAILQ_HEAD_INITIALIZER(rules_list);	/* List of all rules. */
struct rules_queue rules_active =
	TAILQ_HEAD_INITIALIZER(rules_active);	/* Active rules queue. */
struct rules_queue rules_inactive =
	TAILQ_HEAD_INITIALIZER(rules_inactive);	/* Inactive rules queue. */

LIST_HEAD(rules_hash, rule);

static struct rules_hash rules_hash[RULES_HASH_BUCKETS];

/*
 * Add chunk to the rule's counter and to counters of its limits and
 * thresholds.  Check if there is statistics in rule's negative
 * counter, check for counter overflowing.  Add chunk to all rules
 * that are listed in this rule's ac_gather_rev list.
 */
int
add_chunk_to_rule(struct rule *rule, const uint64_t *chunk_ptr)
{
	uint64_t chunk;

	chunk = *chunk_ptr;

	if (rule->cnt_neg >= chunk)
		rule->cnt_neg -= chunk;
	else {
		chunk -= rule->cnt_neg;
		rule->cnt_neg = UINT64_C(0);
		if (rule->cnt <= UINT64_MAX - chunk)
			rule->cnt += chunk;
		else {
			if (db_update_rule(rule, &uint64_max) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: db_update_rule failed",
				    rule->rule_name);
				return -1;
			}
			rule->cnt = UINT64_MAX - rule->cnt;
			rule->cnt = chunk - rule->cnt;
			if (db_append_rule(rule, &rule->cnt, 0) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: db_append_rule failed",
				    rule->rule_name);
				return -1;
			}
		}
	}

#ifdef WITH_LIMITS
	if (!STAILQ_EMPTY(&rule->limits))
		if (add_chunk_to_limits(rule, chunk_ptr) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: add_chunk_to_limits failed",
			    rule->rule_name);
			return -1;
		}
#endif

#ifdef WITH_THRESHOLDS
	if (!STAILQ_EMPTY(&rule->thresholds))
		if (add_chunk_to_thresholds(rule, chunk_ptr) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: add_chunk_to_thresholds failed",
			    rule->rule_name);
			return -1;
		}
#endif

#ifdef WITH_RULES
	if (!SLIST_EMPTY(&rule->ac_gather_rev)) {
		const struct ac_gather_rev *ac_gather_rev;

		SLIST_FOREACH(ac_gather_rev, &rule->ac_gather_rev, link)
			if (IS_ACTIVE(ac_gather_rev->rule)) {
				if (ac_gather_rev->addition) {
					if (add_chunk_to_rule(ac_gather_rev->rule, chunk_ptr) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: add_chunk_to_rule for %s failed",
						    rule->rule_name, ac_gather_rev->rule->rule_name);
						return -1;
					}
				} else {
					if (sub_chunk_from_rule(ac_gather_rev->rule, chunk_ptr) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s: add_chunk_to_rule: sub_chunk_to_rule for %s failed",
						    rule->rule_name, ac_gather_rev->rule->rule_name);
						return -1;
					}
				}
			}
	}
#endif

	return 0;
}

/*
 * Subtract chunk from the rule's counter and from counters of its
 * limits and thresholds.  Check if there is statistics in negative
 * rule's counter, check for counter overflowing.  Subtract chunk from
 * all rules that are listed in this rule's ac_gather_rev list.
 */
int
sub_chunk_from_rule(struct rule *rule, const uint64_t *chunk_ptr)
{
	uint64_t chunk;

	chunk = *chunk_ptr;

	if (rule->cnt >= chunk)
		rule->cnt -= chunk;
	else {
		chunk -= rule->cnt;
		if (rule->cnt_neg > UINT64_MAX - chunk) {
			logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_from_rule: negative statistics counter overflowed",
			    rule->rule_name);
			logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_from_rule: this means that something is wrong in configuration or in modules",
			    rule->rule_name);
			return -1;
		}
		rule->cnt_neg += chunk;
		rule->cnt = UINT64_C(0);
	}

#ifdef WITH_LIMITS
	if (!STAILQ_EMPTY(&rule->limits))
		if (sub_chunk_from_limits(rule, chunk_ptr) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_from_rule: sub_chunk_from_limits failed",
			    rule->rule_name);
			return -1;
		}
#endif

#ifdef WITH_THRESHOLDS
	if (!STAILQ_EMPTY(&rule->thresholds))
		if (sub_chunk_from_thresholds(rule, chunk_ptr) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_from_rule: sub_chunk_from_thresholds failed",
			    rule->rule_name);
			return -1;
		}
#endif

#ifdef WITH_RULES
	if (!SLIST_EMPTY(&rule->ac_gather_rev)) {
		const struct ac_gather_rev *ac_gather_rev;

		SLIST_FOREACH(ac_gather_rev, &rule->ac_gather_rev, link)
			if (IS_ACTIVE(ac_gather_rev->rule)) {
				if (ac_gather_rev->addition) {
					if (sub_chunk_from_rule(ac_gather_rev->rule, chunk_ptr) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_to_rule: sub_chunk_to_rule for %s failed",
						    rule->rule_name, ac_gather_rev->rule->rule_name);
						return -1;
					}
				} else {
					if (add_chunk_to_rule(ac_gather_rev->rule, chunk_ptr) < 0) {
						logmsgx(IPA_LOG_ERR, "rule %s: sub_chunk_to_rule: add_chunk_to_rule for %s failed",
						    rule->rule_name, ac_gather_rev->rule->rule_name);
						return -1;
					}
				}
			}
	}
#endif

	return 0;
}

/*
 * Add a rule to rules_active queue.
 */
void
queue_active_rule(struct rule *rule1)
{
	struct rule *rule2;

#ifdef WITH_AUTORULES
	/* Dynamic rules are always linked to the head. */
	if (RULE_IS_DYNAMIC(rule1)) {
		TAILQ_INSERT_HEAD(&rules_active, rule1, queue);
		return;
	}
#endif

	if (keep_rules_order)
		/* Keep rules order or have ac_gather_*. */
		TAILQ_FOREACH(rule2, &rules_active, queue)
			if (rule1->orderno > rule2->orderno) {
				TAILQ_INSERT_BEFORE(rule2, rule1, queue);
				return;
			}

	/* Don't keep rules order or no dependency was found. */
	TAILQ_INSERT_TAIL(&rules_active, rule1, queue);
}

#ifdef WITH_RULES
/*
 * Add all static rules to active rules queue, this function is called
 * after configuration/reconfiguration.
 */
void
init_rules_active(void)
{
	u_int	orderno;
	struct rule *rule1, *rule2;
	const struct ac_gather_rev *ac_gather_rev;

	TAILQ_FOREACH(rule1, &rules_list, list) {
		rule1->is_active = ACTIVE_FLAG;
		if (!keep_rules_order && has_ac_gather) {
			TAILQ_FOREACH(rule2, &rules_active, queue) {
				/*
				 * The given rule must be below than rule, which
				 * has this rule in its ac_gather_rev.
				 */
				SLIST_FOREACH(ac_gather_rev, &rule2->ac_gather_rev, link)
					if (ac_gather_rev->rule == rule1)
						goto next_rule2;
				/*
				 * Rules listed in ac_gather_rev of the given rule
				 * must below than this rule.
				 */
				SLIST_FOREACH(ac_gather_rev, &rule1->ac_gather_rev, link)
					if (ac_gather_rev->rule == rule2) {
						TAILQ_INSERT_BEFORE(rule2, rule1, queue);
						goto next_rule1;
					}
next_rule2:
				;
			}
		}
		/*
		 * Keep rules order or no dependency was found,
		 * add it to the end of the active rules queue.
		 * Side effect: by default static rules are initially
		 * placed in rules_active queue in the original order.
		 */
		TAILQ_INSERT_TAIL(&rules_active, rule1, queue);
next_rule1:
		;
	}

	/* Let's combine these two flags for queue_active_rule(). */
	keep_rules_order |= has_ac_gather;

	if (keep_rules_order) {
		orderno = 0;
		TAILQ_FOREACH_REVERSE(rule1, &rules_active, rules_queue, queue)
			rule1->orderno = orderno++;
	}
}
#endif /* WITH_RULES */

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

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

	rule->is_active = active;

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

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

	return 0;
}

static void
show_inactive_rules(void)
{
	const struct rule *rule;

	logmsgx(IPA_LOG_INFO, "inactive rules after sorting:");
	TAILQ_FOREACH(rule, &rules_inactive, queue)
		logmsgx(IPA_LOG_INFO, "    active %s, rule %s",
		    sec_to_buf(rule->worktime->active_sec), rule->rule_name);
}

/*
 * Move a rule from the active rules queue to the inactive rules queue.
 */
int
set_rule_inactive(struct rule *rule1)
{
	struct rule *rule2;

	/* Inform modules that rule becomes inactive. */
	if (mod_set_rule_active(rule1, INACTIVE_FLAG) < 0)
		return -1;

	/* Remove the rule from the active rules queue. */
	TAILQ_REMOVE(&rules_active, rule1, queue);

	/* Add the rule to the inactive rules queue, keep that queue sorted. */
	if (rule1->worktime->active_sec != SECONDS_IN_WEEK) {
		/* Will be active in current day. */
		TAILQ_FOREACH(rule2, &rules_inactive, queue)
			if (rule1->worktime->active_sec < rule2->worktime->active_sec) {
				TAILQ_INSERT_BEFORE(rule2, rule1, queue);
				goto out;
			}
	}
	TAILQ_INSERT_TAIL(&rules_inactive, rule1, queue);

out:
	if (debug_worktime)
		show_inactive_rules();

	rules_inactive_check_sec = TAILQ_FIRST(&rules_inactive)->worktime->active_sec;

	return 0;
}

/*
 * Move a rule from the inactive rules queue to the active rules queue.
 */
int
set_rule_active(struct rule *rule)
{
	/* Inform modules that rule becomes active. */
	if (mod_set_rule_active(rule, ACTIVE_FLAG) < 0)
		return -1;

	/* Get new stat for the rule. */
	rule->newstat = 1;

	/* Remove the rule from the inactive rules queue. */
	TAILQ_REMOVE(&rules_inactive, rule, queue);

	/* Add the rule to the active rules queue. */
	queue_active_rule(rule);

	/* Get new value of rules_inactive_check_sec. */
	rules_inactive_check_sec = TAILQ_EMPTY(&rules_inactive) ?
	    SECONDS_IN_WEEK : TAILQ_FIRST(&rules_inactive)->worktime->active_sec;

	/* Append new record for the rule. */
	if (db_append_rule(rule, &uint64_zero, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: set_rule_active: db_append_rule failed",
		    rule->rule_name);
		return -1;
	}

	rule->check_sec = cursec;

	return 0;
}

/*
 * Sort inactive rules queue;
 */
void
sort_inactive_rules(void)
{
	struct rule *rule1, *rule1_next, *rule2;

	if (TAILQ_EMPTY(&rules_inactive))
		rules_inactive_check_sec = SECONDS_IN_WEEK;
	else {
		rule1 = TAILQ_FIRST(&rules_inactive);
		TAILQ_INIT(&rules_inactive);
		for (; rule1 != NULL; rule1 = rule1_next) {
			rule1_next = TAILQ_NEXT(rule1, queue);
			if (rule1->worktime->active_sec != SECONDS_IN_WEEK) {
				/* Will be active in current day. */
				TAILQ_FOREACH(rule2, &rules_inactive, queue)
					if (rule1->worktime->active_sec <= rule2->worktime->active_sec) {
						TAILQ_INSERT_BEFORE(rule2, rule1, queue);
						goto next;
					}
			}
			TAILQ_INSERT_TAIL(&rules_inactive, rule1, queue);
next:
			;
		}
		rules_inactive_check_sec = TAILQ_FIRST(&rules_inactive)->worktime->active_sec;
	}

	if (debug_worktime)
		show_inactive_rules();
}

/*
 * Check if time to make some rule active come.  This function
 * is called when rules_inactive_check_sec <= cursec.
 */
int
check_inactive_rules(void)
{
	struct rule *rule, *rule_next;

	for (rule = TAILQ_FIRST(&rules_inactive); rule != NULL; rule = rule_next)
		if (rule->worktime->active_sec <= cursec) {
			/* It's time to make a rule active. */
			rule_next = TAILQ_NEXT(rule, queue);
			if (set_rule_active(rule) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s: check_inactive_rules: set_rule_active failed",
				    rule->rule_name);
				return -1;
			}
		} else {
			/* Set next time of invocation of this function. */
			rules_inactive_check_sec = rule->worktime->active_sec;
			return 0;
		}

	/* No more inactive rules. */
	rules_inactive_check_sec = SECONDS_IN_WEEK;
	return 0;
}

/*
 * Log messages about lost negative statistics.
 */
static void
check_cnt_neg(const struct rule *rule, int only_for_limits)
{
#ifdef WITH_LIMITS
	const struct limit *limit;
#endif
#ifdef WITH_THRESHOLDS
	const struct threshold *threshold;
#endif

	if (!only_for_limits && rule->cnt_neg > UINT64_C(0))
		logmsgx(IPA_LOG_WARNING, "rule %s: statistics counter is -%"PRIu64", this statistics is lost",
		    rule->rule_name, rule->cnt_neg);

#ifdef WITH_LIMITS
	STAILQ_FOREACH(limit, &rule->limits, link)
		if (limit->cnt_neg > UINT64_C(0))
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: statistics counter is -%s, this statistics is lost",
			    rule->rule_name, limit->limit_name, cnt_to_buf(&limit->cnt_neg, limit->cnt_type));
#endif

#ifdef WITH_THRESHOLDS
	STAILQ_FOREACH(threshold, &rule->thresholds, link)
		if (threshold->cnt_neg > UINT64_C(0))
			logmsgx(IPA_LOG_WARNING, "rule %s, threshold %s: statistics counter is -%s, this statistics is lost",
			    rule->rule_name, threshold->threshold_name, cnt_to_buf(&threshold->cnt_neg, threshold->cnt_type));
#endif
}

#ifdef WITH_RULES
/*
 * Init one static rule.
 */
static int
init_stat_rule(struct rule *rule)
{
	rule->newstat = 1;

	if (rule->append_tevent == NULL)
		rule->append_sec = SECONDS_IN_WEEK;

	if (ac_init_statrule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: init_stat_rule: ac_init_statrule failed",
		    rule->rule_name);
		return -1;
	}

	if (db_init_statrule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: init_stat_rule: db_init_statrule failed",
		    rule->rule_name);
		return -1;
	}

	return 0;
}
#endif /* WITH_RULES */

/*
 * Run startup or shutdown commands for a rule, its limits,
 * sublimits and thresholds.
 */
int
run_rule_cmds(struct rule *rule, int x)
{
	const char *sect;
	struct cmds_rule *rcmds;
#ifdef WITH_LIMITS
	int	if_any_reached, if_all_reached;
	struct limit *limit;
	struct cmds_limit *lcmds;
#endif
#ifdef WITH_SUBLIMITS
	struct sublimit	*sublimit;
#endif
#ifdef WITH_THRESHOLDS
	struct cmd_list *cmdl;
	struct threshold *threshold;
#endif

	sect = x == RC_STARTUP ? "startup" : "shutdown";

	rcmds = &rule->rc[x];
	if (!STAILQ_EMPTY(&rcmds->cmdl.list)) {
		if (rule->debug_exec)
			logmsgx(IPA_LOG_INFO, "rule %s: run %s commands (%s)",
			    rule->rule_name, sect, sync_exec_msg[rcmds->cmdl.sync_exec]);
		if (exec_cmd_list(rule, (struct wpid *)NULL, &rcmds->cmdl,
		    "rule %s { %s {}}", rule->rule_name, sect) < 0)
			return -1;
	}
	if (x == RC_STARTUP)
		free_cmds_rule(rcmds);
#ifdef WITH_LIMITS
	if_any_reached = 0;
	if_all_reached = 1;
	STAILQ_FOREACH(limit, &rule->limits, link)
		if (IS_REACHED(limit))
			if_any_reached = 1;
		else
			if_all_reached = 0;
	if (if_any_reached) {
		if (!STAILQ_EMPTY(&rcmds->cmdl_if_any_reached.list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s: run %s if_any_reached commands (%s)",
				    rule->rule_name, sect, sync_exec_msg[rcmds->cmdl_if_any_reached.sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, &rcmds->cmdl_if_any_reached,
			    "rule %s { %s { if_any_reached {}}}", rule->rule_name, sect) < 0)
				return -1;
		}
	} else {
		if (!STAILQ_EMPTY(&rcmds->cmdl_if_any_not_reached.list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s: run %s if_any_not_reached commands (%s)",
				    rule->rule_name, sect, sync_exec_msg[rcmds->cmdl_if_any_not_reached.sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, &rcmds->cmdl_if_any_not_reached,
			    "rule %s { %s { if_any_not_reached {}}}", rule->rule_name, sect) < 0)
				return -1;
		}
	}
	if (if_all_reached) {
		if (!STAILQ_EMPTY(&rcmds->cmdl_if_all_reached.list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s: run %s if_all_reached commands (%s)",
				    rule->rule_name, sect, sync_exec_msg[rcmds->cmdl_if_all_reached.sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, &rcmds->cmdl_if_all_reached,
			    "rule %s { %s { if_all_reached {}}}", rule->rule_name, sect) < 0)
				return -1;
		}
	} else {
		if (!STAILQ_EMPTY(&rcmds->cmdl_if_all_not_reached.list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s: run %s if_all_not_reached commands (%s)",
				    rule->rule_name, sect, sync_exec_msg[rcmds->cmdl_if_all_not_reached.sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, &rcmds->cmdl_if_all_not_reached,
			    "rule %s { %s { if_all_not_reached {}}}", rule->rule_name, sect) < 0)
				return -1;
		}
	}
	STAILQ_FOREACH(limit, &rule->limits, link) {
		lcmds = &limit->rc[x];
		if (!STAILQ_EMPTY(&lcmds->cmdl.list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: run %s commands (%s)",
				    rule->rule_name, limit->limit_name, sect, sync_exec_msg[lcmds->cmdl.sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl,
			    "rule %s { limit %s { %s {}}}", rule->rule_name, limit->limit_name, sect) < 0)
				return -1;
		}
		if (IS_REACHED(limit)) {
			if (!STAILQ_EMPTY(&lcmds->cmdl_if_reached.list)) {
				if (rule->debug_exec)
					logmsgx(IPA_LOG_INFO, "rule %s, limit %s: run %s if_reached commands (%s)",
					    rule->rule_name, limit->limit_name, sect, sync_exec_msg[lcmds->cmdl_if_reached.sync_exec]);
				if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl_if_reached,
				    "rule %s { limit %s { %s { if_reached{}}}}", rule->rule_name, limit->limit_name, sect) < 0)
					return -1;
			}
			if (ac_limit_event(rule, limit, x == RC_STARTUP ? IPA_LIMIT_EVENT_STARTUP_IF_REACHED : IPA_LIMIT_EVENT_SHUTDOWN_IF_REACHED) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: run_rule_cmds: ac_limit_event failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		} else {
			if (!STAILQ_EMPTY(&lcmds->cmdl_if_not_reached.list)) {
				if (rule->debug_exec)
					logmsgx(IPA_LOG_INFO, "rule %s, limit %s (%s, if_not_reached): run commands (%s)",
					    rule->rule_name, limit->limit_name, sect, sync_exec_msg[lcmds->cmdl_if_not_reached.sync_exec]);
				if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl_if_not_reached,
				    "rule %s { limit %s { %s { if_not_reached{}}}}", rule->rule_name, limit->limit_name, sect) < 0)
					return -1;
			}
			if (ac_limit_event(rule, limit, x == RC_STARTUP ? IPA_LIMIT_EVENT_STARTUP_IF_NOT_REACHED : IPA_LIMIT_EVENT_SHUTDOWN_IF_NOT_REACHED) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: run_rule_cmds: ac_limit_event failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		}
		if (x == RC_STARTUP)
			free_cmds_limit(lcmds);
# ifdef WITH_SUBLIMITS
		STAILQ_FOREACH(sublimit, &limit->sublimits, link) {
			lcmds = &sublimit->rc[x];
			if (!STAILQ_EMPTY(&lcmds->cmdl.list)) {
				if (rule->debug_exec)
					logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s: run %s commands (%s)",
					    rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect, sync_exec_msg[lcmds->cmdl.sync_exec]);
				if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl,
				    "rule %s { limit %s { sublimit %s { %s {}}}}", rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect) < 0)
					return -1;
			}
			if (IS_REACHED(sublimit)) {
				if (!STAILQ_EMPTY(&lcmds->cmdl_if_reached.list)) {
					if (rule->debug_exec)
						logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s: run %s if_reached commands (%s)",
						    rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect, sync_exec_msg[lcmds->cmdl_if_reached.sync_exec]);
					if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl_if_reached,
					    "rule %s { limit %s { sublimit %s { %s { if_reached{}}}}}", rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect) < 0)
						return -1;
				}
			} else {
				if (!STAILQ_EMPTY(&lcmds->cmdl_if_not_reached.list)) {
					if (rule->debug_exec)
						logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s (%s, if_not_reached): run commands (%s)",
						    rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect, sync_exec_msg[lcmds->cmdl_if_not_reached.sync_exec]);
					if (exec_cmd_list(rule, (struct wpid *)NULL, &lcmds->cmdl_if_not_reached,
					    "rule %s { limit %s { sublimit %s { %s { if_not_reached{}}}}}", rule->rule_name, limit->limit_name, sublimit->sublimit_name, sect) < 0)
						return -1;
				}
			}
			if (x == RC_STARTUP)
				free_cmds_limit(lcmds);
		}
# endif /* WITH_SUBLIMITS */
	}
#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
	STAILQ_FOREACH(threshold, &rule->thresholds, link) {
		cmdl = &threshold->rc[x];
		if (!STAILQ_EMPTY(&cmdl->list)) {
			if (rule->debug_exec)
				logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: run %s commands (%s)",
				    rule->rule_name, threshold->threshold_name, sect, sync_exec_msg[cmdl->sync_exec]);
			if (exec_cmd_list(rule, (struct wpid *)NULL, cmdl,
			    "rule %s { threshold %s { %s {}}}", rule->rule_name, threshold->threshold_name, sect) < 0)
				return -1;
			if (x == RC_STARTUP)
				free_cmd_list(cmdl);
		}
	}
#endif /* WITH_THRESHOLDS */

	return 0;
}

/*
 * Run startup or shutdown commands for rules, its limits,
 * sublimits and thresholds.
 */
int
run_rules_cmds(int x)
{
	struct rule *rule;

	TAILQ_FOREACH(rule, &rules_list, list)
		if (run_rule_cmds(rule, x) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: run_rules_cmds: run_rule_cmds failed",
			    rule->rule_name);
			return -1;
		}

	return 0;
}

#ifdef WITH_RULES
/*
 * Build reverse list for "ac_gather_*" parameters: if rule1 matches
 * rule2's "ac_gather_*" parameters, then rule2 is added to rule1's
 * ac_gather_rev list.
 */
int
init_ac_gather_rev(struct rule *rule1)
{
	struct rule *rule2;
	struct ac_gather_rev *ac_gather_rev;

	SLIST_FOREACH(rule2, &ac_gather_list, ac_gather_link)
		if (rule1 != rule2) {
			if (rule2->ac_gather_add_pat != NULL) {
				if (regexec(&rule2->ac_gather_add_reg, rule1->rule_name, 0, (regmatch_t *)NULL, 0) == 0) {
					if ( (ac_gather_rev = mzone_alloc(ac_gather_rev_mzone)) == NULL) {
						xlogmsgx(IPA_LOG_ERR, "init_ac_gather_rev: mzone_alloc failed");
						return -1;
					}
					ac_gather_rev->addition = 1;
					ac_gather_rev->rule = rule2;
					SLIST_INSERT_HEAD(&rule1->ac_gather_rev, ac_gather_rev, link);
				}
			}
			if (rule2->ac_gather_sub_pat != NULL) {
				if (regexec(&rule2->ac_gather_sub_reg, rule1->rule_name, 0, (regmatch_t *)NULL, 0) == 0) {
					if ( (ac_gather_rev = mzone_alloc(ac_gather_rev_mzone)) == NULL) {
						xlogmsgx(IPA_LOG_ERR, "init_ac_gather_rev: mzone_alloc failed");
						return -1;
					}
					ac_gather_rev->addition = 0;
					ac_gather_rev->rule = rule2;
					SLIST_INSERT_HEAD(&rule1->ac_gather_rev, ac_gather_rev, link);
				}
			}
		}
	return 0;
}

/*
 * Release memory used by ac_gather_rev list for one rule.
 */
static void
deinit_ac_gather_rev(struct rule *rule)
{
	struct ac_gather_rev *ac_gather_rev, *ac_gather_rev_next;

	ac_gather_rev = SLIST_FIRST(&rule->ac_gather_rev);
	while (ac_gather_rev != NULL) {
		ac_gather_rev_next = SLIST_NEXT(ac_gather_rev, link);
		mzone_free(ac_gather_rev_mzone, ac_gather_rev);
		ac_gather_rev = ac_gather_rev_next;
	}
}
#endif /* WITH_RULES */

/*
 * Init all rules and their limits and thresholds.  This function is
 * called for initial initialization of static rules (first_call == 0),
 * or when there are problems with time.
 */
int
init_rules(int first_call)
{
	struct rule *rule;

	TAILQ_FOREACH(rule, &rules_list, list) {
#ifdef WITH_RULES
		if (first_call) {
			if (init_stat_rule(rule) < 0) {
				logmsgx(IPA_LOG_ERR, "init_rules: init_stat_rule failed");
				return -1;
			}
		} else
#endif
			check_cnt_neg(rule, 1);
#ifdef WITH_LIMITS
		if (init_limits(rule) < 0) {
			logmsgx(IPA_LOG_ERR, "init_rules: init_limits failed");
			return -1;
		}
#endif
#ifdef WITH_THRESHOLDS
		if (init_thresholds(rule) < 0) {
			logmsgx(IPA_LOG_ERR, "init_rules: init_thresholds failed");
			return -1;
		}
#endif
	}

	return 0;
}

/*
 * Unlink rule from rules list and release memory used by one rule.
 */
void
free_rule(struct rule *rule)
{
	int dyn_flag = RULE_IS_DYNAMIC(rule);

	mem_free(rule->rule_name, m_anon);
	mem_free(rule->rule_info, dyn_flag ? m_anon : m_parser);

	free_cmds_rule(&rule->rc[RC_STARTUP]);
	free_cmds_rule(&rule->rc[RC_SHUTDOWN]);

#ifdef WITH_LIMITS
	free_limits(rule->free_mask, &rule->limits, dyn_flag);
#endif

#ifdef WITH_THRESHOLDS
	free_thresholds(rule->free_mask, &rule->thresholds, dyn_flag);
#endif

#ifdef WITH_RULES
	if (rule->ac_gather_add_pat != NULL) {
		mem_free(rule->ac_gather_add_pat, m_parser);
		regfree(&rule->ac_gather_add_reg);
	}
	if (rule->ac_gather_sub_pat != NULL) {
		mem_free(rule->ac_gather_sub_pat, m_parser);
		regfree(&rule->ac_gather_sub_reg);
	}
	deinit_ac_gather_rev(rule);
#endif

	/* Remove rule from rules list. */
	TAILQ_REMOVE(&rules_list, rule, list);

	/* Remove rule from rules hash. */
	rem_rule_from_hash(rule);

	mzone_free(rule_mzone, rule);
}

/*
 * Call free_rule() for each rule.
 */
void
free_rules(void)
{
	struct rule *rule;

	while ( (rule = TAILQ_FIRST(&rules_list)) != NULL)
		free_rule(rule);
}

/*
 * Deinit one rule.
 */
int
deinit_rule(struct rule *rule)
{
	int	error = 0;
#ifdef WITH_LIMITS
	struct limit *limit;
#endif
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit;
#endif
#ifdef WITH_THRESHOLDS
	struct threshold *threshold;
#endif

	check_cnt_neg(rule, 0);

	/* Remove rule from active or inactive queue. */
	if (IS_ACTIVE(rule))
		TAILQ_REMOVE(&rules_active, rule, queue);
	else if (IS_INACTIVE(rule)) {
		TAILQ_REMOVE(&rules_inactive, rule, queue);
		rules_inactive_check_sec = !TAILQ_EMPTY(&rules_inactive) ?
		    TAILQ_FIRST(&rules_inactive)->worktime->active_sec : SECONDS_IN_WEEK;
	}

	/* Deinit in modules. */
#ifdef WITH_LIMITS
	STAILQ_FOREACH(limit, &rule->limits, link) {
		rem_wpid_from_hash(&limit->wpid);
# ifdef WITH_SUBLIMITS
		STAILQ_FOREACH(sublimit, &limit->sublimits, link)
			rem_wpid_from_hash(&sublimit->wpid);
# endif
		if (db_deinit_limit(rule, limit) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: db_deinit_rule: db_deinit_limit failed",
			    rule->rule_name);
			error = -1;
		}
	}
#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
	STAILQ_FOREACH(threshold, &rule->thresholds, link) {
		rem_wpid_from_hash(&threshold->wpid);
		if (db_deinit_threshold(rule, threshold) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: db_deinit_rule: db_deinit_threshold failed",
			    rule->rule_name);
			error = -1;
		}
	}
#endif

	if (ac_deinit_rule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: deinit_rule: ac_deinit_rule failed",
		    rule->rule_name);
		error = -1;
	}

	if (db_deinit_rule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s: deinit_rule: db_deinit_rule failed",
		    rule->rule_name);
		error = -1;
	}

	/* Decrement ref_count of accounting systems if rule is not inactive. */
	if (!IS_INACTIVE(rule))
		if (ac_dec_ref_count(rule->ac_list) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s: deinit_rule: ac_dec_ref_count failed",
			    rule->rule_name);
			error = -1;
		}

	return error;
}

static int
deinit_stat_rule(struct rule *rule)
{
#ifdef WITH_AUTORULES
	if (rules_ptr_marray != NULL)
		/* Mark rule as unused. */
		marray_free(rules_ptr_marray, rule->ruleno);
#endif

	if (deinit_rule(rule) < 0) {
		logmsgx(IPA_LOG_ERR, "deinit_stat_rule: deinit_rule failed");
		return -1;
	}

	return 0;
}

/*
 * Deinit all static and dynamic rules.
 */
int
deinit_rules(void)
{
	int	error = 0;
	struct rule *rule;

	TAILQ_FOREACH(rule, &rules_list, list) {
#ifdef WITH_AUTORULES
		if (RULE_IS_DYNAMIC(rule)) {
			if (deinit_dyn_rule(rule) < 0) {
				logmsgx(IPA_LOG_ERR, "deinit_rules: deinit_dyn_rule failed");
				error = -1;
			}
		} else
#endif
		{
			if (deinit_stat_rule(rule) < 0) {
				logmsgx(IPA_LOG_ERR, "deinit_rules: deinit_rule failed");
				error = -1;
			}
		}
	}

	return error;
}

/*
 * Return hash_value for the given rule_name;
 */
static u_int
get_rule_hash_value(const char *rule_name)
{
	u_int hash_value;

	for (hash_value = 0; *rule_name != '\0'; ++rule_name)
		hash_value += (u_char)*rule_name;

	return hash_value;
}

#define get_rules_bucket(x) ((x) & (RULES_HASH_BUCKETS - 1))

/*
 * Add rule to rules_hash.
 */
void
add_rule_to_hash(struct rule *rule)
{
	rule->hash_value = get_rule_hash_value(rule->rule_name);
	LIST_INSERT_HEAD(&rules_hash[get_rules_bucket(rule->hash_value)], rule, hlink);
}

/*
 * Remove rule from rules_hash.
 */
void
rem_rule_from_hash(struct rule *rule)
{
	LIST_REMOVE(rule, hlink);
}

/*
 * Init rules_hash.
 */
void
init_rules_hash(void)
{
	struct rules_hash *hash;

	for (hash = rules_hash; hash < rules_hash + RULES_HASH_BUCKETS; ++hash)
		LIST_INIT(hash);
}

int
rules_hash_is_empty(void)
{
	const struct rules_hash *hash;

	for (hash = rules_hash; hash < rules_hash + RULES_HASH_BUCKETS; ++hash)
		if (!LIST_EMPTY(hash))
			return 0;
	return 1;
}

/*
 * Return pointer to rule with the given name.
 */
struct rule *
rule_by_name(const char *rule_name)
{
	u_int	hash_value;
	struct rule *rule;
	const struct rules_hash *hash;

	hash_value = get_rule_hash_value(rule_name);
	hash = &rules_hash[get_rules_bucket(hash_value)];

	LIST_FOREACH(rule, hash, hlink)
		if (rule->hash_value == hash_value &&
		    strcmp(rule->rule_name, rule_name) == 0)
			return rule;

	return NULL;
}

/*
 * Set check_sec for all active rules to 0, so they
 * will be checked immediately.
 */
void
set_rules_for_check(void)
{
	struct rule *rule;

	TAILQ_FOREACH(rule, &rules_active, queue)
		rule->check_sec = 0;
}

/*
 * Release memory used by all rulepats.
 */
void
free_rulepats(void)
{
	struct rulepat *rulepat;

	STAILQ_FOREACH(rulepat, &rulepats_list, link) {
		mem_free(rulepat->pat, m_parser);
		regfree(&rulepat->reg);

		free_cmds_rule(&rulepat->rc[RC_STARTUP]);
		free_cmds_rule(&rulepat->rc[RC_SHUTDOWN]);

#ifdef WITH_LIMITS
		free_limits(RULE_FREE_LIMITS, &rulepat->limits, 0);
#endif

#ifdef WITH_THRESHOLDS
		free_thresholds(RULE_FREE_THRESHOLDS, &rulepat->thresholds, 0);
#endif
	}

	mzone_deinit(rulepat_mzone);
}

void
init_cmds_in_rule(struct rule *rule)
{
	init_cmds_rule(&rule->rc[RC_STARTUP]);
	init_cmds_rule(&rule->rc[RC_SHUTDOWN]);
}

void
set_sync_exec_in_rule(struct rule *rule)
{
	set_sync_exec_cmds_rule(&rule->rc[RC_STARTUP]);
	set_sync_exec_cmds_rule(&rule->rc[RC_SHUTDOWN]);
}

void
init_cmds_in_rulepat(struct rulepat *rulepat)
{
	init_cmds_rule(&rulepat->rc[RC_STARTUP]);
	init_cmds_rule(&rulepat->rc[RC_SHUTDOWN]);
}

void
set_sync_exec_in_rulepat(struct rulepat *rulepat)
{
	set_sync_exec_cmds_rule(&rulepat->rc[RC_STARTUP]);
	set_sync_exec_cmds_rule(&rulepat->rc[RC_SHUTDOWN]);
}


syntax highlighted by Code2HTML, v. 0.9.1