/* * 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 #include #include #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 and 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 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.) ** ** 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 };