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

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <regex.h>

#include "ipa_mod.h"

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

/*
 * All strto_xxx() functions get strings with positive decimal
 * integers, so it is not necessary to check '-' in the first
 * character of a string inside strto_xxx() functions (remember,
 * that strtoul() and strtoull() work with negative values).
 */

extern void *m_anon;

#define LOG_BUF_SIZE	1024

const char *const conf_event_msg[] = {
	"GLOBAL_BEGIN",		/*  0 */
	"GLOBAL_END",		/*  1 */
	"RULE_BEGIN",		/*  2 */
	"RULE_END",		/*  3 */
	"LIMIT_BEGIN",		/*  4 */
	"LIMIT_END",		/*  5 */
	"THRESHOLD_BEGIN",	/*  6 */
	"THRESHOLD_END",	/*  7 */
	"AUTORULE_BEGIN",	/*  8 */
	"AUTORULE_END",		/*  9 */
	"RULEPAT_BEGIN",	/* 10 */
	"RULEPAT_END",		/* 11 */
	"CUSTOM_SECT_BEGIN",	/* 12 */
	"CUSTOM_SECT_END"	/* 13 */
};

const char	*curmodfile;	/* File name of the current module (in sense of configuration prefix). */
const char	*curparam;	/* Current parameter name. */
const char	*cursect;	/* Current section name. */

/* For reporting errors from regcomp(3) function. */
int		re_errcode;
char		re_errbuf[RE_ERRBUF_SIZ];

int		need_nl = 0;	/* Need new line. */
int		was_space = 0;	/* There was already space, flag for print_space(). */
int		conf_indent;	/* Current output indent. */
int		mod_indent;	/* Additional module's output indent. */

void		*conf_sect_hentry_mzone;
void		*conf_param_hentry_mzone;

regex_t		reg_time;
regex_t		reg_bytes;

static char	*arg_string;
static char	*arg_misc;
static int	arg_int;
static int32_t	arg_int32;
static uint32_t	arg_uint32;
static int64_t	arg_int64;
static uint64_t	arg_uint64[2];

extern void	conferrx(const char *, ...) ATTR_FORMAT(printf, 1, 2);

/*
 * Form an error message in re_errbuf according to
 * re_errcode with the help from the regerror() function.
 */
void
re_form_errbuf(void)
{
	regerror(re_errcode, (regex_t *)NULL, re_errbuf, sizeof re_errbuf);
}

/*
 * This function is called by modules to output their log messages
 * about errors occurred during parsing configuration file.  If code
 * isn't zero, then output strerror(code) as well.
 */
void
mod_logconferr(const char *mod_name, int code, const char *format, va_list ap)
{
	int	rv, allocated = 0;
	char	buf[LOG_BUF_SIZE];
	char	*ptr;
	const char *what, *token, *tail;
	const char *in_var_msg;

	if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0)
		ptr = "(mod_logconferr: vsnprintf error)";
	else if (rv >= sizeof buf) {
		if ( (ptr = mem_malloc(++rv, m_anon)) == NULL)
			ptr = "(mod_logconferr: mem_malloc failed)";
		else {
			if (vsnprintf(ptr, rv, format, ap) < 0) {
				mem_free(ptr, m_anon);
				ptr = "(mod_logconferr: vsnprintf error again)";
			} else
				allocated = 1;
		}
	} else
		ptr = buf;

	if (parser_curpbuf != NULL) {
		if (curparam != NULL) {
			what = " parameter \"";
			token = curparam;
			tail = "\":";
		} else if (cursect != NULL) {
			what = " section \"";
			token = cursect;
			tail = "\":";
		} else
			what = token = tail = "\0";
		in_var_msg = parser_curpbuf->fp != NULL ? "" : "(in variable's value)";
		if (code == 0)
			conferrx("%s:%u%s: MOD %s:%s%s%s %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, mod_name, what, token, tail, ptr);
		else
			conferrx("%s:%u%s: MOD %s:%s%s%s %s: %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, mod_name, what, token, tail, ptr, strerror(code));
	} else {
		if (code == 0)
			conferrx("MOD %s: %s", mod_name, ptr);
		else
			conferrx("MOD %s: %s: %s", mod_name, ptr, strerror(code));
	}

	if (allocated)
		mem_free(ptr, m_anon);
}

/*
 * Output an error message for some line in the configuration file.
 * If code isn't zero, then output strerror(code) as well.
 * This function is called from places where parser_curpbuf is defined.
 */
static void
vlogconferr(int code, const char *format, va_list ap)
{
	int	rv, allocated = 0;
	char	buf[LOG_BUF_SIZE];
	char	*ptr;
	const char *what, *token, *tail;
	const char *in_var_msg = parser_curpbuf->fp != NULL ? "" : "(in variable's value)";

	if (curparam != NULL) {
		token = curparam;
		what = " parameter \"";
		tail = "\":";
	} else if (cursect != NULL) {
		token = cursect;
		what = " section \"";
		tail = "\":";
	} else
		what = token = tail = "\0";

	if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0)
		ptr = "(vlogconferr: vsnprintf error)";
	else if (rv >= sizeof buf) {
		if ( (ptr = mem_malloc(++rv, m_anon)) == NULL)
			ptr = "(vlogconferr: mem_malloc failed)";
		else {
			if (vsnprintf(ptr, rv, format, ap) < 0) {
				mem_free(ptr, m_anon);
				ptr = "(vlogconferr: vsnprintf error again)";
			} else
				allocated = 1;
		}
	} else
		ptr = buf;

	if (code == 0) {
		if (curmodfile == NULL)
			conferrx("%s:%u%s:%s%s%s %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, what, token, tail, ptr);
		else
			conferrx("%s:%u%s: module %s:%s%s%s %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, curmodfile, what, token, tail, ptr);
	} else {
		if (curmodfile == NULL)
			conferrx("%s:%u%s:%s%s%s %s: %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, what, token, tail, ptr, strerror(code));
		else
			conferrx("%s:%u%s: module %s:%s%s%s %s: %s", parser_curpbuf->fname, parser_curpbuf->lineno,
			    in_var_msg, curmodfile, what, token, tail, ptr, strerror(code));
	}

	if (allocated)
		mem_free(ptr, m_anon);
}

/*
 * Log an error message during configuration,
 * use errno as error code.
 */
void
logconferr(const char *format, ...)
{
	int	errno_save = errno;
	va_list	va;

	va_start(va, format);
	vlogconferr(errno_save, format, va);
	va_end(va);
}

/*
 * Log an error message during configuration,
 * don't use errno.
 */
void
logconferrx(const char *format, ...)
{
	va_list	va;

	va_start(va, format);
	vlogconferr(0, format, va);
	va_end(va);
}

/*
 * Convert string to uint32_t.
 * Assume that sizeof(u_long) >= sizeof(uint32_t).
 */
int
strto_uint32(uint32_t *result, const char *nptr, char **endptr_ret)
{
	char	*endptr;
	u_long	val_ul;

	errno = 0;
	val_ul = strtoul(nptr, &endptr, 10);
	if (errno != 0) {
		logconferr("strtoul");
		return -1;
	}
	if (val_ul > UINT32_MAX) {
		logconferrx("too big value for uint32_t type");
		return -1;
	}
	if (nptr == endptr) {
		logconferrx("wrong number");
		return -1;
	}
	*result = (uint32_t)val_ul;
	if (endptr_ret != NULL)
		*endptr_ret = endptr;
	return 0;
}

/*
 * Convert string to uint64_t.
 * Assume that sizeof(unsigned long long) >= sizeof(uint64_t).
 */
static int
strto_uint64(uint64_t *result, const char *nptr, char **endptr_ret)
{
	char	*endptr;
	unsigned long long val_ull;

	errno = 0;
	val_ull = strtoull(nptr, &endptr, 10);
	if (errno != 0) {
		logconferr("strtoull");
		return -1;
	}
	if (val_ull > UINT64_MAX) {
		logconferrx("too big value for uint64_t type");
		return -1;
	}
	if (nptr == endptr) {
		logconferrx("wrong number");
		return -1;
	}
	*result = (uint64_t)val_ull;
	if (endptr_ret != NULL)
		*endptr_ret = endptr;
	return 0;
}

/*
 * Convert string to u_int.
 * Assumed that sizeof(u_long) >= sizeof(u_int).
 */
int
strto_u_int(u_int *result, const char *nptr, char **endptr)
{
	u_long val_ul;

	errno = 0;
	val_ul = strtoul(nptr, endptr, 10);
	if (errno != 0) {
		logconferr("strtoul");
		return -1;
	}
	if (val_ul > UINT_MAX) {
		logconferrx("too big value for u_int type");
		return -1;
	}
	*result = (u_int)val_ul;
	return 0;
}

static int
check_unsigned_integer(const char *ptr, const char *what)
{
	for (; *ptr != '\0'; ++ptr)
		if (isdigit(*ptr) == 0) {
			logconferrx("argument should be %s integer", what);
			return -1;
		}
	return 0;
}

/*
 * Convert string to uint32_t, allow user to specify
 * a `+' sign before number.
 */
static int
get_arg_uint32(void *res)
{
	char *ptr = parser_args;

	if (*ptr == '+')
		++ptr;
	if (check_unsigned_integer(ptr, "a positive") < 0)
		return -1;
	return strto_uint32((uint32_t *)res, ptr, (char **)NULL);
}

/*
 * Convert string to int32_t, allow user to specify
 * `-' and `+' signs before number.
 */
static int
get_arg_int32(void *res)
{
	char	*ptr = parser_args;
	int	sign;
	uint32_t val;

	switch (*ptr) {
	case '-':
		sign = -1;
		++ptr;
		break;
	case '+':
		++ptr;
		/* FALLTHROUGH */
	default:
		sign = 1;
	}

	if (check_unsigned_integer(ptr, "an") < 0)
		return -1;
	if (strto_uint32(&val, ptr, (char **)NULL) < 0)
		return -1;

	if (sign < 0) {
		/* This can be wrong on systems where |INT32_MIN| > INT32_MAX */
		if (val > INT32_MAX) {
			logconferrx("too little value for int32_t type");
			return -1;
		}
		*(int32_t *)res = -(int32_t)val;
	} else {
		if (val > INT32_MAX) {
			logconferrx("too big value for int32_t type");
			return -1;
		}
		*(int32_t *)res = (int32_t)val;
	}

	return 0;
}

/*
 * Convert string to int64_t, allow user to specify
 * `-' and `+' signs before number.
 */
static int
get_arg_int64(void *res)
{
	char	*ptr = parser_args;
	int	sign;
	uint64_t val;

	switch (*ptr) {
	case '-':
		sign = -1;
		++ptr;
		break;
	case '+':
		++ptr;
		/* FALLTHROUGH */
	default:
		sign = 1;
	}

	if (check_unsigned_integer(ptr, "an") < 0)
		return -1;
	if (strto_uint64(&val, ptr, (char **)NULL) < 0)
		return -1;

	if (sign < 0) {
		/* This can be wrong on systems where |INT64_MIN| > INT64_MAX */
		if (val > INT64_MAX) {
			logconferrx("too little value for int64_t type");
			return -1;
		}
		*(int64_t *)res = -(int64_t)val;
	} else {
		if (val > INT64_MAX) {
			logconferrx("too big value for int64_t type");
			return -1;
		}
		*(int64_t *)res = (int64_t)val;
	}

	return 0;
}

/*
 * Convert string to uint64_t, allow user to specify
 * a `+' sign before number.
 */
int
get_arg_uint64(void *res)
{
	char *ptr = parser_args;

	if (*ptr == '+')
		++ptr;
	if (check_unsigned_integer(ptr, "a positive") < 0)
		return -1;
	return strto_uint64((uint64_t *)res, ptr, (char **)NULL);
}

int
get_arg_time(void *res)
{
	char	*ptr = parser_args, *endptr;
	int	level = 0, error = 0, overflow = 0;
	uint64_t value, result = UINT64_C(0);

	for (;;) {
		if (strto_uint64(&value, ptr, &endptr) < 0)
			return -1;
		ptr = endptr;
		switch (*ptr) {
		case 'h':
			if (level > 0)
				error = 1;
			else {
				if (value > UINT64_MAX / SECONDS_IN_HOUR)
					overflow = 1;
				else {
					level = 1;
					value *= SECONDS_IN_HOUR;
				}
			}
			break;
		case 'm':
			if (level > 1)
				error = 1;
			else {
				if (value > UINT64_MAX / SECONDS_IN_MINUTE)
					overflow = 1;
				else {
					level = 2;
					value *= SECONDS_IN_MINUTE;
				}
			}
			break;
		default: /* 's' */
			if (level > 2)
				error = 1;
			else
				level = 3;
		}
		if (error) {
			logconferrx("wrong time format");
			return -1;
		}
		if (overflow || result > UINT64_MAX - value) {
			logconferrx("too big value for uint64_t type");
			return -1;
		}

		result += value;

		if (*++ptr == '\0')
			break;	/* EOL */
		if (*ptr == ' ')
			++ptr;
	}
	*(uint64_t *)res = result;
	return 0;
}

/*
 * Parse a value for the boolean variable,
 * all comparisons are case insensitive.
 */
static int
get_arg_boolean(void *res)
{
	if (strcasecmp(parser_args, "yes") == 0)
		*(int *)res = 1;
	else if (strcasecmp(parser_args, "no") == 0)
		*(int *)res = 0;
	else {
		logconferrx("argument should be \"yes\" or \"no\"");
		return -1;
	}
	return 0;
}

static int
get_arg_string(void *res)
{
	char *ptr;

	if (!parser_arg_is_string()) {
		logconferrx("argument should be a string");
		return -1;
	}
	if ( (ptr = parser_string_strdup(parser_args, m_parser)) == NULL)
		return -1;
	*(char **)res = ptr;
	return 0;
}

int
get_arg_bytes(void *res)
{
	char	*ptr = parser_args, *endptr;
	int	level = 0, error = 0, overflow = 0;
	uint64_t value, result = UINT64_C(0);

	for (;;) {
		if (strto_uint64(&value, ptr, &endptr) < 0)
			return -1;
		ptr = endptr;
		switch (*ptr) {
		case 'T':
			if (level > 0)
				error = 1;
			else {
				if (value > UINT64_MAX / TBYTE)
					overflow = 1;
				else {
					level = 1;
					value *= TBYTE;
				}
			}
			break;
		case 'G':
			if (level > 1)
				error = 1;
			else {
				if (value > UINT64_MAX / GBYTE)
					overflow = 1;
				else {
					level = 2;
					value *= GBYTE;
				}
			}
			break;
		case 'M':
			if (level > 2)
				error = 1;
			else {
				if (value > UINT64_MAX / MBYTE)
					overflow = 1;
				else {
					level = 3;
					value *= MBYTE;
				}
			}
			break;
		case 'K':
			if (level > 3)
				error = 1;
			else {
				if (value > UINT64_MAX / KBYTE)
					overflow = 1;
				else {
					level = 4;
					value *= KBYTE;
				}
			}
			break;
		default: /* 'B' */
			if (level > 4)
				error = 1;
			else
				level = 5;
		}
		if (error) {
			logconferrx("wrong format of an argument");
			return -1;
		}
		if (overflow || result > UINT64_MAX - value) {
			logconferrx("too big value for uint64_t type");
			return -1;
		}

		result += value;
		if (*++ptr == '\0')
			break;	/* EOL */
		if (*ptr == ' ')
			++ptr;
	}
	*(uint64_t *)res = result;
	return 0;
}

static int
get_arg_value(void *res)
{
	char	*ptr = parser_args;
	uint64_t type, val;

	if (regexec(&reg_bytes, ptr, 0, (regmatch_t *)NULL, 0) == 0) {
		if (get_arg_bytes(&val) < 0)
			return -1;
		type = IPA_CONF_TYPE_BYTES;
	} else if (regexec(&reg_time, ptr, 0, (regmatch_t *)NULL, 0) == 0) {
		if (get_arg_time(&val) < 0)
			return -1;
		type = IPA_CONF_TYPE_TIME;
	} else {
		if (get_arg_uint64(&val) < 0)
			return -1;
		type = IPA_CONF_TYPE_UINT64;
	}
	*(uint64_t *)res = type;
	*((uint64_t *)res + 1) = val;
	return 0;
}

int
get_arg_per_cent(void *res)
{
	char	*ptr;
	uint32_t per_cent;

	ptr = parser_args + parser_args_len - 1;
	if (*ptr != '%') {
		logconferrx("wrong format of an argument");
		return -1;
	}
	*ptr = '\0';
	if (get_arg_uint32(&per_cent) < 0)
		return -1;
	if (per_cent > 100) {
		logconferrx("per cent value should be <= 100%%");
		return -1;
	}
	*(int *)res = per_cent;
	return 0;
}

/*
 * Simply point *res to read string from configuration file.
 */
static int
get_arg_misc(void *res)
{
	*(char **)res = parser_args;
	return 0;
}

static u_int
get_conf_hash_value(const char *name)
{
	u_int hash_value;

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

	return hash_value;
}

#define get_conf_sect_hash_value(x) get_conf_hash_value(x)
#define get_conf_param_hash_value(x) get_conf_hash_value(x)

#define get_conf_sect_bucket(x) ((x) & (CONF_SECT_BUCKETS - 1))
#define get_conf_param_bucket(x) ((x) & (CONF_PARAM_BUCKETS - 1))

/*
 * Compile regular expressions and init hash tables for
 * configuration sections and parameters.
 */
int
init_conf_tbls(const char *mod_file, int build_regexp,
    ipa_conf_sect *sect_tbl, struct conf_sect_hash **sect_hash_ret,
    ipa_conf_param *param_tbl, struct conf_param_hash **param_hash_ret)
{
	u_int	i;
	const ipa_conf_sect *sect;
	const ipa_conf_param *param;
	struct conf_sect_hash *sect_hash;
	struct conf_param_hash *param_hash;
	struct conf_sect_hentry *sect_hentry;
	struct conf_param_hentry *param_hentry;

	/* Allocate hash buckets. */
	if ( (sect_hash = mem_malloc(CONF_SECT_BUCKETS * sizeof *sect_hash, m_anon)) == NULL) {
		conferrx("init_conf_tbls: mem_malloc failed");
		return -1;
	}

	/* Init head of each hash bucket. */
	for (i = 0; i < CONF_SECT_BUCKETS; ++i)
		SLIST_INIT(&sect_hash[i]);

	*sect_hash_ret = sect_hash;

	/* Build regular expressions and hash entries. */
	for (sect = sect_tbl, i = 0; sect->sect_name != NULL; ++i, ++sect) {
		if (sect->arg_pattern != NULL && build_regexp)
			if ( (re_errcode = regcomp(sect->arg_regexp, sect->arg_pattern, REG_EXTENDED|REG_NOSUB)) != 0) {
				re_form_errbuf();
				if (mod_file == NULL)
					conferrx("init_conf_tbls: regcomp(\"%s\"): %s",
					    sect->arg_pattern, re_errbuf);
				else
					conferrx("init_conf_tbls for module %s: regcomp(\"%s\"): %s",
					    mod_file, sect->arg_pattern, re_errbuf);
				return -1;
			}
		if ( (sect_hentry = mzone_alloc(conf_sect_hentry_mzone)) == NULL) {
			conferrx("init_conf_tbls: mzone_malloc failed");
			return  -1;
		}
		sect_hentry->sect_name = sect->sect_name;
		sect_hentry->idx = i;
		sect_hentry->hash_value = get_conf_sect_hash_value(sect_hentry->sect_name);
		SLIST_INSERT_HEAD(&sect_hash[get_conf_sect_bucket(sect_hentry->hash_value)], sect_hentry, link);
	}

	/* Allocate hash buckets. */
	if ( (param_hash = mem_malloc(CONF_PARAM_BUCKETS * sizeof *param_hash, m_anon)) == NULL) {
		conferrx("init_conf_tbls: mem_malloc failed");
		return -1;
	}

	/* Init head of each hash bucket. */
	for (i = 0; i < CONF_PARAM_BUCKETS; ++i)
		SLIST_INIT(&param_hash[i]);

	*param_hash_ret = param_hash;

	/* Build regular expressions and hash entries. */
	for (param = param_tbl, i = 0; param->param_name != NULL; ++i, ++param) {
		if (param->arg_pattern != NULL && build_regexp)
			if ( (re_errcode = regcomp(param->arg_regexp, param->arg_pattern, REG_EXTENDED|REG_NOSUB)) != 0) {
				re_form_errbuf();
				if (mod_file == NULL)
					conferrx("init_conf_tbls: regcomp(\"%s\"): %s",
					    param->arg_pattern, re_errbuf);
				else
					conferrx("init_conf_tbls for module %s: regcomp(\"%s\"): %s",
					    mod_file, param->arg_pattern, re_errbuf);
				return -1;
			}
		if ( (param_hentry = mzone_alloc(conf_param_hentry_mzone)) == NULL) {
			conferrx("init_conf_tbls: mzone_malloc failed");
			return  -1;
		}
		param_hentry->param_name = param->param_name;
		param_hentry->idx = i;
		param_hentry->hash_value = get_conf_param_hash_value(param_hentry->param_name);
		SLIST_INSERT_HEAD(&param_hash[get_conf_param_bucket(param_hentry->hash_value)], param_hentry, link);
	}

	return 0;
}

const ipa_conf_sect *
find_conf_sect(const ipa_conf_sect *conf_sect_tbl,
    const struct conf_sect_hash *sect_hash, const char *sect_name)
{
	u_int	hash_value;
	const struct conf_sect_hash *hash_bucket;
	const struct conf_sect_hentry *conf_sect_hentry;

	hash_value = get_conf_sect_hash_value(sect_name);
	hash_bucket = &sect_hash[get_conf_sect_bucket(hash_value)];

	SLIST_FOREACH(conf_sect_hentry, hash_bucket, link)
		if (conf_sect_hentry->hash_value == hash_value &&
		    strcmp(conf_sect_hentry->sect_name, sect_name) == 0)
			return &conf_sect_tbl[conf_sect_hentry->idx];

	return NULL;
}

const ipa_conf_param *
find_conf_param(const ipa_conf_param *conf_param_tbl,
    const struct conf_param_hash *param_hash, const char *param_name)
{
	u_int	hash_value;
	const struct conf_param_hash *hash_bucket;
	const struct conf_param_hentry *conf_param_hentry;

	hash_value = get_conf_param_hash_value(param_name);
	hash_bucket = &param_hash[get_conf_param_bucket(hash_value)];

	SLIST_FOREACH(conf_param_hentry, hash_bucket, link)
		if (conf_param_hentry->hash_value == hash_value &&
		    strcmp(conf_param_hentry->param_name, param_name) == 0)
			return &conf_param_tbl[conf_param_hentry->idx];

	return NULL;
}

/*
 * Release memory hold by compiled regular expressions,
 * for configuration sections and parameters, hash entries
 * are deleted all at once with one mzone_deinit() call.
 */
void
deinit_conf_tbls(int free_regexp,
    ipa_conf_sect *sect_tbl, struct conf_sect_hash *sect_hash,
    ipa_conf_param *param_tbl, struct conf_param_hash *param_hash)
{
	const ipa_conf_sect *sect;
	const ipa_conf_param *param;

	if (free_regexp) {
		/* Release memory hold by regular expressions. */
		for (sect = sect_tbl; sect->sect_name != NULL; ++sect)
			if (sect->arg_pattern != NULL)
				regfree(sect->arg_regexp);
		for (param = param_tbl; param->param_name != NULL; ++param)
			if (param->arg_pattern != NULL)
				regfree(param->arg_regexp);
	}

	/* Release memory hold by hash buckets. */
	mem_free(sect_hash, m_anon);
	mem_free(param_hash, m_anon);
}

/*
 * All print_* functions are trivial, just take care about
 * need_nl and was_space variables.
 */

struct time_conv {
	uint64_t div;
	char	ch;
};

static const struct time_conv time_conv_tbl[] = {
	{ SECONDS_IN_HOUR,	'h'  },
	{ SECONDS_IN_MINUTE,	'm'  },
	{ 1,			's'  },
	{ 0,			'\0' }
};

void
print_time(const uint64_t *ptr)
{
	int	need_space = 0;
	uint64_t t, a = *ptr;
	const struct time_conv *time_conv;

	was_space = 0;

	if (a == UINT64_C(0)) {
		printf("0s");
		return;
	}

	for (time_conv = time_conv_tbl; time_conv->div > UINT64_C(0); ++time_conv) {
		t = a / time_conv->div;
		if (t != UINT64_C(0)) {
			if (need_space)
				printf(" ");
			printf("%"PRIu64"%c", t, time_conv->ch);
			need_space = 1;
			a -= t * time_conv->div;
		}
	}
}

struct byte_conv {
	uint64_t div;
	char	ch;
};

static const struct byte_conv byte_conv_tbl[] = {
	{ TBYTE, 'T'  },
	{ GBYTE, 'G'  },
	{ MBYTE, 'M'  },
	{ KBYTE, 'K'  },
	{ 1,	 'B'  },
	{ 0,	 '\0' }
};

void
print_bytes(const uint64_t *bytes)
{
	int	need_space = 0;
	uint64_t t, a = *bytes;
	const struct byte_conv *byte_conv;

	was_space = 0;

	if (a == UINT64_C(0)) {
		printf("0B");
		return;
	}

	for (byte_conv = byte_conv_tbl; byte_conv->div > UINT64_C(0); ++byte_conv) {
		t = a / byte_conv->div;
		if (t != UINT64_C(0)) {
			if (need_space)
				printf(" ");
			printf("%"PRIu64"%c", t, byte_conv->ch);
			need_space = 1;
			a -= t * byte_conv->div;
		}
	}
}

void
print_value(const uint64_t *value, u_int value_type)
{
	switch (value_type) {
	case IPA_CONF_TYPE_BYTES:
		print_bytes(value);
		break;
	case IPA_CONF_TYPE_TIME:
		print_time(value);
		break;
	default: /* IPA_CONF_TYPE_UINT64 */
		printf("%"PRIu64, *value);
	}
}

void
print_boolean(int boolean)
{
	was_space = 0;
	if (boolean)
		printf("yes");
	else
		printf("no");
}

void
print_string(const char *string)
{
	const char *ptr;

	was_space = 0;

	printf("\"");
	for (ptr = string; *ptr != '\0'; ++ptr)
		switch (*ptr) {
		case '\t':
			printf("\\t");
			break;
		case '\n':
			printf("\\n");
			break;
		case '\"':
		case '\\':
			printf("\\");
			/* FALLTHROUGH */
		default:
			printf("%c", *ptr);
		}
	printf("\"");
}

void
print_param_name(const char *name, const char *param)
{
	printf("%*s%*s", conf_indent, "", mod_indent, "");
	if (name == NULL)
		printf("%s = ", param);
	else
		printf("%s:%s = ", name, param);
	was_space = 1;
}

void
print_args(const char *format, va_list ap)
{
	was_space = 0;
	vprintf(format, ap);
}

/*
 * Print the end of a configuration string.
 */
void
print_eol(void)
{
	printf(";\n");
}

/*
 * Print new line if needed.
 */
void
print_nl(void)
{
	if (need_nl) {
		printf("\n");
		need_nl = 0;
	}
}

void
print_param_end(void)
{
	print_eol();
	need_nl = 1;
}

void
print_sect_name(const char *name, const char *sect)
{
	printf("%*s%*s", conf_indent, "", mod_indent, "");
	if (name == NULL)
		printf("%s ", sect);
	else
		printf("%s:%s ", name, sect);
	was_space = 1;
}

void
print_sect_begin(void)
{
	printf("{\n");
}

void
print_sect_end(void)
{
	printf("%*s%*s}\n", conf_indent, "", mod_indent, "");
	need_nl = 1;
}

void
set_indent(int indent)
{
	mod_indent = indent * CONF_INDENT;
}

void
print_space(void)
{
	if (!was_space) {
		printf(" ");
		was_space = 1;
	}
}

struct conf_regexp {
	const char *pat;
	regex_t	*reg;
};

static struct conf_regexp conf_regexp_tbl[] = {
	{ PAT_BYTES,	&reg_bytes	},
	{ PAT_TIME,	&reg_time	},
	{ NULL,		NULL		}
};

/*
 * Build regular expressions for parsing some IPA_CONF_TYPE_xxx.
 */
int
build_conf_regexp(void)
{
	static int called = 0;
	struct conf_regexp *conf_regexp;

	if (called)
		return 0;

	for (conf_regexp = conf_regexp_tbl; conf_regexp->pat != NULL; ++conf_regexp)
		if ( (re_errcode = regcomp(conf_regexp->reg, conf_regexp->pat, REG_EXTENDED|REG_NOSUB)) != 0) {
			re_form_errbuf();
			conferrx("build_conf_regexp: regcomp(\"%s\"): %s", conf_regexp->pat, re_errbuf);
			return -1;
		}

	called = 1;

	return 0;
}

void *
dl_lookup_sym(dl_handle handle, const char *sym)
{
	void *ptr;
#ifdef SYM_PREFIX
	char *psym;
#endif

#ifdef SYM_PREFIX
	if (mem_asprintf(m_anon, &psym, SYM_PREFIX"%s", sym) < 0) {
		logconferrx("dl_lookup_sym: mem_asprintf failed");
		return NULL;
	}
	ptr = dl_sym(handle, psym);
	mem_free(psym, m_anon);
	if (ptr != NULL)
		return NULL;
#endif

	if ( (ptr = dl_sym(handle, sym)) == NULL) {
		logconferrx("dl_lookup_sym: dl_sym(\"%s\"): %s", sym, dl_error());
		return NULL;
	}

	return ptr;
}

/* get_arg_tbl[] must be ordered by IPA_CONF_TYPE_xxx values. */
const struct get_arg_tbl get_arg_tbl[] = {
	{ &arg_int32,	get_arg_int32,	NULL		}, /* IPA_CONF_TYPE_INT32 */
	{ &arg_uint32,	get_arg_uint32,	NULL		}, /* IPA_CONF_TYPE_UINT32 */
	{ &arg_int64,	get_arg_int64,	NULL,		}, /* IPA_CONF_TYPE_INT64 */
	{ arg_uint64,	get_arg_uint64,	NULL		}, /* IPA_CONF_TYPE_UINT64 */
	{ &arg_string,	get_arg_string,	NULL		}, /* IPA_CONF_TYPE_STRING */
	{ arg_uint64,	get_arg_bytes,	&reg_bytes	}, /* IPA_CONF_TYPE_BYTES */
	{ arg_uint64,	get_arg_time,	&reg_time	}, /* IPA_CONF_TYPE_TIME */
	{ arg_uint64,	get_arg_value,	NULL		}, /* IPA_CONF_TYPE_VALUE */
	{ &arg_int,	get_arg_per_cent, NULL		}, /* IPA_CONF_TYPE_PER_CENT */
	{ &arg_int,	get_arg_boolean, NULL		}, /* IPA_CONF_TYPE_BOOLEAN */
	{ &arg_misc,	get_arg_misc,	NULL		}  /* IPA_CONF_TYPE_MISC */
};


syntax highlighted by Code2HTML, v. 0.9.1