/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: sm-conf-type-choice.c,v 1.19 2006/04/10 18:11:45 ca Exp $")

#if SM_LIBCONF_ALONE
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include "sm-conf.h"
#include "sm-conf-util.h"
#else /* SM_LIBCONF_ALONE */
#include "sm/limits.h"
#include "sm/ctype.h"
#include "sm/string.h"
#include "sm/memops.h"
#include "sm/sm-conf.h"
#endif /* SM_LIBCONF_ALONE */

#include "sm-conf-node.h"
#include "sm-conf-state.h"
#include "sm-conf-type.h"
#include "sm-conf-u32.h"
#include "sm-conf-util.h"

/* tag for single choice element */
sm_conf_type_T const
sm_conf_type_choice_value_data;

static int
choice_scan(
	sm_conf_T		*smc,
	sm_conf_node_T const	*node,
	sm_conf_definition_T const *def,
	char const		*text,
	size_t			text_n,
	unsigned long		*val_out,
	int			*pnegate)
{
	char			loc[SM_CONF_ERROR_BUFFER_SIZE];
	sm_conf_definition_T const *val;
	int negate;

	SM_IS_CONF_DEF(def);
	if (text_n > 5 && strncasecmp(text, "dont_", 5) == 0)
		negate = 5;
	else if (text_n > 4 && strncasecmp(text, "not_", 4) == 0)
		negate = 4;
	else if (text_n > 3 && strncasecmp(text, "no_", 3) == 0)
		negate = 3;
	else if (text_n > 1 && (text[0] == '-' || text[0] == '~' ||
				text[0] == '!'))
		negate = 1;
	else
		negate = 0;

	val = sm_conf_subdef(def->scd_contents,
		sm_conf_type_choice_value,
		text + negate, text_n - negate);
	if (val == NULL)
	{
		if (def->scd_flags & SM_CONF_FLAG_NUMERIC)
		{
			unsigned long	num;

			/* allow a choice to be specified as a number. */
			if (sm_conf_u32_scan(smc, NULL, def, text, text_n,
					&num) == 0)
			{
				*val_out = num;
				return 0;
			}
		}
		sm_conf_error_add(smc, "%s: unexpected %s '%.*s'",
			sm_conf_node_location(smc, node, loc, sizeof loc),
			def->scd_flags & SM_CONF_FLAG_MULTIPLE
				? "flag" : "choice",
			(int)text_n, text);
		return SM_CONF_ERR_TYPE;
	}
	if (pnegate != NULL)
		*pnegate = negate;
	*val_out = val->scd_offset;

	return 0;
}

static int
choice_node_to_value_single(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	sm_conf_node_T			*node,
	void				*data,
	int				*pnegate)
{
	char const			*text;
	size_t				text_n;
	int				err;
	unsigned long			val;

	SM_IS_CONF_DEF(def);

	err = sm_conf_node_to_value(smc, "choice", node, &text, &text_n);
	if (err != 0)
		return err;

	err = choice_scan(smc, node, def, text, text_n, &val, pnegate);
	if (err != 0)
		return err;

	if (data != NULL)
		err = sm_conf_u32_store(data, def->scd_size, val);
	return err;
}

static int
sm_conf_type_choice_node_to_value(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	sm_conf_node_T			*node,
	void				*data)
{
	int				err, neg;
	unsigned long			val, ul;

	SM_IS_CONF_DEF(def);

	if (!(def->scd_flags & (SM_CONF_FLAG_MULTIPLE|SM_CONF_FLAG_OR)))
		return choice_node_to_value_single(smc, def, node, data, NULL);

	val = 0;
	err = 0;
	if (data != NULL)
		err = sm_conf_u32_load(data, def->scd_size, &val);

	if (sm_conf_node_type(smc, node) == SM_CONF_NODE_LIST &&
	    (def->scd_flags & SM_CONF_FLAG_MULTIPLE))
	{
		sm_conf_node_T	*ch = NULL;

		while ((ch = sm_conf_list_next(smc, node, ch)) != NULL)
		{
			err = choice_node_to_value_single( smc, def, ch, data,
							&neg);
			if (err != 0)
				return err;

			if (data != NULL)
			{
				err = sm_conf_u32_load(data, def->scd_size,
							&ul);
				if (err != 0)
					return err;
				if (neg)
					val &= ~ul;
				else
					val |= ul;
			}
		}
	}
	else
	{
		err = choice_node_to_value_single(smc, def, node, data, &neg);
		if (err != 0)
			return err;

		if (data != NULL)
		{
			err = sm_conf_u32_load(data, def->scd_size, &ul);
			if (err != 0)
				return err;
			if (neg)
				val &= ~ul;
			else
				val |= ul;
		}
	}
	if (data != NULL)
		err = sm_conf_u32_store(data, def->scd_size, val);
	return err;
}

static int
sm_conf_type_choice_value_check(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void const			*data)
{
	SM_IS_CONF_DEF(def);
	/* if we have a check function, use it. */
	if (def->scd_check != NULL)
		return (* def->scd_check)(smc, def->scd_check_data, def, data);
	return 0;
}

static int
sm_conf_type_choice_value_null(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void				*data)
{
	unsigned long			val;
	int				err;

	if (data != NULL)
	{
		val = 0;
		if (def->scd_default != NULL)
		{
			err = choice_scan(smc, NULL, def, def->scd_default,
				strlen(def->scd_default), &val, NULL);
#if 0
			/*
			**  How to allow for multiple choices, e.g.,
			**  "{flag1, flags2}" as initial value?
			**  It would be necessary to scan and parse the
			**  string that contains the default value first.
			*/

			err = sm_conf_type_choice_node_to_value(smc, def,
				def->scd_default,
				strlen(def->scd_default), &val);
#endif

			if (err != 0)
				return err;
		}
		err = sm_conf_u32_store(data, def->scd_size, val);
		if (err != 0)
			return err;
	}
	return 0;
}

sm_conf_type_T const
sm_conf_type_choice_data =
{
	sm_conf_type_choice_node_to_value,
	sm_conf_type_choice_value_check,
	sm_conf_type_choice_value_null
};


syntax highlighted by Code2HTML, v. 0.9.1