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


#if SM_LIBCONF_ALONE
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "sm-conf.h"
#include "sm-conf-util.h"
#include "sm-util.h"
#else /* SM_LIBCONF_ALONE */
#include "sm/string.h"
#include "sm/ctype.h"
#include "sm/error.h"
#include "sm/heap.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"

/*
**  Dynamic arrays:
**
**	an element size (fixed)
**		either the scd_size of the element type,
**		or explicitly as a sm_conf_type_array_elem_size[]
**			in scd_contents.
**
**	an element type
**		in scd_contents - anything that isn't an element
**		size or number.
**
**	a number of elements
**		as sm_conf_type_array_n[] in scd_contents.
**		Its <scd_size> and <scd_offset> say how it's stored.
**		The offset is relative to the base location
**		of the array.
**
**	an optional maximum number of elements
**		the scd_size of the array type (0 = unlimited).
**
**	an element vector,
**		either flat (and SM_CONF_FLAG_FLAT is set),
**		or it's a pointer stored at the array location.
**
*/

sm_conf_type_T const
sm_conf_type_array_n_data,
sm_conf_type_array_elem_size_data;

typedef struct
{
	void	*scda_data;
	size_t	 scda_size;
	size_t	 scda_n;
	size_t	 scda_m;

} sm_conf_dynarray_T;


/*
**  ARRAY_DYNARRAY_FREE -- (Utilty) callback to free node-cache data
**	for a "dynamic" (i.e. variably sized) array.
**
**	This is passed to the per-node data cache as a callback
**	to be called when the node cache is free'ed.
**
**	Parameters:
**		node -- containing array node, unused
**		data -- the cache data backing the dynamic array
**		def  -- definition for this dynamic array, unused.
**
**	Returns:
**		None.
*/

static void
array_dynarray_free(
	sm_conf_node_T			*node,
	void				*data,
	sm_conf_definition_T const	*def)
{
	sm_conf_dynarray_T *dyn;

	SM_REQUIRE(def != NULL);
	SM_REQUIRE(data != NULL);
	SM_REQUIRE(node != NULL);

	dyn = (sm_conf_dynarray_T *) data;
	if (dyn->scda_data != NULL)
	{
		sm_free(dyn->scda_data);

		/* These should be unnecessary. */
		dyn->scda_data = NULL;
		dyn->scda_size = 0;
		dyn->scda_n = 0;
		dyn->scda_m = 0;
	}
}


/*
**  ARRAY_DYNARRAY_GET -- (Utilty) given a node, get its dynamic array
**	from the cache; create it if necessary.
**
**	Parameters:
**		node -- node containing the array
**		def -- node's definition
**
**	Returns:
**		The, freshly allocated if necessary, dynarray
**		for this node.
*/

static sm_conf_dynarray_T *
array_dynarray_get(sm_conf_node_T *node, sm_conf_definition_T const *def)
{
	sm_conf_dynarray_T		*dyn;
	int				result;

	dyn = sm_conf_node_cache_get(node, sizeof(*dyn), def,
		array_dynarray_free, &result);
	if (dyn == NULL)
		return dyn;
	if (result == 0)
	{
		dyn->scda_data = 0;
		dyn->scda_size = 0;
		dyn->scda_n = 0;
		dyn->scda_m = 0;
	}

	return dyn;
}


/*
**  ROUND_ALLOC_SIZE -- (Utility)  Round up an allocation size to the
**	next largest of 0, 8, 16, 32, 64*N.
**
**	Parameters:
**		n -- the number of bytes needed
**
**	Returns:
**		the number of bytes to allocate to avoid
**		too many reallocations for small stuff.
*/

static size_t
round_alloc_size(size_t n)
{
	if (n == 0)
		return n;
	if (n <= 8)
		return 8;
	if (n <= 16)
		return 16;
	if (n <= 32)
		return 32;
	return ((n + 63) / 64) * 64;
}


/*
**  ARRAY_ELEMENT_TYPE -- (Utility) given an array definition,
**	find the element type
**
**	The element type of an array is the first element in its scd_contents
**	array that isn't an sm_conf_type_array_elem_size or an
**	sm_conf_type_array_n.
**
**	Parameters:
**		smc -- overall context
**		def -- definition of the array
**
**	Returns:
**		NULL if the array didn't have an element definition;
**		otherwise, a pointer to the first candidate definition.
*/

static sm_conf_definition_T const *
array_element_type(sm_conf_T *smc, sm_conf_definition_T const *def)
{
	sm_conf_definition_T const	*sub;
	char				loc[SM_CONF_ERROR_BUFFER_SIZE];

	SM_IS_CONF_DEF(def);

	SM_REQUIRE(def != NULL);
	SM_REQUIRE(def->scd_type == sm_conf_type_array);

	if (def->scd_contents != NULL)
		for (sub = def->scd_contents; sub->scd_name != NULL; sub++)
			if (  sub->scd_type != sm_conf_type_array_elem_size
			   && sub->scd_type != sm_conf_type_array_n)
				return sub;

	sm_conf_error_add(smc,
		"%s: %s%scannot find element type in array definition",
		sm_conf_node_location(smc, NULL, loc, sizeof loc),
		def->scd_name,
		def->scd_name[0] == '\0' ? "" :  ": ");
	return NULL;
}


/*
**  ARRAY_ELSIZE -- (Utility) given an array definition, return the element size
**
**	The array element size is the size of the array element type.
**	It must not be 0.
**
**	Parameters:
**		smc -- overall context
**		def -- definition of the array
**		els_out -- store the element size here.
**
**	Returns:
**		SM_CONF_ERR_TYPE if there was an error, otherwise 0.
*/

static int
array_element_size(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	size_t				*els_out)
{
	sm_conf_definition_T const	*els;

	SM_IS_CONF_DEF(def);
	SM_REQUIRE(def != NULL);
	SM_REQUIRE(def->scd_type == sm_conf_type_array);

	if ((els = array_element_type(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	if (els->scd_size == 0)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];
		sm_conf_error_add(smc,
			"%s:%s%s array with zero element size",
			sm_conf_node_location(smc, NULL, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" : ": ");
		return SM_CONF_ERR_TYPE;
	}

	*els_out = els->scd_size;
	return 0;
}


/*
**  ARRAY_NELEMS_DEF -- (Utility) given an array definition, return the
**	``nelems'' definition (one of the subdefinitions of the array).
**
**	The nelems definition describes where and how the "fill level"
**	of the arary is stored in the application data.
**
**	Parameters:
**		smc -- overall context
**		def -- definition of the array
**
**	Returns:
**		the nelems subdefinition, or NULL if none was found.
**
**	Side Effects:
**		If the array doesn't have an element count definition,
**		or if the size of the element count is 0, the call logs
**		an error into the parser state.
*/

static sm_conf_definition_T const *
array_nelems_def(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def)
{
	sm_conf_definition_T const	*n;

	n = sm_conf_subdef(def->scd_contents, sm_conf_type_array_n, NULL, 0);
	if (n == NULL || n->scd_size == 0)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];
		sm_conf_error_add(smc,
			"%s:%s%s array without element count",
			sm_conf_node_location(smc, NULL, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" : ": ");
		return NULL;
	}
	return n;
}


/*
**  ARRAY_NELEMS -- (Utility) extract the "current number of elements"
**	counter from the data backing an array.
**
**	If <data> is NULL, the assigned element count is always 0.
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the array
**		data -- NULL or the user data backing the array
**		nelems_out -- value to which the count is assigned.
**
**	Returns:
**		0 on success, an error number on error.
*/

static int
array_nelems(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void const			*data,
	size_t				*nelems_out)
{
	sm_conf_definition_T const	*n;
	unsigned long			ul;
	int				err;

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

	if ((n = array_nelems_def(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	if (data == NULL)
	{
		/* There is no data to save our count in; pretend we're at 0. */
		*nelems_out = 0;
		return 0;
	}

	err = sm_conf_u32_load((char *)data + n->scd_offset,
		n->scd_size, &ul);
	if (err)
		return err;

	*nelems_out = ul;
	return 0;
}


/*
**  ARRAY_BASE -- (Utility) return the address where the
**	individual array elements start (in contrast to the
**	number that coutns them.)
**
**	<data> must be non-NULL.
**
**	Parameters:
**		def -- definition of the array
**		sub -- individual element definition
**		data -- user data backing the array.
**
**	Returns:
**		a pointer to the first element of the
**		flat array.
*/

static void *
array_base(
	sm_conf_definition_T const	*def,
	sm_conf_definition_T const	*sub,
	void const			*data)
{
	if (sub == NULL)
		return NULL;

	SM_REQUIRE(data != NULL);

	SM_LC_ISA(def, data);
	data = (char *)data + sub->scd_offset;
	return (def->scd_flags & SM_CONF_FLAG_FLAT)
		? (void *)data
		: *(void * const *)data;
}


/*
**  ARRAY_NODE_TO_VALUE_SINGLE -- (Utility) Convert a single node
**	to one array element.
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the array
**		node -- the node we're trying to convert
**		data -- user data backing the array, or NULL
**
**	Returns:
**		0 on success, a nonzero error on error.
*/

static int
array_node_to_value_single(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	sm_conf_node_T			*node,
	void				*data)
{
	sm_conf_dynarray_T		*dyn;
	sm_conf_definition_T const	*n_def, *sub;
	unsigned long			n;
	int				err;
	size_t				elsize;
	char				loc[SM_CONF_ERROR_BUFFER_SIZE];
	void				*base;

	if (smc == NULL)
		return SM_CONF_ERR_INVALID;

	SM_IS_CONF_DEF(def);
	err = 0;
	dyn = NULL;

	if ((n_def = array_nelems_def(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	if (data == NULL)
		n = 0;
	else if ((err = sm_conf_u32_load((char *)data + n_def->scd_offset,
					n_def->scd_size, &n)) != 0)
	{
		sm_conf_error_add(smc,
			"%s: %s%sarray element count with invalid size (%lu)",
			sm_conf_node_location(smc, node, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" : ": ",
			n);
		return err;
	}

	/* Is there a maximum?  If yes, are we there? */
	if (  def->scd_size != 0
	   && n >= def->scd_size)
	{
		sm_conf_error_add(smc,
			"%s: %s%stoo many elements in array (maximum: %lu)",
			sm_conf_node_location(smc, node, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" : ": ",
			(unsigned long)def->scd_size);
		return SM_CONF_ERR_TOO_MANY;
	}

	/*
	**  Did we already cache this?  If yes, just increment
	**  the counter.
	*/

	if ((err = array_element_size(smc, def, &elsize)) != 0)
		return err;
	if ((sub = array_element_type(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	/* where are we now? */
	base = NULL;
	if (data != NULL)
	{
		SM_LC_ISA(def, data);
		base = (char *)data + sub->scd_offset;
		if (def->scd_flags & SM_CONF_FLAG_FLAT)
			base = (char *)base + (elsize * n);
		else
		{
			size_t	old_size, new_size;
			dyn = array_dynarray_get(
				sm_conf_node_parent(smc, node), def);
			if (dyn == NULL)
				return SM_CONF_ERR_NO_MEMORY;

			/* Just store the array and increment the counter. */
			if (n + 1 <= dyn->scda_n)
			{
				*(void **)base = dyn->scda_data;
				return sm_conf_u32_store(
					(char *)data + n_def->scd_offset,
					n_def->scd_size, n + 1);
			}

			old_size = dyn->scda_n;
			new_size = round_alloc_size(dyn->scda_n + 1);

			if (old_size != new_size)
			{
				void *tmp;

				tmp = sm_zalloc(new_size * elsize);
				if (tmp == NULL)
					return ENOMEM;

				if (old_size > 0)
					memcpy(tmp, dyn->scda_data,
						old_size * elsize);
				if (dyn->scda_data != NULL)
					sm_free(dyn->scda_data);
				*(void **)base = dyn->scda_data = tmp;
			}
			base = (char *)dyn->scda_data + elsize * n;
		}
	}

	/* store the element. */
	if ((err = sm_conf_scan_node_to_value(smc,
		def->scd_name, strlen(def->scd_name),
		sub, node, base)) != 0)
	{
		return err;
	}

	if (data == NULL)
		return 0;

	/* increment the element count */
	n++;
	if (dyn != NULL && dyn->scda_n < n)
		dyn->scda_n = n;

	/* store the element count in the value */
	return sm_conf_u32_store(
		(char *)data + n_def->scd_offset,
		n_def->scd_size, n);
}

/*
**  SM_CONF_TYPE_ARRAY_NODE_TO_VALUE -- (Method) Convert a node
**	and its subnodes to an array
**
**	Depending on the node type, this may be adding all elements
**	of the a list:
**
**	   array = { 1, 2, 3, }
**
**	or one of repeated assignments setting sequential elements.
**
**	   array = 1;
**	   array = 2;
**	   array = 3;
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the array
**		node -- the node we're trying to convert
**		data -- user data backing the array, or NULL
**
**	Returns:
**		0 on success, a nonzero error on error.
*/

static int
sm_conf_type_array_node_to_value(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	sm_conf_node_T			*node,
	void				*data)
{
	sm_conf_node_T			*child;

	/*
	**   VALUE, SECTION -- add a single element to the array.
	**   LIST	    -- add all elements of the list.
	*/

	if (smc == NULL)
		return SM_CONF_ERR_INVALID;

	if (sm_conf_node_type(smc, node) != SM_CONF_NODE_LIST)
		return array_node_to_value_single(smc, def, node, data);

	child = NULL;
	while ((child = sm_conf_list_next(smc, node, child)) != NULL)
	{
		int err;
		err = array_node_to_value_single(smc, def, child, data);
		if (err != 0)
			return err;
	}
	return 0;
}

/*
**  SM_CONF_TYPE_ARRAY_VALUE_CHECK -- (Method) Check array contents.
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the array
**		data -- user data backing the array.
**
**	Returns:
**		0 on success, a nonzero error on error.
*/

static int
sm_conf_type_array_value_check(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void const			*data)
{
	sm_conf_definition_T const	*sub;
	size_t				i, elsize, nelems;
	void				*base;
	int				err;

	SM_IS_CONF_DEF(def);

	if (  (err = array_nelems(smc, def, data, &nelems)) != 0
	   || (err = array_element_size(smc, def, &elsize)) != 0)
		return err;

	if ((sub = array_element_type(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	if (nelems > 0)
	{
		/*
		**  If data were NULL, array_nelems above would have set
		**  nelems to 0.
		*/

		SM_REQUIRE(data != NULL);
		if ((base = array_base(def, sub, data)) == NULL)
		{
			char loc[SM_CONF_ERROR_BUFFER_SIZE];
			sm_conf_error_add(smc,
				"%s:%s%s NULL non-empty array?",
				sm_conf_node_location(smc, NULL,
					loc, sizeof loc),
				def->scd_name,
				def->scd_name[0] == '\0' ? "" :  ": ");
			return SM_CONF_ERR_TYPE;
		}

		/* check the individual elements. */
		for (i = 0; i < nelems; i++)
			if ((err = (* sub->scd_type->sctp_value_check)(
				smc, sub, (char *)base + (i * elsize))) != 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_ARRAY_VALUE_NULL -- (Method) Zero out array contents
**
**	If the user data passed in is NULL, the call checks the
**	definition data structures, but doesn't actually zero out
**	anything.
**
**	Parameters:
**		smc -- configuration parser state
**		def -- definition of the array
**		data -- user data backing the array, or NULL
**
**	Returns:
**		0 on success, a nonzero error on error.
*/

static int
sm_conf_type_array_value_null(
	sm_conf_T			*smc,
	sm_conf_definition_T const	*def,
	void				*data)
{
	sm_conf_definition_T const	*nel;
	sm_conf_definition_T const	*sub;
	size_t				i, elsize, nelems;
	void				*base;
	int				err;

	SM_IS_CONF_DEF(def);

	nel = sm_conf_subdef(def->scd_contents, sm_conf_type_array_n, NULL, 0);
	if (nel == NULL)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];
		sm_conf_error_add(smc,
			"%s: %s%sno size for array",
			sm_conf_node_location(smc, NULL, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" :  ": ");
		return SM_CONF_ERR_TYPE;
	}

	if (nel->scd_size == 0)
	{
		char loc[SM_CONF_ERROR_BUFFER_SIZE];
		sm_conf_error_add(smc,
			"%s:%s%s no place to store # of elements in array",
			sm_conf_node_location(smc, NULL, loc, sizeof loc),
			def->scd_name,
			def->scd_name[0] == '\0' ? "" :  ": ");
		return SM_CONF_ERR_TYPE;
	}

	if ((sub = array_element_type(smc, def)) == NULL)
		return SM_CONF_ERR_TYPE;

	if (data == NULL)
		return 0;

	SM_LC_ISA(def, data);
	sm_conf_u32_store((char *)data + nel->scd_offset, nel->scd_size, 0);

	if (!(def->scd_flags & SM_CONF_FLAG_FLAT))
	{
		*(void **)((char *)data + sub->scd_offset) = NULL;
		return 0;
	}

	if (  (err = array_nelems(smc, def, data, &nelems)) != 0
	   || (err = array_element_size(smc, def, &elsize)) != 0)
		return err;

	base = (char *)data + sub->scd_offset;
	for (i = 0; i < nelems; i++)
		if ((err = (* sub->scd_type->sctp_value_null)(
			smc, sub, (char *)base + (i * elsize))) != 0)
			return err;
	return 0;
}

sm_conf_type_T const
sm_conf_type_array_data =
{
	sm_conf_type_array_node_to_value,
	sm_conf_type_array_value_check,
	sm_conf_type_array_value_null
};


syntax highlighted by Code2HTML, v. 0.9.1