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