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

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

#include "ipa_mod.h"

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

#include "ipastat_conf.h"
#include "ipastat_log.h"
#include "ipastat_main.h"
#include "ipastat_rules.h"
#include "ipastat_time.h"
#include "ipastat_st.h"

const char	*x_pat = NULL;		/* -q -x <pattern> */
regex_t		x_reg;			/* Compiled x_pat. */
regex_t		*x_reg_ptr = NULL;	/* NULL or &x_reg. */

u_int		a_flag = A_FLAG_ABSENT;	/* -q -a ... */

ipa_mem_type	*m_anon;		/* Anonymous memory allocations. */
ipa_mem_type	*m_result;		/* Memory used for query results. */

static int	need_nl_raw = 0;	/* Non-zero if new line is needed in raw output. */

/*
 * If some rule has several buffers with statistics for
 * several time intervals, then keep them in rule_stat_list
 * structure.
 */
struct rule_stat {
	STAILQ_ENTRY(rule_stat) link;	/* All rule_stat for rule. */
	u_int		n;		/* Number of elements in buf. */
	struct ipa_rule_stat *buf;	/* Buffer with statistics for rule. */
	const struct opt_tint *opt_tint; /* Time interval. */
};

STAILQ_HEAD(rule_stat_list, rule_stat);

#ifdef WITH_LIMITS
/*
 * If some limit has several buffers with statistics for
 * several time intervals, then keep them in limit_stat_list
 * structure.
 */
struct limit_stat {
	STAILQ_ENTRY(limit_stat) link;	/* All limit_stat for limit. */
	u_int		n;		/* Number of elements in buf. */
	struct ipa_limit_state *buf;	/* Buffer with statistics for limit. */
	const struct opt_tint *opt_tint; /* Time interval. */
};

STAILQ_HEAD(limit_stat_list, limit_stat);
#endif /* WITH_LIMITS */

#ifdef WITH_ANY_LIMITS
/*
 * Description of several entities.
 */
struct entity_desc {
	u_int		n;		/* Number of elements in desc_list. */
	struct ipa_entity_desc *desc_list; /* Array of descriptions. */
};
#endif

static int	output(const char *, ...) ATTR_FORMAT(printf, 1, 2);

static int
output(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	if (vprintf(format, ap) < 0) {
		logmsg(IPA_LOG_ERR, "output: vprintf failed");
		return -1;
	}
	va_end(ap);

	return 0;
}

static const char *
plural_trailing(u_int n)
{
	return n != 1 ? "s" : "";
}

static int
print_line(size_t len)
{
	while (len--)
		if (printf("-") < 0) {
			logmsg(IPA_LOG_ERR, "print_line: printf failed");
			return -1;
		}
	return 0;
}

static int
output_flush(void)
{
	if (fflush(stdout) != 0) {
		logmsg(IPA_LOG_ERR, "output_flush: fflush(stdout)");
		return -1;
	}
	return 0;
}

static int
output_ipa_tm(const char *msg, const char sep, const ipa_tm *tm, u_int is_set)
{
	if (output("%s %c ", msg, sep) < 0)
		goto failed;

	if (is_set) {
		if (output("%d.%02d.%02d/%02d:%02d:%02d\n",
		    tm->tm_year, tm->tm_mon, tm->tm_mday,
		    tm->tm_hour, tm->tm_min, tm->tm_sec) < 0)
			goto failed;
	} else {
		if (output("-\n") < 0)
			goto failed;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_INFO, "output_ipa_tm: output failed");
	return -1;
}

static int
cmp_entity_desc(const void *p1, const void *p2)
{
	return strcmp(
	    ((const struct ipa_entity_desc *)p1)->name,
	    ((const struct ipa_entity_desc *)p2)->name);
}

static int
output_desc_list_raw(const char *what, u_int n,
    const struct ipa_entity_desc *desc_list)
{
	size_t	len, name_width, info_width;
	const struct ipa_entity_desc *desc;

	name_width = strlen(what);
	info_width = 4; /* strlen("info") */
	for (desc = desc_list; desc < desc_list + n; ++desc) {
		len = strlen(desc->name);
		if (name_width < len)
			name_width = len;
		if (desc->info != NULL) {
			len = strlen(desc->info);
			if (info_width < len)
				info_width = len;
		}
	}
	++info_width;

	if (output("%-*s | Info\n", (int)name_width, what) < 0)
		goto failed;

	if (print_line(name_width) < 0 || output("-+") < 0 ||
	    print_line(info_width) < 0 || output("\n") < 0)
		goto failed;

	for (desc = desc_list; desc < desc_list + n; ++desc)
		if ((desc->info != NULL ?
		    output("%-*s | %s\n", (int)name_width, desc->name, desc->info) :
		    output("%-*s |\n", (int)name_width, desc->name)) < 0)
			goto failed;

	if (print_line(name_width) < 0 || output("-+") < 0 ||
	    print_line(info_width) < 0 || output("\n\n") < 0)
		goto failed;

	if (output(" * %u line%s\n", n, plural_trailing(n)) < 0)
		goto failed;

	if (output_flush() < 0) {
		logmsgx(IPA_LOG_ERR, "output_entity_desc_raw: output_flush failed");
		return -1;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "output_entity_desc_raw: output failed");
	return -1;
}

static void
free_desc_list(u_int n, struct ipa_entity_desc *desc_list)
{
	struct ipa_entity_desc *desc;

	if (desc_list != NULL) {
		for (desc = desc_list; desc < desc_list + n; ++desc) {
			mem_free(desc->name, m_result);
			mem_free(desc->info, m_result);
		}
		mem_free(desc_list, m_result);
	}
}

static int
show_rules_list(void)
{
	int	error;
	u_int	n;
	const struct st_list *st_list;
	struct ipa_entity_desc *desc_list;

	st_list = cur_opt_st != NULL ? cur_opt_st->st_list : global_st_list;

	error = -1;

	if (st_get_rules_list(st_list, &n, &desc_list) < 0) {
		logmsgx(IPA_LOG_ERR, "show_rules_list: st_get_rules_list failed");
		desc_list = NULL;
		goto done;
	}

	qsort(desc_list, n, sizeof *desc_list, cmp_entity_desc);

	if (output_desc_list_raw("Rule", n, desc_list) < 0) {
		logmsgx(IPA_LOG_ERR, "show_rules_list: output_desc_list_raw failed");
		goto done;
	}

	error = 0;

done:
	free_desc_list(n, desc_list);
	return error;
}

static int
output_rule_stat_raw(const struct opt_rule *opt_rule)
{
	u_int	curyear, curmon, curmday;
	u_int	ndays, ndays_tot;
	uint64_t cnt_day, cnt_sum, cnt_tot;
	const struct rule *rule;
	const struct ipa_rule_stat *stat, *stat_end;
	const struct rule_stat *rule_stat;
	const struct rule_stat_list *rule_stat_list;

	if (need_nl_raw) {
		if (output("\n") < 0)
			goto failed;
	} else
		need_nl_raw = 1;

	rule = opt_rule->rule;
	if (output("Rule : %s\nInfo : %s\n", rule->rule_name,
	    rule->rule_info != NULL ? rule->rule_info : "") < 0)
		goto failed;

	rule_stat_list = opt_rule->data;
	ndays_tot = 0;
	cnt_tot = UINT64_C(0);

	STAILQ_FOREACH(rule_stat, rule_stat_list, link) {
		if (output_ipa_tm("\nFrom", ':', &rule_stat->opt_tint->tm1, 1) < 0)
			goto failed;
		if (output_ipa_tm("To  ", ':', &rule_stat->opt_tint->tm2, 1) < 0)
			goto failed;
		cnt_sum = UINT64_C(0);
		if (rule_stat->n != 0) {
			stat = rule_stat->buf;
			stat_end = stat + rule_stat->n;
			curyear = stat->year;
			curmon = stat->mon;
			curmday = stat->mday;
			cnt_day = UINT64_C(0);
			ndays = 1;
			if (output("\nTimestamp                    |              Counter |              Per day\n-----------------------------+----------------------+---------------------") < 0)
				goto failed;
			for (;;) {
				if (output("\n%u.%02u.%02u/%02u:%02u:%02u-%02u:%02u:%02u | %20"PRIu64" |",
				    stat->year, stat->mon, stat->mday,
				    stat->h1, stat->m1, stat->s1,
				    stat->h2, stat->m2, stat->s2,
				    stat->cnt) < 0)
					goto failed;
				if (cnt_day < UINT64_MAX - stat->cnt)
					cnt_day += stat->cnt;
				else
					cnt_day = UINT64_MAX;
				if (++stat == stat_end)
					break;
				if (curmday != stat->mday ||
				    curmon != stat->mon ||
				    curyear != stat->year) {
					curmday = stat->mday;
					curmon = stat->mon;
					curyear = stat->year;
					ndays++;
					if (cnt_sum < UINT64_MAX - cnt_day)
						cnt_sum += cnt_day;
					else
						cnt_sum = UINT64_MAX;
					if (cnt_day != UINT64_MAX) {
						if (output(" %20"PRIu64, cnt_day) < 0)
							goto failed;
					} else {
						if (output(" %20s", "TOO_BIG") < 0)
							goto failed;
					}
					cnt_day = UINT64_C(0);
				}
			}
			if (cnt_day != UINT64_MAX) {
				if (output(" %20"PRIu64, cnt_day) < 0)
					goto failed;
			} else {
				if (output(" %20s", "TOO_BIG") < 0)
					goto failed;
			}
			if (output("\n-----------------------------+----------------------+---------------------") < 0 ||
			    output("\n") < 0)
				goto failed;

			if (cnt_sum < UINT64_MAX - cnt_day)
				cnt_sum += cnt_day;
			else
				cnt_sum = UINT64_MAX;
		} else
			ndays = 0;
		if (cnt_sum != UINT64_MAX) {
			if (output("\n * Summary %"PRIu64" (%u day%s)\n",
			    cnt_sum, ndays, plural_trailing(ndays)) < 0)
				goto failed;
		} else {
			if (output("\n * Summary TOO_BIG (%u day%s)\n",
			    ndays, plural_trailing(ndays)) < 0)
				goto failed;
		}
		ndays_tot += ndays;
		if (cnt_tot < UINT64_MAX - cnt_sum)
			cnt_tot += cnt_sum;
		else
			cnt_tot = UINT64_MAX;
	}

	if (cnt_tot != UINT64_MAX) {
		if (output("\n * Total %"PRIu64" (%u day%s)\n",
		    cnt_tot, ndays_tot, plural_trailing(ndays_tot)) < 0)
			goto failed;
	} else {
		if (output("\n * Total TOO_BIG (%u day%s)\n",
		    ndays_tot, plural_trailing(ndays_tot)) < 0)
			goto failed;
	}

	if (output_flush() < 0) {
		logmsgx(IPA_LOG_ERR, "output_rule_stat_raw: output_flush failed");
		return -1;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "output_rule_stat_raw: output failed");
	return -1;
}

static int
show_rules_stat(void)
{
	int	error;
	u_int	n;
	struct ipa_rule_stat *buf;
	struct rule *rule;
	struct opt_rule *opt_rule;
	struct rule_stat *rule_stat, *rule_stat_next;
	struct rule_stat_list *rule_stat_list;
	const struct opt_tint *opt_tint;

	error = -1;

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule = opt_rule->rule;
		if (rule->rule_info == NULL)
			if (st_get_rule_info(rule, &rule->rule_info) < 0) {
				logmsgx(IPA_LOG_ERR, "show_rules_stat: st_get_rule_info failed");
				goto done;
			}
		if ( (rule_stat_list = mem_malloc(sizeof *rule_stat_list, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "show_rules_stat: mem_malloc failed");
			goto done;
		}
		STAILQ_INIT(rule_stat_list);
		opt_rule->data = rule_stat_list;

		STAILQ_FOREACH(opt_tint, &opt_tint_list, link) {
			if (st_get_rule_stat(rule, &opt_tint->tm1,
			    &opt_tint->tm2, opt_tint->exact, &n, &buf) < 0) {
				logmsgx(IPA_LOG_ERR, "show_rules_stat: st_get_rule_stat failed");
				goto done;
			}
			if ( (rule_stat = mem_malloc(sizeof *rule_stat, m_anon)) == NULL) {
				logmsgx(IPA_LOG_ERR, "show_rules_stat: mem_malloc failed");
				mem_free(buf, m_result);
				goto done;
			}
			rule_stat->n = n;
			rule_stat->buf = buf;
			rule_stat->opt_tint = opt_tint;
			STAILQ_INSERT_TAIL(rule_stat_list, rule_stat, link);
		}
	}

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if (output_rule_stat_raw(opt_rule) < 0) {
			logmsgx(IPA_LOG_ERR, "show_rules_stat: output_rule_stat_raw failed");
			goto done;
		}

	error = 0;

done:
	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule_stat_list = opt_rule->data;
		if (rule_stat_list != NULL) {
			for (rule_stat = STAILQ_FIRST(rule_stat_list); rule_stat != NULL; rule_stat = rule_stat_next) {
				rule_stat_next = STAILQ_NEXT(rule_stat, link);
				mem_free(rule_stat->buf, m_result);
				mem_free(rule_stat, m_anon);
			}
			mem_free(rule_stat_list, m_anon);
		}
	}

	return error;
}

#ifdef WITH_ANY_LIMITS
static int
output_any_limits_list_raw(const struct opt_rule *opt_rule, const char *what)
{
	const struct rule *rule;
	const struct entity_desc *entity_desc;

	if (need_nl_raw) {
		if (output("\n") < 0)
			goto failed;
	} else
		need_nl_raw = 1;

	rule = opt_rule->rule;
	if (output("Rule : %s\nInfo : %s\n\n", rule->rule_name,
	    rule->rule_info != NULL ? rule->rule_info : "") < 0)
		goto failed;

	entity_desc = opt_rule->data;

	if (output_desc_list_raw(what, entity_desc->n, entity_desc->desc_list) < 0) {
		logmsgx(IPA_LOG_ERR, "output_any_limits_list: output_desc_list_raw failed");
		goto failed;
	}

	if (output_flush() < 0) {
		logmsgx(IPA_LOG_ERR, "output_any_limits_list_raw: output_flush failed");
		return -1;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "output_any_limits_list_raw: output failed");
	return -1;
}
#endif /* WITH_ANY_LIMITS */

#ifdef WITH_LIMITS
static int
show_limits_list(void)
{
	int	error;
	u_int	n;
	struct rule *rule;
	struct opt_rule *opt_rule;
	struct entity_desc *entity_desc;
	struct ipa_entity_desc *desc_list;

	error = -1;

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule = opt_rule->rule;
		if (rule->rule_info == NULL)
			if (st_get_rule_info(rule, &rule->rule_info) < 0) {
				logmsgx(IPA_LOG_ERR, "show_limits_list: st_get_rule_info failed");
				goto done;
			}
		if (st_get_limits_list(rule, &n, &desc_list) < 0) {
			logmsgx(IPA_LOG_ERR, "show_limits_list: st_get_limits_list failed");
			goto done;
		}
		if ( (entity_desc = mem_malloc(sizeof *entity_desc, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "show_limits_list: mem_malloc failed");
			free_desc_list(n, desc_list);
			goto done;
		}
		entity_desc->desc_list = desc_list;
		entity_desc->n = n;
		opt_rule->data = entity_desc;
		qsort(desc_list, n, sizeof *desc_list, cmp_entity_desc);
	}

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if (output_any_limits_list_raw(opt_rule, "Limit") < 0) {
			logmsgx(IPA_LOG_ERR, "show_limits_list: output_any_limits_list_raw failed");
			goto done;
		}

	error = 0;

done:
	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if ( (entity_desc = opt_rule->data) != NULL) {
			free_desc_list(entity_desc->n, entity_desc->desc_list);
			mem_free(entity_desc, m_anon);
		}

	return error;
}

#define LIMIT_START_STR		"Start       "
#define LIMIT_RESTART_STR	"Restart     "
#define LIMIT_RESTART_EXEC_STR	"Restart exec"
#define LIMIT_REACH_STR		"Reach       "
#define LIMIT_REACH_EXEC_STR	"Reach exec  "
#define LIMIT_EXPIRE_STR	"Expire      "
#define LIMIT_EXPIRE_EXEC_STR	"Expire exec "
#define LIMIT_UPDATED_STR	"Updated     "

struct limit_event {
	const char	*name;
	u_int		set;
	u_int		idx;
};

#define EVENT_TBL_ENTRY(X) { LIMIT_ ## X ## _STR, IPA_LIMIT_EVENT_ ## X ## _SET, IPA_LIMIT_EVENT_ ## X }

static const struct limit_event limit_event_tbl[] = {
	EVENT_TBL_ENTRY(START),
	EVENT_TBL_ENTRY(RESTART),
	EVENT_TBL_ENTRY(RESTART_EXEC),
	EVENT_TBL_ENTRY(REACH),
	EVENT_TBL_ENTRY(REACH_EXEC),
	EVENT_TBL_ENTRY(EXPIRE),
	EVENT_TBL_ENTRY(EXPIRE_EXEC),
	EVENT_TBL_ENTRY(UPDATED)
};

#undef EVENT_TBL_ENTRY

static int
output_limits_stat_raw(const struct opt_rule *opt_rule)
{
	const struct rule *rule;
	const struct limit *limit;
	const struct opt_limit *opt_limit;
	const struct ipa_limit_state *state, *state_end;
	const struct limit_stat *limit_stat;
	const struct limit_stat_list *limit_stat_list;
	const struct limit_event  *event;

	if (need_nl_raw) {
		if (output("\n") < 0)
			goto failed;
	} else
		need_nl_raw = 1;

	rule = opt_rule->rule;
	if (output("Rule  : %s\nInfo  : %s\n",
	    rule->rule_name, rule->rule_info != NULL ? rule->rule_info : "") < 0)
		goto failed;

	STAILQ_FOREACH(opt_limit, &opt_rule->opt_limits, link) {
		limit = opt_limit->limit;
		if (output("\nLimit : %s\nInfo  : %s\n",
		    limit->limit_name,
		    limit->limit_info != NULL ? limit->limit_info : "") < 0)
			goto failed;

		limit_stat_list = opt_limit->data;

		STAILQ_FOREACH(limit_stat, limit_stat_list, link) {
			if (limit_stat->opt_tint == NULL) {
				if (output("\nFrom  : current\nTo    : current\n") < 0)
					goto failed;
			} else {
				if (output_ipa_tm("\nFrom ", ':', &limit_stat->opt_tint->tm1, 1) < 0)
					goto failed;
				if (output_ipa_tm("To   ", ':', &limit_stat->opt_tint->tm2, 1) < 0)
					goto failed;
			}
			if (limit_stat->n != 0) {
				state = limit_stat->buf;
				state_end = state + limit_stat->n;
				for (; state != state_end; ++state) {
					if (output("\n-------------+---------------------\n") < 0)
						goto failed;
					if (state->lim != UINT64_C(0) || limit_stat->opt_tint != NULL) {
						for (event = limit_event_tbl; event < limit_event_tbl + IPA_LIMIT_EVENT_NUM; ++event)
							if (output_ipa_tm(event->name, '|',
							    &state->event_date[event->idx],
							    state->event_date_set & event->set) < 0)
								goto failed;
						if (output("Limit        | %20"PRIu64"\nCounter      | %20"PRIu64"",
						    state->lim, state->cnt) < 0)
							goto failed;
					} else {
						for (event = limit_event_tbl; event < limit_event_tbl + IPA_LIMIT_EVENT_NUM; ++event)
							if (output("%s |\n", event->name) < 0)
								goto failed;
						if (output("Limit        |       NOT_REGISTERED\nCounter      |") < 0)
							goto failed;
					}
				}
				if (output("\n-------------+---------------------\n") < 0)
					goto failed;
			}
			if (output("\n * %u state%s\n", limit_stat->n,
			    plural_trailing(limit_stat->n)) < 0)
				goto failed;
		}
	}

	if (output_flush() < 0) {
		logmsgx(IPA_LOG_ERR, "output_limits_stat_raw: output_flush failed");
		return -1;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "output_limits_stat_raw: output failed");
	return -1;
}

static int
show_limits_stat(void)
{
	int	error;
	u_int	n;
	struct ipa_limit_state *buf;
	struct rule *rule;
	struct limit *limit;
	struct opt_rule *opt_rule;
	struct opt_limit *opt_limit;
	struct limit_stat *limit_stat, *limit_stat_next;
	struct limit_stat_list *limit_stat_list;
	const struct opt_tint *opt_tint;

	error = -1;

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule = opt_rule->rule;
		if (rule->rule_info == NULL)
			if (st_get_rule_info(rule, &rule->rule_info) < 0) {
				logmsgx(IPA_LOG_ERR, "show_limits_stat: st_get_rule_info failed");
				goto done;
			}
		STAILQ_FOREACH(opt_limit, &opt_rule->opt_limits, link) {
			limit = opt_limit->limit;
			if (limit->limit_info == NULL)
				if (st_get_limit_info(rule, limit, &limit->limit_info) < 0) {
					logmsgx(IPA_LOG_ERR, "show_limits_stat: st_get_limit_info failed");
					goto done;
				}
			if ( (limit_stat_list = mem_malloc(sizeof *limit_stat_list, m_anon)) == NULL) {
				logmsgx(IPA_LOG_ERR, "show_limits_stat: mem_malloc failed");
				goto done;
			}
			STAILQ_INIT(limit_stat_list);
			opt_limit->data = limit_stat_list;

			if (STAILQ_EMPTY(&opt_tint_list)) {
				if (st_get_limit_stat(rule, limit,
				    (ipa_tm *)NULL, (ipa_tm *)NULL,
				    &n, &buf) < 0) {
					logmsgx(IPA_LOG_ERR, "show_limits_stat: st_get_limit_stat failed");
					goto done;
				}
				if ( (limit_stat = mem_malloc(sizeof *limit_stat, m_anon)) == NULL) {
					logmsgx(IPA_LOG_ERR, "show_limits_stat: mem_malloc failed");
					mem_free(buf, m_result);
					goto done;
				}
				limit_stat->n = n;
				limit_stat->buf = buf;
				limit_stat->opt_tint = NULL;
				STAILQ_INSERT_TAIL(limit_stat_list, limit_stat, link);
			} else STAILQ_FOREACH(opt_tint, &opt_tint_list, link) {
				if (st_get_limit_stat(rule, limit,
				    &opt_tint->tm1, &opt_tint->tm2,
				    &n, &buf) < 0) {
					logmsgx(IPA_LOG_ERR, "show_limits_stat: st_get_limit_stat failed");
					goto done;
				}
				if ( (limit_stat = mem_malloc(sizeof *limit_stat, m_anon)) == NULL) {
					logmsgx(IPA_LOG_ERR, "show_limits_stat: mem_malloc failed");
					mem_free(buf, m_result);
					goto done;
				}
				limit_stat->n = n;
				limit_stat->buf = buf;
				limit_stat->opt_tint = opt_tint;
				STAILQ_INSERT_TAIL(limit_stat_list, limit_stat, link);
			}
		}
	}

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if (output_limits_stat_raw(opt_rule) < 0) {
			logmsgx(IPA_LOG_ERR, "show_limits_stat: output_limits_stat_raw failed");
			goto done;
		}

	error = 0;

done:
	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		STAILQ_FOREACH(opt_limit, &opt_rule->opt_limits, link) {
			limit_stat_list = opt_limit->data;
			if (limit_stat_list != NULL) {
				for (limit_stat = STAILQ_FIRST(limit_stat_list); limit_stat != NULL; limit_stat = limit_stat_next) {
					limit_stat_next = STAILQ_NEXT(limit_stat, link);
					mem_free(limit_stat->buf, m_result);
					mem_free(limit_stat, m_anon);
				}
				mem_free(limit_stat_list, m_anon);
			}
		}

	return error;
}


#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
static int
show_thresholds_list(void)
{
	int	error;
	u_int	n;
	struct rule *rule;
	struct entity_desc *entity_desc;
	struct ipa_entity_desc *desc_list;
	struct opt_rule *opt_rule;

	error = -1;

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule = opt_rule->rule;
		if (rule->rule_info == NULL)
			if (st_get_rule_info(rule, &rule->rule_info) < 0) {
				logmsgx(IPA_LOG_ERR, "show_thresholds_list: st_get_rule_info failed");
				goto done;
			}
		if (st_get_thresholds_list(rule, &n, &desc_list) < 0) {
			logmsgx(IPA_LOG_ERR, "show_thresholds_list: st_get_thresholds_list failed");
			goto done;
		}
		if ( (entity_desc = mem_malloc(sizeof *entity_desc, m_anon)) == NULL) {
			logmsgx(IPA_LOG_ERR, "show_thresholds_list: mem_malloc failed");
			free_desc_list(n, desc_list);
			goto done;
		}
		entity_desc->desc_list = desc_list;
		entity_desc->n = n;
		opt_rule->data = entity_desc;
		qsort(desc_list, n, sizeof *desc_list, cmp_entity_desc);
	}

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if (output_any_limits_list_raw(opt_rule, "Threshold") < 0) {
			logmsgx(IPA_LOG_ERR, "show_thresholds_list: output_any_limits_list_raw failed");
			goto done;
		}

	error = 0;

done:
	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if ( (entity_desc = opt_rule->data) != NULL) {
			free_desc_list(entity_desc->n, entity_desc->desc_list);
			mem_free(entity_desc, m_anon);
		}

	return error;
}

static int
output_thresholds_stat_raw(const struct opt_rule *opt_rule)
{
	const struct rule *rule;
	const struct threshold *threshold;
	const struct ipa_threshold_state *state;
	const struct opt_threshold *opt_threshold;

	if (need_nl_raw) {
		if (output("\n") < 0)
			goto failed;
	} else
		need_nl_raw = 1;

	rule = opt_rule->rule;
	if (output("Rule      : %s\nInfo      : %s\n",
	    rule->rule_name, rule->rule_info != NULL ? rule->rule_info : "") < 0)
		goto failed;

	STAILQ_FOREACH(opt_threshold, &opt_rule->opt_thresholds, link) {
		threshold = opt_threshold->threshold;
		if (output("\nThreshold : %s\nInfo      : %s\n\n",
		    threshold->threshold_name,
		    threshold->threshold_info != NULL ? threshold->threshold_info : "") < 0)
			goto failed;

		state = &opt_threshold->data;

		if (output("----------+---------------------\n") < 0)
			goto failed;
		if (state->thr != UINT64_C(0)) {
			if (output_ipa_tm("From     ", '|', &state->tm_from, 1) < 0)
				goto failed;
			if (output_ipa_tm("Updated  ", '|', &state->tm_updated, 1) < 0)
				goto failed;
			if (output("Threshold | %20"PRIu64"\nCounter   | %20"PRIu64"\n",
			    state->thr, state->cnt) < 0)
				goto failed;
		} else {
			if (output("From      |\nUpdated   |\nThreshold |       NOT_REGISTERED\nCounter   |\n") < 0)
				goto failed;
		}
		if (output("----------+---------------------\n") < 0)
			goto failed;
	}

	if (output_flush() < 0) {
		logmsgx(IPA_LOG_ERR, "output_thresholds_stat_raw: output_flush failed");
		return -1;
	}

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "output_thresholds_stat_raw: output failed");
	return -1;
}

static int
show_thresholds_stat(void)
{
	int	error;
	struct rule *rule;
	struct threshold *threshold;
	struct opt_rule *opt_rule;
	struct opt_threshold *opt_threshold;

	error = -1;

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link) {
		rule = opt_rule->rule;
		if (rule->rule_info == NULL)
			if (st_get_rule_info(rule, &rule->rule_info) < 0) {
				logmsgx(IPA_LOG_ERR, "show_thresholds_stat: st_get_rule_info failed");
				goto done;
			}
		STAILQ_FOREACH(opt_threshold, &opt_rule->opt_thresholds, link) {
			threshold = opt_threshold->threshold;
			if (threshold->threshold_info == NULL)
				if (st_get_threshold_info(rule, threshold,
				    &threshold->threshold_info) < 0) {
					logmsgx(IPA_LOG_ERR, "show_thresholds_stat: st_get_threshold_info failed");
					goto done;
				}
			if (st_get_threshold_stat(rule, threshold, &opt_threshold->data) < 0) {
				logmsgx(IPA_LOG_ERR, "show_thresholds_stat: st_get_threshold_stat failed");
				goto done;
			}
		}
	}

	STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
		if (output_thresholds_stat_raw(opt_rule) < 0) {
			logmsgx(IPA_LOG_ERR, "show_thresholds_stat: output_thresholds_stat_raw failed");
			goto done;
		}

	error = 0;

done:
	return error;
}
#endif /* WITH_THRESHOLDS */

int
ipastat_main(void)
{
	int error;

	/* Check if there is any query in -q. */
	if (a_flag != A_FLAG_RULES && STAILQ_EMPTY(&opt_rules_list)) {
		logmsgx(IPA_LOG_WARNING, "ipastat_main: no query is given");
		return 0;
	}

	/* Pre-init statistics modules. */
	if (pre_init_st_mods() < 0) {
		logmsgx(IPA_LOG_ERR, "ipastat_main: pre_init_st_mods failed");
		return -1;
	}

	/* Init rules, limits or thresholds. */
	if (init_rules() < 0) {
		logmsgx(IPA_LOG_ERR, "ipastat_main: init_rules failed");
		return -1;
	}

	/* Init statistics modules. */
	if (init_st_mods() < 0) {
		logmsgx(IPA_LOG_ERR, "ipastat_main: init_st_mods failed");
		return -1;
	}

	error = 0;

	switch (a_flag) {
	case A_FLAG_ABSENT:
		switch (cur_opt_rule->type) {
		case OPT_RULE_RULE:
			if (STAILQ_EMPTY(&opt_tint_list))
				init_opt_tint_default();
			error = show_rules_stat();
			break;
#ifdef WITH_LIMITS
		case OPT_RULE_LIMIT:
			error = show_limits_stat();
			break;
#endif
#ifdef WITH_THRESHOLDS
		case OPT_RULE_THRESHOLD:
			error = show_thresholds_stat();
			break;
#endif
		}
		break;
	case A_FLAG_RULES:
		error = show_rules_list();
		break;
#ifdef WITH_LIMITS
	case A_FLAG_LIMITS:
		error = show_limits_list();
		break;
#endif
#ifdef WITH_THRESHOLDS
	case A_FLAG_THRESHOLDS:
		error = show_thresholds_list();
		break;
#endif
	}

	return error;
}

int
deinit_all(void)
{
	int error = 0;

	/* First deinit rules. */
	if (deinit_rules() < 0) {
		logmsgx(IPA_LOG_ERR, "deinit_all: deinit_rules failed");
		error = -1;
	}

	/* Deinit statistics modules. */
	if (deinit_st_mods() < 0) {
		logmsgx(IPA_LOG_ERR, "deinit_all: deinit_st_mods failed");
		error = -1;
	}

	return error;
}


syntax highlighted by Code2HTML, v. 0.9.1