/*-
 * 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_limits.c,v 1.1.4.2 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.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		nstatlimits;		/* Number of static limits. */
u_int		ndynlimits;		/* Number of dynamic limits. */

u_int		nstatsublimits;		/* Number of static sublimits. */
u_int		ndynsublimits;		/* Number of dynamic sublimits. */

#ifdef WITH_LIMITS

int		global_debug_limit;	/* global { debug_limit } */
int		global_debug_limit_init;/* global { debug_limit_init } */

int		global_load_limit;	/* global { load_limit } */

ipa_mzone	*limit_mzone;		/* Mzone for all struct limit{}. */

#ifdef WITH_SUBLIMITS
ipa_mzone	*sublimit_mzone;	/* Mzone for all struct sublimit{}. */
#endif

#define set_limit_active(r, l)		mod_set_limit_active((r), (l), ACTIVE_FLAG)

const char *const limit_event_msg[] = {
	"START",			/*  0 */
	"RESTART",			/*  1 */
	"RESTART_EXEC",			/*  2 */
	"REACH",			/*  3 */
	"REACH_EXEC",			/*  4 */
	"EXPIRE",			/*  5 */
	"EXPIRE_EXEC",			/*  6 */
	"UPDATED",			/*  7 */
	"STARTUP_IF_REACHED",		/*  8 */
	"STARTUP_IF_NOT_REACHED",	/*  9 */
	"SHUTDOWN_IF_REACHED",		/* 10 */
	"SHUTDOWN_IF_NOT_REACHED"	/* 11 */
};

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

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

	limit->is_active = active;

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

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

	return 0;
}

/*
 * Set limit->event_sec from limit->event_tm.
 */
void
limit_set_event_sec(struct limit *limit)
{
	const ipa_tm *tm = &limit->event_tm;

	if (cmp_ipa_tm(tm, &curdate) > 0) {
		if (tm->tm_year == curdate.tm_year &&
		    tm->tm_mon == curdate.tm_mon &&
		    tm->tm_mday == curdate.tm_mday)
			limit->event_sec = TIME_TO_SEC(tm);
		else
			limit->event_sec = SECONDS_IN_WEEK;
	} else
		limit->event_sec = cursec;
}

/*
 * Add chunk to one limit, if positive counter overflows, then
 * return -1, since this means incorrect configuration.
 */
int
add_chunk_to_limit(const struct rule *rule, struct limit *limit,
    const uint64_t *chunk_ptr)
{
	uint64_t chunk;

	chunk = *chunk_ptr;

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

	return 0;
}

/*
 * Add chunk to every not reached and active rule's limit.
 */
int
add_chunk_to_limits(const struct rule *rule, const uint64_t *chunk_ptr)
{
	struct limit *limit;

	STAILQ_FOREACH(limit, &rule->limits, link)
		if (IS_NOTREACHED(limit) && IS_ACTIVE(limit))
			if (add_chunk_to_limit(rule, limit, chunk_ptr) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: add_chunk_to_limits: add_chunk_to_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
	return 0;
}

/*
 * Subtract chunk from one limit, if negative counter overflows, then
 * return -1, since this means incorrect configuration.
 */
int
sub_chunk_from_limit(const struct rule *rule, struct limit *limit,
    const uint64_t *chunk_ptr)
{
	uint64_t chunk;

	chunk = *chunk_ptr;

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

	return 0;
}

/*
 * Subtract chunk from every not reached and active rule's limit.
 */
int
sub_chunk_from_limits(const struct rule *rule, const uint64_t *chunk_ptr)
{
	struct limit *limit;

	STAILQ_FOREACH(limit, &rule->limits, link)
		if (IS_NOTREACHED(limit) && IS_ACTIVE(limit))
			if (sub_chunk_from_limit(rule, limit, chunk_ptr) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: sub_chunk_from_limits: sub_chunk_from_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
	return 0;
}

/*
 * Register a new state for the limit.
 */
static int
new_limit_state(const struct rule *rule, struct limit *limit)
{
	ipa_tm	event_tm;
	struct ipa_limit_state newstate;
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit;
#endif

	if (rule->debug_limit_init || rule->debug_limit)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: register new limit state",
		    rule->rule_name, limit->limit_name);

	event_tm = curdate;
	/* This function can be called at 24:00:00. */
	if (newday_flag)
		fix_240000(&event_tm);

	/* Set limit status as not reached. */
	limit->is_reached = 0;

	newstate.lim = limit->lim;

	/* Flush limit's counter. */
	newstate.cnt = limit->cnt = UINT64_C(0);

	newstate.event_date_set = IPA_LIMIT_EVENT_START_SET;
	newstate.event_date[IPA_LIMIT_EVENT_START] = curdate;

	if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) {
		limit->event_tm = event_tm;
		ipa_tm_texp(&limit->event_tm, &limit->restart.restart);
		newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET;
		newstate.event_date[IPA_LIMIT_EVENT_RESTART] = limit->event_tm;
		limit_set_event_sec(limit);
		if (rule->debug_limit || rule->debug_limit_init)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit will be restarted at %s",
			    rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm));
	} else
		limit->event_sec = SECONDS_IN_WEEK;

#ifdef WITH_SUBLIMITS
	STAILQ_FOREACH(sublimit, &limit->sublimits, link)
		sublimit->is_reached = 0;
#endif

	if (db_set_limit_state(rule, limit, &newstate, 1) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: new_limit_state: db_set_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	return 0;
}

/*
 * Restart a limit.
 */
int
restart_limit(const struct rule *rule, struct limit *limit)
{
	struct wpid *wpid;

	if (rule->debug_limit)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: restart limit",
		    rule->rule_name, limit->limit_name);
	if (!STAILQ_EMPTY(&limit->restart.cmdl.list)) {
		/* restart { exec } */
		if (rule->debug_exec)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit restarted: run commands (%s)",
			    rule->rule_name, limit->limit_name, sync_exec_msg[limit->restart.cmdl.sync_exec]);
		wpid = &limit->wpid;
		if (wpid->pid != 0)
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (restart): previously run process with PID %ld has not exited yet",
			    rule->rule_name, limit->limit_name, (long)wpid->pid);
		if (exec_cmd_list(rule, wpid, &limit->restart.cmdl, "rule %s, limit %s: restart",
		    rule->rule_name, limit->limit_name) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: exec_cmd_list failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
		if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_RESTART_EXEC, &curdate) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: db_limit_event failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
	}
	if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_RESTART) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: ac_limit_event failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	if (new_limit_state(rule, limit) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: restart_limit: new_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	return 0;
}

/*
 * Expire a limit.
 */
int
expire_limit(const struct rule *rule, struct limit *limit)
{
	struct wpid *wpid;

	if (rule->debug_limit)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit expired",
		    rule->rule_name, limit->limit_name);
	if (!STAILQ_EMPTY(&limit->expire.cmdl.list)) {
		/* expire { exec } */
		if (rule->debug_exec)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit expired: run commands (%s)",
			    rule->rule_name, limit->limit_name, sync_exec_msg[limit->expire.cmdl.sync_exec]);
		wpid = &limit->wpid;
		if (wpid->pid != 0)
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (expire): previously run process with PID %ld has not exited yet",
			    rule->rule_name, limit->limit_name, (long)wpid->pid);
		if (exec_cmd_list(rule, wpid, &limit->expire.cmdl, "rule %s, limit %s: expire",
		    rule->rule_name, limit->limit_name) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: exec_cmd_list failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
		if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE_EXEC, &curdate) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: db_limit_event failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
	}
	if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: ac_limit_event failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	if (new_limit_state(rule, limit) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: expire_limit: new_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	return 0;
}

/*
 * A limit is reached.
 */
int
reach_limit(const struct rule *rule, struct limit *limit)
{
	ipa_tm	event_tm;
	struct wpid *wpid;

	/* This function can be called at 24:00:00. */
	event_tm = curdate;
	if (newday_flag)
		fix_240000(&event_tm);

	if (rule->debug_limit)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit reached, cnt %s",
		    rule->rule_name, limit->limit_name, cnt_to_buf(&limit->cnt, limit->cnt_type));

	/* Set limit status as reached. */
	limit->is_reached = 1;

	if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH, &curdate) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	if (!STAILQ_EMPTY(&limit->reach.list)) {
		/* reach { exec } */
		if (rule->debug_exec)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s (reach): run commands (%s)",
			    rule->rule_name, limit->limit_name, sync_exec_msg[limit->reach.sync_exec]);
		wpid = &limit->wpid;
		if (wpid->pid != 0)
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s (reach): previously run process with PID %ld has not exited yet",
			    rule->rule_name, limit->limit_name, (long)wpid->pid);
		if (exec_cmd_list(rule, wpid, &limit->reach, "rule %s, limit %s: reach",
		    rule->rule_name, limit->limit_name) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: exec_cmd_list failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
		if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH_EXEC, &curdate) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
	}
	if (ac_limit_event(rule, limit, IPA_LIMIT_EVENT_REACH) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: ac_limit_event failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	if (limit->expire.expire.upto != TEXP_UPTO_NOTSET) {
		/* limit { expire } */
		limit->event_tm = event_tm;
		ipa_tm_texp(&limit->event_tm, &limit->expire.expire);
		limit_set_event_sec(limit);
		if (rule->debug_limit)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit will expire at %s",
			    rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm));
		if (db_limit_event(rule, limit, IPA_LIMIT_EVENT_EXPIRE, &limit->event_tm) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: db_limit_event failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
		if (limit->event_sec == cursec) {
			/* expire = 0s */
			if (expire_limit(rule, limit) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: reach_limit: expire_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
		}
	} else
		limit->event_sec = SECONDS_IN_WEEK;

	return 0;
}

#ifdef WITH_SUBLIMITS
/*
 * A sublimit is reached.
 */
int
reach_sublimit(const struct rule *rule, const struct limit *limit,
    struct sublimit *sublimit)
{
	struct wpid *wpid;

	if (rule->debug_limit)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s: sublimit reached, cnt %s",
		    rule->rule_name, limit->limit_name, sublimit->sublimit_name, cnt_to_buf(&limit->cnt, limit->cnt_type));
	sublimit->is_reached = 1;
	if (!STAILQ_EMPTY(&sublimit->reach.list)) {
		/* reach { exec } */
		if (rule->debug_exec)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s (reach): run commands (%s)",
			    rule->rule_name, limit->limit_name, sublimit->sublimit_name, sync_exec_msg[sublimit->reach.sync_exec]);
		wpid = &sublimit->wpid;
		if (wpid->pid != 0)
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s, sublimit %s (reach): previously run process with PID %ld has not exited yet",
			    rule->rule_name, limit->limit_name, sublimit->sublimit_name, (long)wpid->pid);
		if (exec_cmd_list(rule, wpid, &sublimit->reach, "rule %s, limit %s, sublimit %s: reach",
		    rule->rule_name, limit->limit_name, sublimit->sublimit_name) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: reach_sublimit: exec_cmd_list failed",
			    rule->rule_name, limit->limit_name, sublimit->sublimit_name);
			return -1;
		}
	}
	return 0;
}
#endif /* WITH_SUBLIMITS */

/*
 * Check for rule's limits events.
 */
int
check_limits_events(const struct rule *rule, u_int *check_secp)
{
	u_int	check_sec, tmp;
	struct limit *limit;
#ifdef WITH_SUBLIMITS
	struct sublimit	*sublimit;
#endif
	const struct worktime *wt;

	check_sec = SECONDS_IN_WEEK; /* Hint. */

	STAILQ_FOREACH(limit, &rule->limits, link) {
		wt = limit->worktime;
		if (IS_INACTIVE(limit)) {
			/* Limit is inactive. */
			if (wt->active_sec <= cursec) {
				/* It's time to make limit active. */
				if (set_limit_active(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "check_limits_events: set_limit_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 limit. */
			}
		}

		/* Here limit is active. */
		if (IS_REACHED(limit)) {
			/* Limit has been reached already. */
			if (limit->event_sec <= cursec) {
				/* limit { expire } and it's time to expire limit. */
				tmp = ipa_tm_diff(&curdate, &limit->event_tm);
				if (tmp > sensitive_time)
					logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: expire limit too late: delta %s is greater than \"sensitive_time\" %u seconds",
					    rule->rule_name, limit->limit_name, time_to_buf(tmp), sensitive_time);
				if (expire_limit(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: expire_limit failed",
					    rule->rule_name, limit->limit_name);
					return -1;
				}
			}
		} else {
			/* Limit is not reached. */
			if (db_update_limit(rule, limit) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: db_update_limit failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
#ifdef WITH_SUBLIMITS
			STAILQ_FOREACH(sublimit, &limit->sublimits, link)
				if (IS_NOTREACHED(sublimit)) {
					/* Sublimit is not reached. */
					if (limit->cnt >= sublimit->lim) {
						/* Sublimit has just been reached. */
						if (reach_sublimit(rule, limit, sublimit) < 0) {
							logmsgx(IPA_LOG_ERR, "rule %s, limit %s, sublimit %s: check_limits_events: reach_sublimit failed",
							    rule->rule_name, limit->limit_name, sublimit->sublimit_name);
							return -1;
						}
					}
				}
#endif
			if (limit->cnt >= limit->lim) {
				/* Limit has just been reached. */
				if (reach_limit(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: reach_limit failed",
					    rule->rule_name, limit->limit_name);
					return -1;
				}
			} else if (limit->event_sec <= cursec) {
				/* limit { restart } and it's time to restart limit. */
				tmp = ipa_tm_diff(&curdate, &limit->event_tm);
				if (tmp > sensitive_time)
					logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: restart limit too late: delta %s is greater than \"sensitive_time\" %u seconds",
					    rule->rule_name, limit->limit_name, time_to_buf(tmp), sensitive_time);
				if (restart_limit(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "rule %s, limit %s: check_limits_events: restart_limit failed",
					    rule->rule_name, limit->limit_name);
					return -1;
				}
			}
		}

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

	*check_secp = check_sec;

	return 0;
}

/*
 * Init one limit.
 */
static int
init_limit(const struct rule *rule, struct limit *limit)
{
	const char *db_name;
	int	i, error;
	char	*buf;
	u_int	bit;
	ipa_tm	event_tm;
	struct ipa_limit_state oldstate, newstate;

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

	error = 0;

	limit->cnt_neg = UINT64_C(0);

	if (STAILQ_EMPTY(limit->db_list)) {
		/* "null" database is used. */
		if (rule->debug_limit_init)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: create limit with no database",
			    rule->rule_name, limit->limit_name);
		goto new_limit_state;
	}

	/* Get limit state from databases. */
	if (db_get_limit_state(rule, limit, &oldstate, &db_name) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: db_get_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}
	if (db_name == NULL) {
		logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: any database used for this limit does not support db_get_limit_state function",
		    rule->rule_name, limit->limit_name);
		goto new_limit_state;
	}
	if (rule->debug_limit_init)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: got limit state from database %s",
		    rule->rule_name, limit->limit_name, db_name);

	/* Check if limit has state in database. */
	if (oldstate.lim == UINT64_C(0)) {
		/* There isn't the old state of the limit. */
		if (rule->debug_limit_init)
			logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: database does not has limit state, probably this is a new limit",
			    rule->rule_name, limit->limit_name);
		goto new_limit_state;
	}

	/* Check and init "limit" parameter. */
	if (rule->debug_limit_init && limit->lim != oldstate.lim) {
		buf = mem_strdup(cnt_to_buf(&limit->lim, limit->cnt_type), m_tmp);
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: config limit %s, database limit %s (will use %s value)",
		    rule->rule_name, limit->limit_name,
		    buf != NULL ? buf : "(no memory)", cnt_to_buf(&oldstate.lim, limit->cnt_type),
		    limit->load_limit ? "database" : "config");
		mem_free(buf, m_tmp);
	}

	if (limit->load_limit)
		limit->lim = oldstate.lim;

	/* Check EVENT_START. */
	if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_START_SET)) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: cannot find EVENT_START",
		    rule->rule_name, limit->limit_name);
		goto new_limit_state_error;
	}

	/* Check EVENT_UPDATED */
	if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_UPDATED_SET)) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: cannot find EVENT_UPDATED",
		    rule->rule_name, limit->limit_name);
		goto new_limit_state_error;
	}

	/* Set limit status as unreached. */
	limit->is_reached = 0;

	/* Check dates in event_date array. */
	for (i = 0, bit = 1; i < IPA_LIMIT_EVENT_NUM; bit <<= 1, ++i)
		if (oldstate.event_date_set & bit) {
			event_tm = oldstate.event_date[i];
			/* Some events could happen at 24:00:00. */
			switch (i) {
			case IPA_LIMIT_EVENT_START:
			case IPA_LIMIT_EVENT_REACH:
			case IPA_LIMIT_EVENT_REACH_EXEC:
			case IPA_LIMIT_EVENT_UPDATED:
				fix_240000(&event_tm);
			}
			if (check_ipa_tm(&event_tm) < 0) {
				logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: wrong value of LIMIT_EVENT_%s date: %s",
				    rule->rule_name, limit->limit_name,
				    limit_event_msg[i], ipa_tm_to_buf(&event_tm));
				goto new_limit_state_error;
			}
			switch (i) {
			case IPA_LIMIT_EVENT_START:
			case IPA_LIMIT_EVENT_RESTART_EXEC:
			case IPA_LIMIT_EVENT_REACH:
			case IPA_LIMIT_EVENT_REACH_EXEC:
			case IPA_LIMIT_EVENT_UPDATED:
				if (cmp_ipa_tm(&event_tm, &curdate) > 0) {
					logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: LIMIT_EVENT_%s %s is greater than current time",
					    rule->rule_name, limit->limit_name, limit_event_msg[i], ipa_tm_to_buf(&event_tm));
					goto new_limit_state_error;
				}
			}
		}

	if (cmp_ipa_tm(&oldstate.event_date[IPA_LIMIT_EVENT_START], &oldstate.event_date[IPA_LIMIT_EVENT_UPDATED]) > 0) {
		buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_START]), m_tmp);
		logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: EVENT_START %s is greater than EVENT_UPDATED %s",
		    rule->rule_name, limit->limit_name,
		    buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_UPDATED]));
		mem_free(buf, m_tmp);
		goto new_limit_state_error;
	}

	if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_EXEC_SET) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: limit has EVENT_RESTART_EXEC, this is wrong",
		    rule->rule_name, limit->limit_name);
		goto new_limit_state_error;
	}

	if (oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_EXEC_SET) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: limit has EVENT_EXPIRE_EXEC, this is wrong",
		    rule->rule_name, limit->limit_name);
		goto new_limit_state_error;
	}

	/* Set start date of limit. */
	newstate.event_date_set = IPA_LIMIT_EVENT_START_SET;
	newstate.event_date[IPA_LIMIT_EVENT_START] = oldstate.event_date[IPA_LIMIT_EVENT_START];

	if (oldstate.cnt < oldstate.lim ||
	    !(oldstate.event_date_set & IPA_LIMIT_EVENT_REACH_SET)) {
		/* Limit wasn't reached or wasn't mark as reached with old "limit" parameter. */
		newstate.lim = limit->lim;
		if (oldstate.cnt >= oldstate.lim)
			logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit was reached with old state, but EVENT_REACH was not registered",
			    rule->rule_name, limit->limit_name);

		if (limit->restart.restart.upto != TEXP_UPTO_NOTSET) {
			/* The "restart" parameter is used. */
			limit->event_tm = oldstate.event_date[IPA_LIMIT_EVENT_START];
			fix_240000(&limit->event_tm);
			if (set_wday(&limit->event_tm) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: set_wday failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
			ipa_tm_texp(&limit->event_tm, &limit->restart.restart);
			newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET;
			newstate.event_date[IPA_LIMIT_EVENT_RESTART] = limit->event_tm;
			if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET))
				logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has not EVENT_RESTART in database, but it has \"restart\" parameter in config",
				    rule->rule_name, limit->limit_name);
			else {
				if (cmp_ipa_tm(&limit->event_tm, &oldstate.event_date[IPA_LIMIT_EVENT_RESTART]) != 0) {
					buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_RESTART]), m_tmp);
					logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: restart time is changed %s -> %s",
					    rule->rule_name, limit->limit_name,
					    buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&limit->event_tm));
					mem_free(buf, m_tmp);
				}
			}
			if (rule->debug_limit || rule->debug_limit_init)
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: limit will be restarted at %s",
				    rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm));
		} else {
			if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET)
				logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has EVENT_RESTART in database, but limit has not \"restart\" parameter in config: forgot about EVENT_RESTART",
				    rule->rule_name, limit->limit_name);
			limit->event_sec = SECONDS_IN_WEEK;
		}
	} else {
		/* Limit was reached and marked as reached with old "limit" parameter. */
		if (oldstate.event_date_set & IPA_LIMIT_EVENT_RESTART_SET) {
			/* If limit had restart time in previous state, then simply copy it. */
			newstate.event_date_set |= IPA_LIMIT_EVENT_RESTART_SET;
			newstate.event_date[IPA_LIMIT_EVENT_RESTART] = oldstate.event_date[IPA_LIMIT_EVENT_RESTART];
		}
		newstate.lim = oldstate.lim;
		limit->is_reached = 1;
		newstate.event_date_set |= IPA_LIMIT_EVENT_REACH_SET;
		newstate.event_date[IPA_LIMIT_EVENT_REACH] = oldstate.event_date[IPA_LIMIT_EVENT_REACH];
		if (oldstate.event_date_set & IPA_LIMIT_EVENT_REACH_EXEC_SET) {
			/* If limit run any commands, then save this in the new state. */
			newstate.event_date_set |= IPA_LIMIT_EVENT_REACH_EXEC_SET;
			newstate.event_date[IPA_LIMIT_EVENT_REACH_EXEC] = oldstate.event_date[IPA_LIMIT_EVENT_REACH_EXEC];
		}
		if (limit->expire.expire.upto != TEXP_UPTO_NOTSET) {
			newstate.event_date_set |= IPA_LIMIT_EVENT_EXPIRE_SET;
			limit->event_tm = newstate.event_date[IPA_LIMIT_EVENT_REACH];
			fix_240000(&limit->event_tm);
			if (set_wday(&limit->event_tm) < 0) {
				logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: set_wday failed",
				    rule->rule_name, limit->limit_name);
				return -1;
			}
			ipa_tm_texp(&limit->event_tm, &limit->expire.expire);
			newstate.event_date[IPA_LIMIT_EVENT_EXPIRE] = limit->event_tm;
			if (!(oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_SET))
				logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has not EVENT_EXPIRE in database, but limit has \"expire\" section in config",
				    rule->rule_name, limit->limit_name);
			else {
				if (cmp_ipa_tm(&limit->event_tm, &oldstate.event_date[IPA_LIMIT_EVENT_EXPIRE]) != 0) {
					buf = mem_strdup(ipa_tm_to_buf(&oldstate.event_date[IPA_LIMIT_EVENT_EXPIRE]), m_tmp);
					logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: expire time is changed %s -> %s",
					    rule->rule_name, limit->limit_name,
					    buf != NULL ? buf : "(no memory)", ipa_tm_to_buf(&limit->event_tm));
					mem_free(buf, m_tmp);
				}
			}
			if (rule->debug_limit || rule->debug_limit_init)
				logmsgx(IPA_LOG_INFO, "rule %s, limit %s: reached limit will expire at %s",
				    rule->rule_name, limit->limit_name, ipa_tm_to_buf(&limit->event_tm));
		} else {
			if (oldstate.event_date_set & IPA_LIMIT_EVENT_EXPIRE_SET)
				logmsgx(IPA_LOG_WARNING, "rule %s, limit %s: init_limit: limit has EVENT_EXPIRE in database, but limit has not \"expire\" section in config: forgot about EVENT_EXPIRE",
				    rule->rule_name, limit->limit_name);
			limit->event_sec = SECONDS_IN_WEEK;
		}
	}

	/* Copy counter value from the old state. */
	limit->cnt = newstate.cnt = oldstate.cnt;

	if (rule->debug_limit_init)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: set limit state",
		    rule->rule_name, limit->limit_name);

	if (db_set_limit_state(rule, limit, &newstate, 0) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: db_set_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	return 0;

new_limit_state_error:
	error = 1;

new_limit_state:
	if (rule->debug_limit_init || error)
		logmsgx(IPA_LOG_INFO, "rule %s, limit %s: init_limit: set new limit state %s",
		    rule->rule_name, limit->limit_name, error ? "(fixing errors found in database)" : "");

	if (new_limit_state(rule, limit) < 0) {
		logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limit: new_limit_state failed",
		    rule->rule_name, limit->limit_name);
		return -1;
	}

	return 0;
}

#ifdef WITH_SUBLIMITS
/*
 * Init all sublimits for one limit.
 */
static void
init_sublimits(const struct rule *rule, const struct limit *limit)
{
	struct sublimit *sublimit;

	STAILQ_FOREACH(sublimit, &limit->sublimits, link) {
		if (sublimit->lim_per_cent != 0)
			sublimit->lim = uint64_per_cent(&limit->lim, sublimit->lim_per_cent);
		/*
		 * If limit is reached, then "limit" parameter is
		 * not used and sublimit is always reached.
		 */
		sublimit->is_reached = IS_REACHED(limit) || limit->cnt >= sublimit->lim;
	}
}

void
init_cmds_in_sublimit(struct sublimit *sublimit)
{
	init_cmd_list(&sublimit->reach);
	init_cmds_limit(&sublimit->rc[RC_STARTUP]);
	init_cmds_limit(&sublimit->rc[RC_SHUTDOWN]);
}

static void
set_sync_exec_in_sublimit(struct sublimit *sublimit)
{
	if (sublimit->reach.sync_exec < 0)
		sublimit->reach.sync_exec = 0;
	set_sync_exec_cmds_limit(&sublimit->rc[RC_STARTUP]);
	set_sync_exec_cmds_limit(&sublimit->rc[RC_SHUTDOWN]);
}
#endif /* WITH_SUBLIMITS */

/*
 * Init all limits in one rule.
 */
int
init_limits(const struct rule *rule)
{
	struct limit *limit;

	STAILQ_FOREACH(limit, &rule->limits, link) {
		if (init_limit(rule, limit) < 0) {
			logmsgx(IPA_LOG_ERR, "rule %s, limit %s: init_limits: init_limit failed",
			    rule->rule_name, limit->limit_name);
			return -1;
		}
#ifdef WITH_SUBLIMITS
		init_sublimits(rule, limit);
#endif
	}
	return 0;
}

/*
 * Return pointer to limit with the given name.
 */
struct limit *
limit_by_name(const struct rule *rule, const char *name)
{
	struct limit *limit;

	STAILQ_FOREACH(limit, &rule->limits, link)
		if (strcmp(name, limit->limit_name) == 0)
			return limit;
	return NULL;
}

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

	check_sec = SECONDS_IN_WEEK; /* Hint. */

	STAILQ_FOREACH(limit, &rule->limits, link) {
		if (IS_REACHED(limit)) {
			/* Is reached. */
			if (limit->expire.expire.upto != TEXP_UPTO_NOTSET)
				/* limit { expire } */
				limit_set_event_sec(limit);
		} else {
			/* Is not reached. */
			if (limit->restart.restart.upto != TEXP_UPTO_NOTSET)
				/* limit { restart } */
				limit_set_event_sec(limit);
		}
		if (IS_INACTIVE(limit->worktime)) {
			if (IS_ACTIVE(limit))
				if (set_limit_inactive(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "limits_newday: set_limit_inactive failed");
					return -1;
				}
			if (check_sec > limit->worktime->active_sec)
				check_sec = limit->worktime->active_sec;
		} else {
			if (IS_INACTIVE(limit))
				if (set_limit_active(rule, limit) < 0) {
					logmsgx(IPA_LOG_ERR, "limits_newday: set_limit_active failed");
					return -1;
				}
			if (check_sec > limit->worktime->inactive_sec)
				check_sec = limit->worktime->inactive_sec;
			if (check_sec > limit->event_sec)
				check_sec = limit->event_sec;
		}
		if (rule->check_sec > check_sec)
			rule->check_sec = check_sec;
	}

	return 0;
}

/*
 * Copy all limits from list_src to rule.
 */
int
copy_limits(struct rule *rule, const struct limits_list *list_src, int dyn_flag)
{
	struct limit *limit_dst;
	const struct limit *limit_src;
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit_dst;
	const struct sublimit *sublimit_src;
#endif

	STAILQ_FOREACH(limit_src, list_src, link) {
		if ( (limit_dst = mzone_alloc(limit_mzone)) == NULL) {
			xlogmsgx(IPA_LOG_ERR, "copy_limits: mzone_alloc failed");
			return -1;
		}

		/* Copy settings from source limit. */
		*limit_dst = *limit_src;

		/* Init fields, which are pointers to memory, which can't be shared. */
		init_cmds_in_limit(limit_dst);
#ifdef WITH_SUBLIMITS
		STAILQ_INIT(&limit_dst->sublimits);
#endif

		limit_dst->rule = rule;

		/* Link just allocated limit to rule. */
		STAILQ_INSERT_TAIL(&rule->limits, limit_dst, link);

		if (dyn_flag)
			++ndynlimits;
		else
			++nstatlimits;

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

		if (copy_cmd_list(rule, &limit_dst->restart.cmdl, &limit_src->restart.cmdl) < 0)
			return -1;

		if (copy_cmd_list(rule, &limit_dst->reach, &limit_src->reach) < 0)
			return -1;

		if (copy_cmd_list(rule, &limit_dst->expire.cmdl, &limit_src->expire.cmdl) < 0)
			return -1;

		if (copy_cmds_limit(rule, &limit_dst->rc[RC_STARTUP], &limit_src->rc[RC_STARTUP]) < 0)
			return -1;

		if (copy_cmds_limit(rule, &limit_dst->rc[RC_SHUTDOWN], &limit_src->rc[RC_SHUTDOWN]) < 0)
			return -1;

#ifdef WITH_SUBLIMITS
		STAILQ_FOREACH(sublimit_src, &limit_src->sublimits, link) {
			if ( (sublimit_dst = mzone_alloc(sublimit_mzone)) == NULL) {
				xlogmsgx(IPA_LOG_ERR, "copy_limits: mzone_alloc failed");
				return -1;
			}

			/* Copy settings from source sublimit. */
			*sublimit_dst = *sublimit_src;

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

			sublimit_dst->limit = limit_dst;

			/* Link just allocated sublimit to rule. */
			STAILQ_INSERT_TAIL(&limit_dst->sublimits, sublimit_dst, link);
			if (dyn_flag)
				++ndynsublimits;
			else
				++nstatsublimits;

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

			if (copy_cmd_list(rule, &sublimit_dst->reach, &sublimit_src->reach) < 0)
				return -1;

			if (copy_cmds_limit(rule, &sublimit_dst->rc[RC_STARTUP], &sublimit_src->rc[RC_STARTUP]) < 0)
				return -1;

			if (copy_cmds_limit(rule, &sublimit_dst->rc[RC_SHUTDOWN], &sublimit_src->rc[RC_SHUTDOWN]) < 0)
				return -1;

			sublimit_dst->wpid.u.sublimit = sublimit_dst;
		}
#endif /* WITH_SUBLIMITS */

		limit_dst->wpid.u.limit = limit_dst;
	}

	return 0;
}

/*
 * Release memory used by a list of limits, free_mask determines
 * which part of limit{} structure to free.
 */
void
free_limits(u_int free_mask, struct limits_list *limits, int dyn_flag)
{
	struct limit *limit, *limit_next;
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit, *sublimit_next;
#endif

	for (limit = STAILQ_FIRST(limits); limit != NULL; limit = limit_next) {
		limit_next = STAILQ_NEXT(limit, link);
		if (dyn_flag)
			--ndynlimits;
		if (free_mask & RULE_FREE_LIMITS) {
			mem_free(limit->limit_name, m_anon);
			mem_free(limit->limit_info, m_parser);
		}
		free_cmd_list(&limit->restart.cmdl);
		free_cmd_list(&limit->reach);
		free_cmd_list(&limit->expire.cmdl);
		free_cmds_limit(&limit->rc[RC_STARTUP]);
		free_cmds_limit(&limit->rc[RC_SHUTDOWN]);
# ifdef WITH_SUBLIMITS
		for (sublimit = STAILQ_FIRST(&limit->sublimits); sublimit != NULL; sublimit = sublimit_next) {
			sublimit_next = STAILQ_NEXT(sublimit, link);
			if (dyn_flag)
				--ndynsublimits;
			if (free_mask & RULE_FREE_LIMITS)
				mem_free(sublimit->sublimit_name, m_anon);
			free_cmd_list(&sublimit->reach);
			free_cmds_limit(&sublimit->rc[RC_STARTUP]);
			free_cmds_limit(&sublimit->rc[RC_SHUTDOWN]);
			mzone_free(sublimit_mzone, sublimit);
		}
# endif /* WITH_SUBLIMITS */
		mzone_free(limit_mzone, limit);
	}
}

void
init_cmds_in_limit(struct limit *limit)
{
	init_cmd_list(&limit->restart.cmdl);
	init_cmd_list(&limit->reach);
	init_cmd_list(&limit->expire.cmdl);
	init_cmds_limit(&limit->rc[RC_STARTUP]);
	init_cmds_limit(&limit->rc[RC_SHUTDOWN]);
}

void
set_sync_exec_in_limit(struct limit *limit)
{
#ifdef WITH_SUBLIMITS
	struct sublimit *sublimit;
#endif

	if (limit->restart.cmdl.sync_exec < 0)
		limit->restart.cmdl.sync_exec = 0;
	if (limit->reach.sync_exec < 0)
		limit->reach.sync_exec = 0;
	if (limit->expire.cmdl.sync_exec < 0)
		limit->expire.cmdl.sync_exec = 0;
	set_sync_exec_cmds_limit(&limit->rc[RC_STARTUP]);
	set_sync_exec_cmds_limit(&limit->rc[RC_SHUTDOWN]);

#ifdef WITH_SUBLIMITS
	STAILQ_FOREACH(sublimit, &limit->sublimits, link)
		set_sync_exec_in_sublimit(sublimit);
#endif
}
#endif /* WITH_LIMITS */


syntax highlighted by Code2HTML, v. 0.9.1