/*
* 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