/*
 * 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 <errno.h>
#include <string.h>
#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" {
		**			...
		**		}
		**	<foo> 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" {
		**			...
		**		}
		**	<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 <text>
**		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 <text>
**
**	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 <prev> is NULL, the list's first element
**		if <prev> is non-NULL, <prev>'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 <name>.
**		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 <name>
**		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 <name>
**		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 <pred> is NULL)
**			or <pred>'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 <pred> is NULL)
**		or <pred>'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 <pred> is NULL)
**		or <pred>'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 <buf>
**
**	Returns:
**		a rendered version of the source location of
**		an error involving <node>.
**
**	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;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1