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