/* * Copyright (c) 2004 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-node.c,v 1.14 2005/06/16 20:35:10 ca Exp $") #if SM_LIBCONF_ALONE #include #include #include "sm-util.h" #include "sm-conf-util.h" #else /* SM_LIBCONF_ALONE */ #include "sm/error.h" #include "sm/memops.h" #include "sm/string.h" #include "sm/heap.h" #endif /* SM_LIBCONF_ALONE */ #include "sm-conf-state.h" #include "sm-conf-node.h" #include "sm-conf-token.h" #include "sm-conf-type.h" /* SM-CONF-NODE.C -- node access routines. */ #define SM_CONF_NODE_MAGIC SM_MAGIC('C', 'N', 'O', 'D') #define SM_IS_CONF_NODE(sm_conf_node) \ SM_REQUIRE((sm_conf_node) != NULL && \ (sm_conf_node)->sm_magic == SM_CONF_NODE_MAGIC) typedef struct sm_conf_node_cache_S { struct sm_conf_node_cache_S *scnc_next; void *scnc_data; sm_conf_definition_T const *scnc_definition; void (*scnc_free)( union sm_conf_node_U *, void *, sm_conf_definition_T const *); } sm_conf_node_cache_T; /* ** (ca) use queue.h macros for lists? */ union sm_conf_node_U { sm_magic_T sm_magic; #define scn_type scn_generic.scng_type struct { sm_magic_T sm_magic; /* SCNG_TYPE -- distinguish between node, list, or section. */ enum sm_conf_node_type_E scng_type; /* ** SCNG_NEXT ** SCNG_PARENT -- list or section elements have these. ** (next points to the next element in the list or ** section; parent to the container node.) ** ** Only the root node has a NULL parent. */ union sm_conf_node_U *scng_next; union sm_conf_node_U *scng_parent; /* ** SCNG_SLOT ** SCNG_SLOT_N -- section elements can have this ** if they're named (i.e., if this is a named ** property in a section rather than a subsection.) ** */ char const *scng_slot; size_t scng_slot_n; /* SCNG_TOKEN -- the first input token; has line number. */ sm_conf_token_T scng_token; /* SCNG_CACHE -- temporary memory for value conversion. */ sm_conf_node_cache_T *scng_cache; } scn_generic; struct { sm_magic_T sm_magic; enum sm_conf_node_type_E scnv_type; union sm_conf_node_U *scnv_next; union sm_conf_node_U *scnv_parent; char const *scnv_slot; size_t scnv_slot_n; sm_conf_token_T scnv_token; sm_conf_node_cache_T *scnv_cache; char const *scnv_text; size_t scnv_text_n; } scn_value; struct { sm_magic_T sm_magic; enum sm_conf_node_type_E scnl_type; union sm_conf_node_U *scnl_next; union sm_conf_node_U *scnl_parent; char const *scnl_slot; size_t scnl_slot_n; sm_conf_token_T scnl_token; sm_conf_node_cache_T *scnl_cache; /* ** SCNL_HEAD -- first element of the list. ** SCNL_TAIL -- address of last element of the list. ** SCNL_N -- # of subelements. */ union sm_conf_node_U *scnl_head; union sm_conf_node_U **scnl_tail; size_t scnl_n; } scn_list; struct { sm_magic_T sm_magic; enum sm_conf_node_type_E scns_type; union sm_conf_node_U *scns_next; union sm_conf_node_U *scns_parent; char const *scns_slot; size_t scns_slot_n; sm_conf_token_T scns_token; sm_conf_node_cache_T *scns_cache; /* ** SCNS_KEYWORD ** SCNS_KEYWORD_N -- (unquoted) type of a section. ** In ** foo "bar" { ** ... ** } ** is the section keyword. */ char const *scns_keyword; size_t scns_keyword_n; /* ** SCNS_NAME ** SCNS_NAME_N -- (quoted) name of a section. ** In ** foo "bar" { ** ... ** } ** is the section name. ** Syntactically, section names are optional. */ char const *scns_name; size_t scns_name_n; /* ** SCNS_HEAD -- first element of the section. ** SCNS_TAIL -- address of last element of the section. ** SCNS_N -- # of subelements. */ sm_conf_node_T *scns_head; sm_conf_node_T **scns_tail; size_t scns_n; } scn_section; }; /* ** SM_CONF_NODE_CACHE_GET -- allocate a per-node cache structure. ** ** Called by the to-value conversion of the definition. ** ** Parameters: ** node -- node for which we're allocating a cache ** size -- # of bytes to allocate ** def -- definition that causes the cache to be allocated, ** or NULL ** free_cache -- callback to call when this is free'ed, ** or NULL ** result -- assign result code to this. ** ** Returns: ** pointer to the allocated or existing data, ** or NULL if an allocation failed. */ void * sm_conf_node_cache_get( sm_conf_node_T *node, size_t size, sm_conf_definition_T const *def, void (*free_cache)( sm_conf_node_T *, void *, sm_conf_definition_T const *), int *result) { sm_conf_node_cache_T *ca; if (node == NULL) { *result = EINVAL; return NULL; } /* ** If we find an existing cache with the same definition, ** just reuse that. */ for (ca = node->scn_list.scnl_cache; ca != NULL; ca = ca->scnc_next) if (ca->scnc_definition == def) { *result = EEXIST; return ca->scnc_data; } if ((ca = sm_zalloc(sizeof(*ca))) == NULL) { *result = ENOMEM; return NULL; } ca->scnc_data = sm_zalloc(size == 0 ? 1 : size); if (ca->scnc_data == NULL) { *result = ENOMEM; sm_free(ca); return NULL; } *result = 0; ca->scnc_next = node->scn_list.scnl_cache; node->scn_list.scnl_cache = ca; ca->scnc_definition = def; ca->scnc_free = free_cache; return ca->scnc_data; } /* ** SM_CONF_NODE_CACHE_FREE -- free per-node cache structures. ** ** Parameters: ** node -- node whose cache we're freeing ** ** Returns: ** none */ static void sm_conf_node_cache_free(sm_conf_node_T *node) { sm_conf_node_cache_T *ca; while ((ca = node->scn_list.scnl_cache) != NULL) { node->scn_list.scnl_cache = ca->scnc_next; if (ca->scnc_free != NULL) (* ca->scnc_free)( node, ca->scnc_data, ca->scnc_definition); sm_free(ca->scnc_data); sm_free(ca); } } /* ** SM_CONF_NODE_NEW -- allocate a new node ** ** Parameters: ** smc -- context handle ** token -- token that started that node. ** ** Returns: ** a node pointer on success, NULL on error. ** ** Last code review: 2004-05-17 16:14:28; see comments ** Last code change: */ static sm_conf_node_T * sm_conf_node_new(sm_conf_T *smc, sm_conf_token_T *token) { sm_conf_node_T *n; SM_IS_CONF(smc); if (token != NULL) SM_IS_CONF_TOKEN(token); if ((n = sm_zalloc(sizeof(*n))) != NULL) { n->sm_magic = SM_CONF_NODE_MAGIC; n->scn_generic.scng_slot = NULL; n->scn_generic.scng_slot_n = 0; n->scn_generic.scng_next = NULL; if (token != NULL) n->scn_generic.scng_token = *token; else n->scn_generic.scng_token.sct_type = SM_CONF_TOKEN_NONE; } return n; } /* ** SM_CONF_VALUE_NEW -- allocate a new single-element value ** ** Parameters: ** smc -- context handle ** text -- the (unquoted) spelling of the value data. ** text_n -- number of bytes pointed to by ** token -- origin of the data. ** ** Returns: ** a node pointer on success, NULL on error. ** ** Last code review: 2004-05-17 16:14:52; see comments! ** Last code change: 2004-05-20 21:02:49 jutta */ sm_conf_node_T * sm_conf_value_new( sm_conf_T *smc, char const *text, size_t text_n, sm_conf_token_T *token) { sm_conf_node_T *n; SM_IS_CONF(smc); if (token != NULL) SM_IS_CONF_TOKEN(token); if ((n = sm_conf_node_new(smc, token)) != NULL) { n->scn_value.scnv_type = SM_CONF_NODE_VALUE; if (text != NULL) { n->scn_value.scnv_text = text; n->scn_value.scnv_text_n = text_n; } else if (token != NULL) { n->scn_value.scnv_text = token->sct_text; n->scn_value.scnv_text_n = token->sct_text_n; } else { n->scn_value.scnv_text = NULL; n->scn_value.scnv_text_n = 0; } } return n; } /* ** SM_CONF_VALUE -- get the text of a value node. ** ** Parameters: ** smc -- context handle ** node -- the node ** text_out -- out: the (unquoted) spelling of the value data. ** text_n_out -- out: number of bytes pointed to by ** ** Returns: ** 0 on success, SM_CONF_ERR_INVALID on error. ** ** Last code review: 2004-05-17 16:17:26 ** Last code change: 2004-05-20 21:04:48 */ int sm_conf_value( sm_conf_T *smc, sm_conf_node_T const *node, char const **text_out, size_t *text_n_out) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); if ( node == NULL || node->scn_generic.scng_type != SM_CONF_NODE_VALUE) return SM_CONF_ERR_INVALID; if (text_out != NULL) *text_out = node->scn_value.scnv_text; if (text_n_out != NULL) *text_n_out = node->scn_value.scnv_text_n; return 0; } /* ** SM_CONF_LIST_NEW -- allocate a new list object ** ** Parameters: ** smc -- context handle ** ** Returns: ** a node pointer on success, NULL on error. ** ** Last code review: 2004-05-17 16:18:11 ** Last code change: 2004-05-20 21:04:38 */ sm_conf_node_T * sm_conf_list_new(sm_conf_T *smc, sm_conf_token_T *token) { sm_conf_node_T *n; SM_IS_CONF(smc); if (token != NULL) SM_IS_CONF_TOKEN(token); if ((n = sm_conf_node_new(smc, token)) != NULL) { n->scn_list.scnl_type = SM_CONF_NODE_LIST; n->scn_list.scnl_head = NULL; n->scn_list.scnl_tail = &n->scn_list.scnl_head; n->scn_list.scnl_n = 0; } return n; } /* ** SM_CONF_LIST_APPEND -- append to a list ** ** Parameters: ** smc -- context handle ** list -- list to append to, must be non-NULL ** node -- node or node chain to append. ** ** Returns: ** none ** ** Last code review: 2004-05-17 16:23:50, see comments ** Last code change: 2004-05-20 21:05:53 */ void sm_conf_list_append(sm_conf_T *smc, sm_conf_node_T *list, sm_conf_node_T *node) { SM_IS_CONF(smc); if (node == NULL) return; SM_REQUIRE(list != NULL); SM_IS_CONF_NODE(node); SM_IS_CONF_NODE(list); *list->scn_list.scnl_tail = node; while (node->scn_generic.scng_next != NULL) node = node->scn_generic.scng_next; list->scn_list.scnl_tail = &node->scn_generic.scng_next; list->scn_list.scnl_n++; node->scn_generic.scng_parent = list; } /* ** SM_CONF_LIST_NEXT -- get next element from a list ** ** Parameters: ** smc -- context handle ** list -- list to get the next element of ** prev -- NULL or predecessor ** ** Returns: ** NULL if the list is NULL or not a list. ** if is NULL, the list's first element ** if is non-NULL, 's successor element. ** ** Last code review: 2004-05-17 16:24:46, see comments ** Last code change: 2004-05-20 21:11:17 jutta */ sm_conf_node_T * sm_conf_list_next( sm_conf_T *smc, sm_conf_node_T const *list, sm_conf_node_T const *prev) { SM_IS_CONF(smc); if (list != NULL) SM_IS_CONF_NODE(list); if (prev != NULL) SM_IS_CONF_NODE(prev); if (list == NULL || list->scn_type != SM_CONF_NODE_LIST) return NULL; return prev == NULL ? list->scn_list.scnl_head : prev->scn_generic.scng_next; } /* ** SM_CONF_LIST_N -- get # of entries of a list. ** ** Parameters: ** smc -- context handle ** list -- NULL or a node pointer ** ** Returns: ** 0 if this isn't a list, otherwise the number ** of entries. ** ** Last code review: 2004-05-17 16:28:13, see comments ** Last code change: 2004-05-20 21:12:36 jutta */ size_t sm_conf_list_n(sm_conf_T *smc, sm_conf_node_T const *list) { SM_IS_CONF(smc); if (list != NULL) SM_IS_CONF_NODE(list); if (list == NULL || list->scn_type != SM_CONF_NODE_LIST) return 0; return list->scn_list.scnl_n; } /* ** SM_CONF_LIST_ARGV -- get a vector of a list's textual subentries ** ** Parameters: ** smc -- context handle ** list -- NULL or a node ** argv_out -- NULL or a location where the argv value ** should be stored. ** Returns: ** 0 on success, ** SM_CONF_ERR_NUL_IN_STRING -- at least one of the ** values in the list contains a NUL byte and ** cannot be represented as part of a string vector ** SM_CONF_ERR_TYPE -- at least one of the values in the ** list is not a single value (but another list), ** SM_CONF_ERR_INVALID -- that wasn't a list or value. ** other error codes (e.g., allocation errors) ** ** Last code review: 2004-05-17 16:41:02, see comments ** Last code change: 2004-05-20 21:11:03 jutta */ int sm_conf_node_argv( sm_conf_T *smc, sm_conf_node_T *list, char const * const **argv_out) { int result; char const **argv; SM_IS_CONF(smc); if (list == NULL) { if (argv_out != NULL) *argv_out = NULL; return 0; } SM_IS_CONF_NODE(list); if (list->scn_type == SM_CONF_NODE_VALUE) { if ( strlen(list->scn_value.scnv_text) != list->scn_value.scnv_text_n) return SM_CONF_ERR_NUL_IN_STRING; /* Make a two-element vector. */ argv = sm_conf_node_cache_get(list, 2 * sizeof(char *), NULL, NULL, &result); if (argv == NULL) return SM_CONF_ERR_NO_MEMORY; /* was this actually new? */ if (result == 0) { argv[0] = list->scn_value.scnv_text; argv[1] = NULL; } } else if (list->scn_type == SM_CONF_NODE_LIST) { sm_conf_node_T *n; size_t i; /* Do these list elements fit into an argv-style vector? */ for (n = list->scn_list.scnl_head, i = 0; n != NULL && i <= list->scn_list.scnl_n; n = n->scn_generic.scng_next, i++) { if (n->scn_type != SM_CONF_NODE_VALUE) return SM_CONF_ERR_TYPE; if ( strlen(n->scn_value.scnv_text) != n->scn_value.scnv_text_n) return SM_CONF_ERR_NUL_IN_STRING; } /* Yes. Obtain or allocate one. */ argv = sm_conf_node_cache_get(list, (list->scn_list.scnl_n + 1) * sizeof(char *), NULL, NULL, &result); if (argv == NULL) return SM_CONF_ERR_NO_MEMORY; /* was this actually new? */ if (result == 0) { for (n = list->scn_list.scnl_head, i = 0; n != NULL && i <= list->scn_list.scnl_n; n = n->scn_generic.scng_next, i++) argv[i] = n->scn_value.scnv_text; SM_ASSERT(i <= list->scn_list.scnl_n); argv[i] = NULL; } } else return SM_CONF_ERR_INVALID; if (argv_out != NULL) *argv_out = argv; return 0; } /* ** SM_CONF_SECTION_NEW -- allocate a new section object ** ** A section is an optionally named hashtable ((ca): list right now) ** ** Parameters: ** smc -- context handle ** name -- name of the section, or NULL ** name_n -- # of bytes pointed to by . ** token -- NULL or first token of the section. ** ** Returns: ** pointer to new node, NULL on failure ** ** Last code review: ** Last code change: */ sm_conf_node_T * sm_conf_section_new( sm_conf_T *smc, char const *keyword, size_t keyword_n, char const *name, size_t name_n, sm_conf_token_T *token) { sm_conf_node_T *n; SM_IS_CONF(smc); if ((n = sm_conf_node_new(smc, token)) != NULL) { n->scn_section.scns_type = SM_CONF_NODE_SECTION; n->scn_section.scns_keyword = keyword; n->scn_section.scns_keyword_n = keyword_n; n->scn_section.scns_name = name; n->scn_section.scns_name_n = name_n; n->scn_section.scns_head = NULL; n->scn_section.scns_tail = &n->scn_section.scns_head; n->scn_section.scns_n = 0; } return n; } /* ** SM_CONF_SECTION_APPEND -- Add an option or subsection ** to an existing section. ** ** Parameters: ** smc -- context handle ** section -- existing section ** name -- name of the entry ** name_n -- # of bytes pointed to by ** node -- value of the entry ** ** Returns: ** None ** ** Last code review: ** Last code change: 2004-05-20 21:16:56 jutta */ void sm_conf_section_append( sm_conf_T *smc, sm_conf_node_T *section, char const *name, size_t name_n, sm_conf_node_T *node) { SM_IS_CONF(smc); SM_IS_CONF_NODE(section); SM_IS_CONF_NODE(node); node->scn_generic.scng_slot = name; node->scn_generic.scng_slot_n = name_n; *section->scn_section.scns_tail = node; section->scn_section.scns_tail = &node->scn_generic.scng_next; node->scn_generic.scng_parent = section; section->scn_section.scns_n++; } /* ** SM_CONF_SECTION_NEXT -- get the next section element. ** ** Parameters: ** smc -- context handle ** section -- NULL or container node ** name -- out: name of the entry ** name_n -- out: # of bytes pointed to by ** pred -- NULL or predecessor ** ** Returns: ** NULL once we're out of nodes or if the container ** isn't actually a section, otherwise ** the first node in the section (if is NULL) ** or 's successor. ** ** Last code review: ** Last code change: */ sm_conf_node_T * sm_conf_section_next( sm_conf_T *smc, sm_conf_node_T const *section, char const **name_out, size_t *name_n_out, sm_conf_node_T const *pred) { sm_conf_node_T *node; if (name_out != NULL) *name_out = NULL; if (name_n_out != NULL) *name_n_out = 0; SM_IS_CONF(smc); if (section != NULL) SM_IS_CONF_NODE(section); if (pred != NULL) SM_IS_CONF_NODE(pred); if (section == NULL || section->scn_type != SM_CONF_NODE_SECTION) return NULL; node = (pred == NULL ? section->scn_section.scns_head : pred->scn_generic.scng_next); if (node == NULL) return NULL; if (name_out != NULL) *name_out = node->scn_generic.scng_slot; if (name_n_out != NULL) *name_n_out = node->scn_generic.scng_slot_n; return node; } /* ** SM_CONF_SECTION_NEXT_SUBSECTION -- get the next subsection ** ** Parameters: ** smc -- context handle ** parent -- NULL or parent section ** keyword -- if non-NULL, must match this keyword. ** keyword_n -- # of bytes pointed to by keyword ** name -- if non-NULL, must match this name. ** name_n -- # of bytes pointed to by the name. ** pred -- NULL or predecessor ** ** Returns: ** NULL once we're out of nodes, otherwise ** the first node in the section (if is NULL) ** or 's successor. ** ** Last code review: ** Last code change: 2004-05-20 21:21:11 jutta */ sm_conf_node_T * sm_conf_section_next_subsection( sm_conf_T *smc, sm_conf_node_T const *parent, char const *keyword, size_t keyword_n, char const *name, size_t name_n, sm_conf_node_T const *pred) { SM_IS_CONF(smc); if (parent != NULL) SM_IS_CONF_NODE(parent); if (pred != NULL) SM_IS_CONF_NODE(pred); while ( (pred = sm_conf_section_next(smc, parent, NULL, NULL, pred)) != NULL) { if (pred->scn_type != SM_CONF_NODE_SECTION) continue; if ( keyword != NULL && !sm_memncaseeq(pred->scn_section.scns_keyword, pred->scn_section.scns_keyword_n, keyword, keyword_n)) continue; if ( name != NULL && !sm_memncaseeq(pred->scn_section.scns_name, pred->scn_section.scns_name_n, name, name_n)) continue; break; } return (sm_conf_node_T *)pred; } /* ** SM_CONF_SECTION_NEXT_OPTION -- get the next named section element ** ** Parameters: ** smc -- context handle ** parent -- NULL or node pointer of containing section ** name -- if non-NULL, must match this name. ** name_n -- # of bytes pointed to by the name. ** pred -- NULL or predecessor ** ** Returns: ** NULL once we're out of nodes, otherwise ** the first node in the section (if is NULL) ** or 's successor. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ sm_conf_node_T * sm_conf_section_next_option( sm_conf_T *smc, sm_conf_node_T const *parent, char const *name, size_t name_n, sm_conf_node_T const *pred) { char const *elem; size_t elem_n; SM_IS_CONF(smc); if (parent != NULL) SM_IS_CONF_NODE(parent); if (pred != NULL) SM_IS_CONF_NODE(pred); while ( (pred = sm_conf_section_next(smc, parent, &elem, &elem_n, pred)) != NULL) { if (elem == NULL || elem_n != name_n) continue; if ( name != NULL && !sm_memncaseeq(elem, elem_n, name, name_n)) continue; break; } return (sm_conf_node_T *)pred; } /* ** SM_CONF_SECTION_KEYWORD -- get a section's keyword. ** ** The keyword is the first identifier associated with the section; ** e.g., in ** interface "local" { } ** the keyword is "interface". ** ** Parameters: ** smc -- context handle ** section -- NULL or a node pointer ** kw_out -- out: section keyword. ** kw_n_out -- out: # of bytes pointed to by <*kw_out> ** ** Returns: ** SM_CONF_ERR_INVALID if this isn't a section, otherwise 0. ** ** Last code review: ** Last code change: 2004-05-20 21:21:40 */ int sm_conf_section_keyword( sm_conf_T *smc, sm_conf_node_T const *section, char const **kw_out, size_t *kw_n_out) { SM_IS_CONF(smc); if (section != NULL) SM_IS_CONF_NODE(section); if ( section == NULL || section->scn_type != SM_CONF_NODE_SECTION) return SM_CONF_ERR_INVALID; if (kw_out != NULL) *kw_out = section->scn_section.scns_keyword; if (kw_n_out != NULL) *kw_n_out = section->scn_section.scns_keyword_n; return 0; } /* ** SM_CONF_SECTION_NAME -- get a section's name. ** ** The name is the second identifier associated with the section; ** e.g., in ** interface "local" { } ** the name is "local". ** ** If the passed-in node is not a section, the call fails. ** If a section is anonymous, the function call succeeds, but ** the assigned pointer and length will be null and zero, respectively. ** ** Parameters: ** smc -- context handle ** section -- NULL or a node pointer ** name_out -- out: section name. ** name_n_out -- out: # of bytes pointed to by <*name_n_out> ** ** Returns: ** SM_CONF_ERR_INVALID if this isn't a section, otherwise 0. ** ** Last code review: ** Last code change: 2004-05-20 21:21:53 */ int sm_conf_section_name( sm_conf_T *smc, sm_conf_node_T const *section, char const **name_out, size_t *name_n_out) { SM_IS_CONF(smc); if (section != NULL) SM_IS_CONF_NODE(section); if ( section == NULL || section->scn_type != SM_CONF_NODE_SECTION) return SM_CONF_ERR_INVALID; if (name_out != NULL) *name_out = section->scn_section.scns_name; if (name_n_out != NULL) *name_n_out = section->scn_section.scns_name_n; return 0; } /* ** SM_CONF_SECTION_N -- get # of entries of a section. ** ** Parameters: ** smc -- context handle ** section -- NULL or a node pointer ** ** Returns: ** 0 if this isn't a section, otherwise the number ** of entries. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ size_t sm_conf_section_n(sm_conf_T *smc, sm_conf_node_T const *section) { SM_IS_CONF(smc); if (section != NULL) SM_IS_CONF_NODE(section); if ( section == NULL || section->scn_type != SM_CONF_NODE_SECTION) return 0; return section->scn_section.scns_n; } /* ** SM_CONF_NODE_DESTROY -- free a node and its subnodes. ** ** Parameters: ** smc -- context handle ** node -- node to destroy. ** ** Returns: ** 0 on success, an error otherwise. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ void sm_conf_node_destroy(sm_conf_T *smc, sm_conf_node_T *node) { sm_conf_node_T *n, *nn; SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); if (node == NULL) return; switch (node->scn_type) { case SM_CONF_NODE_LIST: for (n = node->scn_list.scnl_head; n != NULL; n = nn) { nn = n->scn_generic.scng_next; sm_conf_node_destroy(smc, n); } sm_conf_node_cache_free(node); break; case SM_CONF_NODE_VALUE: sm_conf_node_cache_free(node); break; case SM_CONF_NODE_SECTION: for (n = node->scn_section.scns_head; n != NULL; n = nn) { nn = n->scn_generic.scng_next; sm_conf_node_destroy(smc, n); } break; } node->sm_magic = SM_MAGIC_NULL; sm_free(node); } /* ** SM_CONF_ROOT -- get root of the configuration tree. ** ** Parameters: ** smc -- NULL or context handle ** ** Returns: ** NULL if smc is NULL or hasn't been successfully parsed. ** ((ca) hasn't been successfully parsed???) ** Otherwise, the root of smc's configuration context. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ sm_conf_node_T * sm_conf_root(sm_conf_T *smc) { if (smc == NULL) return NULL; SM_IS_CONF(smc); return smc->smc_root; } /* ** SM_CONF_NODE_TYPE -- get the type of a configuration node. ** ** Parameters: ** smc -- context handle ** node -- node whose type we're interested in. ** ** Returns: ** SM_CONF_NODE_NONE if the node is NULL. ** Otherwise, the type of the node: ** SM_CONF_NODE_VALUE for a single value ** SM_CONF_NODE_LIST for a list ** SM_CONF_NODE_SECTION for a section of named values. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ int sm_conf_node_type(sm_conf_T *smc, sm_conf_node_T const *node) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); return node == NULL ? 0 : node->scn_type; } /* ** SM_CONF_NODE_TYPE_NAME -- get the type of a configuration node, as a string ** ** Parameters: ** smc -- context handle ** node -- node whose type we're interested in. ** ** Returns: ** "null" if the node is NULL. ** Otherwise, the type of the node: ** "value" for a single value ** "list" for a list ** "section" for a section of named values. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ char const * sm_conf_node_type_name(sm_conf_T *smc, sm_conf_node_T const *node) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); if (node == NULL) return "null"; switch (node->scn_type) { case SM_CONF_NODE_VALUE: return "value"; case SM_CONF_NODE_LIST: return "value"; case SM_CONF_NODE_SECTION: return "value"; default: break; } return "unexpected node type"; } /* ** SM_CONF_NODE_NEXT -- get the next node, or NULL ** ** Parameters: ** smc -- context handle ** node -- node whose sibling we're interested in. ** ** Returns: ** NULL if the node is the root node; ** Otherwise, the node's parent. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ sm_conf_node_T * sm_conf_node_next(sm_conf_T *smc, sm_conf_node_T const *node) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); return node == NULL ? NULL : node->scn_generic.scng_next; } /* ** SM_CONF_NODE_PARENT -- get the containing node, or NULL ** ** Parameters: ** smc -- context handle ** node -- node whose container we're interested in. ** ** Returns: ** NULL if the node is the root node; ** Otherwise, the node's parent. ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ sm_conf_node_T * sm_conf_node_parent(sm_conf_T *smc, sm_conf_node_T const *node) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); return node == NULL ? 0 : node->scn_generic.scng_parent; } /* ** SM_CONF_NODE_LOCATION -- print node location for an error message ** ** Parameters: ** smc -- context handle ** node -- node we're interested in. ** buf -- buffer for assembling result ** bufsize -- number of bytes pointed to by ** ** Returns: ** a rendered version of the source location of ** an error involving . ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ char const * sm_conf_node_location( sm_conf_T *smc, sm_conf_node_T const *node, char *buf, size_t bufsize) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); if (bufsize == 0) return smc->smc_name ? smc->smc_name : "*stdin*"; SM_REQUIRE(buf != NULL); if (node != NULL && node->scn_generic.scng_token.sct_line > 0) snprintf(buf, bufsize, "%s:%d", smc->smc_name ? smc->smc_name : "*stdin*", node->scn_generic.scng_token.sct_line); else snprintf(buf, bufsize, "%s", smc->smc_name ? smc->smc_name : "*stdin*"); return buf; } /* ** SM_CONF_NODE_TERMINATE -- terminate node values ** ** '\0'-terminate all strings in a parsed subtree. ** ** We can't do this on the fly because we may still be ** needing the characters right after strings in the ** text we're parsing (e.g., the commas and {} in {1,2,3}. ** ** But once everything is parsed, every string has at least ** one element of punctuation after it, and we can drop ** '\0' after each string. ** ** Parameters: ** smc -- context handle ** node -- subtree we're terminating ** ** Returns: ** none ** ** Last code review: ** Last code change: 2004-05-20 21:21:25 jutta */ void sm_conf_node_terminate(sm_conf_T *smc, sm_conf_node_T *node) { SM_IS_CONF(smc); if (node != NULL) SM_IS_CONF_NODE(node); if (node == NULL) return; switch (node->scn_type) { case SM_CONF_NODE_VALUE: ((char *)node->scn_value.scnv_token.sct_text) [node->scn_value.scnv_token.sct_text_n] = '\0'; break; case SM_CONF_NODE_LIST: for (node = node->scn_list.scnl_head; node != NULL; node = node->scn_generic.scng_next) sm_conf_node_terminate(smc, node); break; case SM_CONF_NODE_SECTION: if (node->scn_section.scns_keyword != NULL) ((char *)node->scn_section.scns_keyword) [node->scn_section.scns_keyword_n] = '\0'; if (node->scn_section.scns_name != NULL) ((char *)node->scn_section.scns_name) [node->scn_section.scns_name_n] = '\0'; for (node = node->scn_section.scns_head; node != NULL; node = node->scn_generic.scng_next) { if (node->scn_generic.scng_slot != NULL) ((char *)node->scn_generic.scng_slot) [node->scn_generic.scng_slot_n] = '\0'; sm_conf_node_terminate(smc, node); } break; default: return; } }