/*
 * 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.c,v 1.19 2007/05/20 15:41:56 ca Exp $")

#if SM_LIBCONF_ALONE
#include <limits.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include "sm-conf.h"
#include "sm-util.h"
#else /* SM_LIBCONF_ALONE */
#include "sm/limits.h"
#include "sm/string.h"
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/ctype.h"
#include "sm/heap.h"
#include "sm/memops.h"
#include "sm/sm-conf.h"
#endif /* SM_LIBCONF_ALONE */

#include "sm-conf-state.h"
#include "sm-conf-token.h"
#include "sm-conf-parse.h"

/* SM-CONF.C -- high-level API functions */

/*
**  SM_CONF_NEW -- create a new configuration state
**
**	The configuration state is passed into all other
**	configuration-related functions.
**
**	Parameters:
**		name -- name used for error messages
**
**	Returns:
**		NULL on allocation errors,
**		otherwise a pointer to a new sm_conf_T that must
**		be free'd using sm_conf_destroy().
*/

sm_conf_T *
sm_conf_new(char const *name)
{
	size_t	  name_n;
	sm_conf_T *smc;

	name_n = (NULL == name ? 0 : strlen(name) + 1);
	if ((smc = sm_zalloc(sizeof(*smc) + name_n)) != NULL) {
		if (0 == name_n)
			smc->smc_name  = NULL;
		else
			smc->smc_name = sm_memcpy((char *)(smc + 1), name, name_n);
		smc->sm_magic = SM_CONF_MAGIC;
		smc->smc_line  = 1;
		smc->smc_error_tail = &smc->smc_error_head;
	}
	return smc;
}

/*
**  SM_CONF_READ_DATA -- read configuration text from a data pointer
**
**	Parameters:
**		smc -- configuration state with a read configuration file
**		data -- bytes to read
**		n -- number of bytes pointed to by <data>.
**		copy -- copy data or just point to it?
**
**	Returns:
**		0 on success,
**		otherwise an errno or SM_CONF_ERR_... error number.
*/

int
sm_conf_read_data(sm_conf_T *smc, char *data, size_t n, bool copy)
{
	if (NULL == smc || (data == NULL && n != 0))
		return SM_CONF_ERR_INVALID;

	SM_IS_CONF(smc);
	if (n > 0 && copy) {
		SM_REQUIRE(data != NULL);
		smc->smc_buf = sm_malloc(n);
		if (NULL == smc->smc_buf)
			return ENOMEM;
		smc->smc_mybuf = true;
		sm_memcpy(smc->smc_buf, data, n);
	}
	else if (!copy) {
		smc->smc_buf = data;
		smc->smc_mybuf = false;
	}
	smc->smc_buf_n = n;
	return sm_conf_parse(smc);
}

/*
**  SM_CONF_DESTROY -- free configuration resources
**
**	Given a handle returned by <sm_conf_new()>, this call
**	frees all configuration resources associated with that handle.
**
**	Parameters:
**		smc -- NULL or configuration handle to free
**
**	Returns:
**		none
*/

void
sm_conf_destroy(sm_conf_T *smc)
{
	if (smc != NULL) {
		sm_conf_error_T	*e;

		/* Free stored syntax errors */
		while ((e = smc->smc_error_head) != NULL) {
			smc->smc_error_head = e->sce_next;
			sm_free(e);
		}

		if (smc->smc_mybuf) {
			SM_FREE(smc->smc_buf);
			smc->smc_mybuf = false;
		}
		sm_conf_node_destroy(smc, smc->smc_root);
		smc->sm_magic = SM_MAGIC_NULL;
		sm_free(smc);
	}
}

/*
**  SM_CONF_STRERROR -- return English string for error message
**
**	Parameters:
**		err -- error number returned by an sm_conf_* function
**		buf -- buffer to use in assembling the error message
**		bufsize -- # of bytes pointed to by <buf>.
**
**	Returns:
**		a pointer to an English error message with at least
**		the lifetime of <buf>.
*/

char const *
sm_conf_strerror(int err, char *buf, size_t bufsize)
{
	switch (err) {
	  case SM_CONF_ERR_NEWLINE_IN_STRING:
		return "newline in string";
	  case SM_CONF_ERR_EOF_IN_STRING:
		return "EOF in string";
	  case SM_CONF_ERR_BAD_CHAR:
		return "unexpected character";
	  case SM_CONF_ERR_NO_MEMORY:
		return "out of memory";
	  case SM_CONF_ERR_HEX_EXPECTED:
		return "hexadecimal digit expected after \\x, \\u or \\U";
	  case SM_CONF_ERR_CHAR_OVERFLOW:
		return "character constant too large";
	  case SM_CONF_ERR_INVALID:
		return "invalid argument to configuration function";
	  case SM_CONF_ERR_READ:
		return "error while reading configuration file";
	  case SM_CONF_ERR_READ_OPEN:
		return "cannot open configuration file for reading";
	  case SM_CONF_ERR_READ_CLOSE:
		return "error closing configuration file after reading";
	  case SM_CONF_ERR_SYNTAX:
		return "syntax error";
	  case SM_CONF_ERR_NOT_FOUND:
		return "not found";
	  case SM_CONF_ERR_ALREADY:
		return "already exists";
	  case SM_CONF_ERR_TYPE:
		return "invalid value";
	  case SM_CONF_ERR_NUL_IN_STRING:
		return "ascii NUL character in string";
	  case SM_CONF_ERR_TOO_MANY:
		return "too many elements in array";
	  default:
		break;
	}

	if (err > 0)
		return strerror(err);

	snprintf(buf, bufsize,
		"unexpected configuration parser error %d", err);
	return buf;
}

/*
**  SM_CONF_SYNTAX_ERROR -- return the next syntax error.
**
**	Parameters:
**		smc -- handle
**		prev -- NULL or the previous error
**
**	Returns:
**		a pointer to an English error message or NULL,
**		if we're out of errors.
*/

char const *
sm_conf_syntax_error(sm_conf_T *smc, char const *prev)
{
	sm_conf_error_T	const *e;

	if (NULL == smc)
		return NULL == prev ? "initialization failed" : NULL;

	SM_IS_CONF(smc);
	if (NULL == prev) {
		if (NULL == smc->smc_error_head)
			return NULL;
		return smc->smc_error_head->sce_text;
	}

	/*
	**  prev points to previous error message. To get back to the previous
	**  sm_conf_error_T we need to subtract the offset of sce_text.
	*/

	e = (sm_conf_error_T const *)
		((char const *)prev - offsetof(sm_conf_error_T, sce_text));

	if (NULL == e->sce_next)
		return NULL;
	return e->sce_next->sce_text;
}

/*
**  SM_CONF_ERROR_ADD -- add an error statement
**
**	Parameters:
**		smc -- handle
**		fmt -- format
**		... -- arguments for fmt
**
**	Returns:
**		error code
**
**	Side Effects:
**		allocates new error context and adds it to smc error list.
**		prints formatted text into error text field of error context.
*/

int
sm_conf_error_add(sm_conf_T *smc, char const *fmt, ...)
{
	va_list		ap;
	sm_conf_error_T	*err;

	SM_IS_CONF(smc);
	err = sm_malloc(sizeof(*err));
	if (NULL == err)
		return ENOMEM;

	va_start(ap, fmt);
	vsnprintf(err->sce_text, sizeof err->sce_text, fmt, ap);
	err->sce_next = NULL;
	va_end(ap);

	*smc->smc_error_tail = err;
	smc->smc_error_tail = &err->sce_next;
	smc->smc_error_n++;

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1