/*
 * 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-union.c,v 1.11 2006/04/10 18:11:45 ca Exp $")


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

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

/* flag */
sm_conf_type_T const
sm_conf_type_union_type_data,
sm_conf_type_union_choice_data;

/*
**  UNION_TYPE_DEFINITION -- return the sm_conf_definition_T for the union type
**
**	Returns the definition for the integer in which we store
**	which of the various competing substructures was selected.
**
**	Parameters:
**		smc -- overall context
**		def -- definition of the union
**
**	Returns:
**		NULL if there is a programming error in the array definition
**		(the array didn't have an element definition);
**		otherwise, a pointer to the first candidate definition.
*/

static sm_conf_definition_T const *
union_type_definition(sm_conf_T *smc, sm_conf_definition_T const *def)
{
	sm_conf_definition_T const		*sub;

	sub = sm_conf_subdef(
		def->scd_contents, sm_conf_type_union_type, NULL, 0);
	if (sub == NULL)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];

		sm_conf_error_add(smc, "%s: %s%s"
			"missing definition for type of multiple-choice object",
			sm_conf_node_location(smc, NULL, loc, sizeof loc),
			def->scd_name, def->scd_name[0] == '\0'? "" : ": ");
		return NULL;
	}
	return sub;
}

/*
**  UNION_NAMED_CHOICE -- (Utility) which choice does this name pick?
**
**	Parameters:
**		def -- the definition of the union object as a whole
**		name -- '\0'-terminated name
**		val_out -- assign the numeric choice value to this parameter.
**
**	Returns:
**		NULL if no specific subdefinition is selected,
**		otherwise a pointer to the selected subdefinition.
*/

static sm_conf_definition_T const *
union_named_choice(
	sm_conf_definition_T const	*def,
	char const			*name,
	unsigned long			*val_out)
{
	sm_conf_definition_T const	*sub;

	if (name == NULL)
		return NULL;

	/* Look up the type in our table. */
	for (sub = def->scd_contents; sub->scd_name != NULL; sub++)
		if (  sub->scd_type == sm_conf_type_union_choice
		   && strcasecmp(sub->scd_name, name))
		{
			if (val_out != NULL)
				*val_out = sub->scd_offset;
			return sub;
		}

	return NULL;
}


/*
**  UNION_IS_EMPTY -- (Utility) is this application value empty?
**
**	Parameters:
**		def -- the definition of the union object as a whole
**		val -- the application value
**
**	Returns:
**		true if the union is empty (with no alternative
**		selected), false otherwise.
*/

static bool
union_is_empty(sm_conf_definition_T const *def, void const *val)
{
	unsigned long			ul;
	sm_conf_definition_T const	*sub;

	if (  val == NULL
	   || def->scd_size == 0
	   || sm_conf_u32_load(val, def->scd_size, &ul) != 0
	   || ul != 0)
		return false;

	/* Look up the type in our table. */
	for (sub = def->scd_contents; sub->scd_name != NULL; sub++)
		if (  sub->scd_type   == sm_conf_type_union_choice
		   && sub->scd_offset == ul)
			return false;

	return true;
}

/*
**  UNION_VALUE_CHOICE -- (Utility) which union subtype is this?
**
**	Determine which union choice defintion applies to a value,
**	based on the value.
**
**	Parameters:
**		smc -- context
**		def -- the definition of the union object as a whole
**		val -- the value
**
**	Returns:
**		NULL if the subtype is unclear, otherwise
**		the definition of the applicable subtype.
*/

static sm_conf_definition_T const *
union_value_choice(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void const			*val)
{
	unsigned long			ul;
	sm_conf_definition_T const	*sub;

	if ((sub = union_type_definition(smc, def)) == NULL)
		return NULL;

	if (sm_conf_u32_load(
		(void *)((char *)val + sub->scd_offset),
		sub->scd_size, &ul) != 0)
		return union_named_choice(def, def->scd_default, NULL);

	/* Look up the type in our table. */
	for (sub = def->scd_contents; sub->scd_name != NULL; sub++)
		if (  sub->scd_type   == sm_conf_type_union_choice
		   && sub->scd_offset == ul)
			return sub;

	return union_named_choice(def, def->scd_default, NULL);
}


/*
**  UNION_CHOICE_MATCH -- does a given subdefinition match the node?
**
**	There are multiple ways of selecting which subdefinition matches
**	a node.  They can be distinguished by some explicit tag that
**	is present in all subtypes and has different values:
**
**		vehicle { type = "car"; ... }
**
**	the tag could also be a section name:
**
**		vehicle "car" { ... }
**
**	or they can be distinguished implicitly by testing for the
**	presence of certain parameters that show up in only one of
**	the subtypes:
**
**		vehicle { licenseplate = "HAKRGRL"; mpg = 17; }
**
**	The sm_conf_union_choice record in a choice type's details
**	covers these alternatives as follows:
**
**	scd_name	scd_default	matches ...
**	------------------------------------------------------------
**	""		NULL		always (designates the default type)
**
**	""		""		matches an anonymous section
**
**	""		"foo"		matches section "foo" (case 2 above).
**
**	"bar"		NULL		matches anything with a
**					"bar" option present (case 3 above).
**
**	"bar"		"foo"		matches if there's a "bar" option
**					with value "foo" (case 1 above).
**
**	Parameters:
**		smc -- overall context
**		ch -- choice to match against
**		section -- node we're trying to match.
**
**	Returns:
**		true or false -- does it match?
*/

static bool
union_choice_match(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*ch,
	sm_conf_node_T const		*section)
{
	sm_conf_node_T const		*opt;
	size_t				subdef_name_n;

	if (ch->scd_type != sm_conf_type_union_choice)
		return false;

	if (ch->scd_name[0] == '\0')
	{
		char const	*in;
		size_t		in_n;

		return ch->scd_default == NULL
		 || (  sm_conf_section_name(smc, section, &in, &in_n) == 0
		    && ( in == NULL
		       ? ch->scd_default[0] == '\0'
		       : sm_memncaseeq(ch->scd_default, strlen(ch->scd_default),
			in, in_n)));
	}

	subdef_name_n = strlen(ch->scd_name);

	/* options with this name exist? */
	opt = NULL;
	while ((opt = sm_conf_section_next_option(smc, section,
		ch->scd_name, subdef_name_n, opt)) != NULL)
	{
		char const	*in;
		size_t		in_n;

		/* value doesn't matter? */
		if (ch->scd_default == NULL)
			return true;

		switch (sm_conf_node_type(smc, opt))
		{
		   case SM_CONF_NODE_VALUE:
			if (   sm_conf_value(smc, opt, &in, &in_n) == 0
			    && sm_memncaseeq(in, in_n, ch->scd_default,
				strlen(ch->scd_default)))
				return true;
			break;

		  case SM_CONF_NODE_SECTION:
			if (  sm_conf_section_keyword(smc, opt, &in, &in_n) == 0
			    && sm_memncaseeq(in, in_n, ch->scd_default,
					strlen(ch->scd_default)))
				return true;
			break;
		  default:
			break;
		}
	}
	return false;
}

/*
**  SM_CONF_TYPE_UNION_NODE_TO_VALUE -- (Method) convert a union member
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the union object
**		section -- containing section
**		data -- data to write to (begin of struct), or NULL
**
**	Returns:
**		0 on success, a nonzero error code on error
*/

static int
sm_conf_type_union_node_to_value(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	sm_conf_node_T			*section,
	void				*data)
{
	unsigned int			flags;
	sm_conf_definition_T const	*ch, *type;
	int				err;
	char const			*name;
	size_t				name_n;

	if (smc == NULL)
		return SM_CONF_ERR_INVALID;

	SM_IS_CONF_DEF(def);
	flags = def->scd_flags;
	err = 0;

	/* Which of the subtypes applies? */
	for (ch = def->scd_contents; ch->scd_name != NULL; ch++)
	{
		if (union_choice_match(smc, ch, section))
			break;
	}

	/*
	**  If we didn't get a choice this time around, or if the choice
	**  is the default, make our decision based on the default node.
	*/

	if (  (  ch->scd_name == NULL
	      || (ch->scd_default == NULL && ch->scd_name[0] == '\0'))
	   && (flags & SM_CONF_FLAG_SECTION_DEFAULT_FROM_ANONYMOUS)
	   && section != NULL
	   && sm_conf_section_name(smc, section, &name, &name_n) == 0
	   && name != NULL
	   && sm_conf_section_keyword(smc, section, &name, &name_n) == 0)
	{
		sm_conf_node_T const	*default_node;

		default_node = sm_conf_section_anonymous_default(smc, name,
							name_n, section);

		if (default_node != NULL)
		{
			for (ch = def->scd_contents; ch->scd_name != NULL; ch++)
			{
				if (union_choice_match(smc, ch, default_node))
					break;
			}
		}
	}

	if (ch->scd_name == NULL)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];

		sm_conf_error_add(smc,
			"%s: can't determine type of multiple-choice object",
			sm_conf_node_location(smc, section, loc, sizeof loc));
		return SM_CONF_ERR_TYPE;
	}

	err = sm_conf_type_section_node_to_value_subdef(smc,
		ch->scd_contents, flags | ch->scd_flags, section, data);
	if (err != 0)
		return err;

	/* Store chosen alternative in the type field. */
	if ((type = union_type_definition(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;
	if (data == NULL)
		return 0;

	SM_LC_ISA(def, data);
	return sm_conf_u32_store((char *)data + type->scd_offset,
		type->scd_size, ch->scd_offset);
}

/*
**  SM_CONF_TYPE_UNION_VALUE_CHECK -- (Method) check validity of
**	union application data
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the union object
**		data -- data to write to, or NULL
**
**	Returns:
**		0 on success, a nonzero error code on error
*/

static int
sm_conf_type_union_value_check(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void const			*data)
{
	sm_conf_definition_T const	*ch, *d;
	int				err;

	SM_IS_CONF_DEF(def);
	SM_REQUIRE(smc != NULL);

	if (data == NULL)
		return 0;

	ch = union_value_choice(smc, def, data);
	if (ch == NULL)
	{
		char	loc[SM_CONF_ERROR_BUFFER_SIZE];

		/*
		**  Allow a value of 0 for the type choice if a
		**  multiple choice object is simply absent.
		*/

		if (  (def->scd_flags & SM_CONF_FLAG_REQUIRED)
		   || !union_is_empty(def, data))
		{
			sm_conf_error_add(smc,
				"%s: unclear type of multiple-choice object",
				sm_conf_node_location(smc, NULL,
					loc, sizeof loc));
			return SM_CONF_ERR_TYPE;
		}
		return 0;
	}

	/* recursively check the chosen subtype's contents. */
	for (d = ch->scd_contents; d != NULL && d->scd_name != NULL; d++)
	{
		err = (* d->scd_type->sctp_value_check)(smc, d,
			(char *)data + d->scd_offset);
		if (err != 0)
			return err;
	}

	/* if the subtype has a check function, use it. */
	if (ch->scd_check != NULL)
	{
		err = (* ch->scd_check)(smc, ch->scd_check_data, ch, data);
		if (err != 0)
			return err;
	}

	/* if we have our own check function, use it. */
	if (def->scd_check != NULL)
		return (* def->scd_check)(smc, def->scd_check_data, def, data);
	return 0;
}


/*
**  SM_CONF_TYPE_UNION_VALUE_NULL -- (Method) zero out union application data
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the union object
**		data -- data to write to (field of struct), or NULL
**
**	Returns:
**		0 on success, a nonzero error code on error
*/

static int
sm_conf_type_union_value_null(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void				*data)
{
	sm_conf_definition_T const	*ch, *d;
	int				err;

	SM_REQUIRE(smc != NULL);
	SM_IS_CONF_DEF(def);
	if (data == NULL)
		return 0;

	if (def->scd_size > 0)
		sm_memset(data, 0, def->scd_size);

	if (def->scd_default != NULL)
	{
		unsigned long	ch_val;

		ch = union_named_choice(def, def->scd_default, &ch_val);
		if (ch != NULL)
		{
			if (def->scd_size != 0)
			{
				err = sm_conf_u32_store(
					data, def->scd_size, ch_val);
				if (err != 0)
					return err;
			}

			for (d = ch->scd_contents; d->scd_name != NULL; d++)
			{
				err = (* d->scd_type->sctp_value_null)(
					smc, d, (char *)data + d->scd_offset);
				if (err != 0)
					return err;
			}
		}
		else
		{
			char loc[SM_CONF_ERROR_BUFFER_SIZE];

			sm_conf_error_add(smc,
				"%s:%s%s default selection \"%s\" doesn't "
				"exist in union definition",
				sm_conf_node_location(smc, NULL,
					loc, sizeof loc),
				def->scd_name,
				def->scd_name[0] == '\0'? "" : ": ",
				def->scd_default);
			return SM_CONF_ERR_TYPE;
		}
	}
	return 0;
}

sm_conf_type_T const
sm_conf_type_union_data =
{
	sm_conf_type_union_node_to_value,
	sm_conf_type_union_value_check,
	sm_conf_type_union_value_null
};


syntax highlighted by Code2HTML, v. 0.9.1