view ini.c @ 130:1513cf3a985e

Add setprogname(3) and getprogname(3) port
author David Demelier <markand@malikania.fr>
date Fri, 09 Mar 2012 19:31:51 +0100
parents 268566dcc2c4
children 07800b7af208
line wrap: on
line source

/*
 * ini.c -- parse .ini like files
 *
 * Copyright (c) 2011, 2012, David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "ini.h"

static char	ini_error[1024 + 1];

/* --------------------------------------------------------
 * Prototypes
 * -------------------------------------------------------- */

static void		*ini_read(INI_Config *, FILE *);
static int		ini_getline(INI_Config *, FILE *);
static INI_Config	*ini_readline(INI_Config *);
static int		ini_switch(INI_Config *, char **);
static int		ini_register(INI_Config *, char **);
static void		*ini_fatal(INI_Config *, FILE *, const char *, ...);
static char		*xstrndup(const char *, size_t);
static INI_Option	*xoptiondup(const INI_Option *option);

#define I_VERBOSE(file)		((file)->flags & INI_VERBOSE)
#define I_NOREDEFINE(file)	((file)->flags & INI_NOREDEFINE)
#define I_FAILERROR(file)	((file)->flags & INI_FAILERROR)

#define SKIP_SPACES(lp)		while (isspace(*lp) && *lp != '\0') ++lp
#define WARN(file, fmt, ...)						\
	if (I_VERBOSE((file)))						\
		fprintf(stderr, fmt, __VA_ARGS__)

/* --------------------------------------------------------
 * Public functions
 * -------------------------------------------------------- */

INI_Config *
ini_load(const char *path, int flags)
{
	INI_Config *conf;	
	FILE *fp = NULL;

	if ((conf = calloc(1, sizeof (INI_Config))) == NULL)
		return ini_fatal(conf, fp, "%s", strerror(errno));

	if ((fp = fopen(path, "r")) == NULL)
		return ini_fatal(conf, fp, "%s: %s", path, strerror(errno));

	if ((conf->_line = calloc(1024 + 1, 1)) == NULL)
		return ini_fatal(conf, fp, "%s", strerror(errno));

	conf->path	= path;
	conf->flags	= flags;
	conf->_ignore	= 1;
	conf->_lineno	= 1;
	conf->_linesize	= 1024;

	return ini_read(conf, fp);
}

/*
 * Search the section `sectname' in the config structure. If the section
 * is not found, this function returns NULL.
 */

INI_Section *
ini_select_section(const INI_Config *conf, const char *sectname)
{
	INI_Section *s;

	for (s = conf->sections; s != NULL; s = s->next)
		if (strcmp(s->key, sectname) == 0)
			return s;

	return NULL;
}

/*
 * Search the option `optname' in the section structure, argument
 * section mustn't be NULL. If the option is not found, this function
 * returns NULL.
 */

INI_Option *
ini_select_option(const INI_Section *section, const char *optname)
{
	INI_Option *o;

	for (o = section->options; o != NULL; o = o->next)
		if (strcmp(o->key, optname) == 0)
			return o;

	return NULL;
}

/*
 * Find an option from the config, by section and option names.
 * Returns the option or NULL if not found.
 */

INI_Option *
ini_find(const INI_Config *config, const char *sname, const char *oname)
{
	INI_Section *sc;

	if ((sc = ini_select_section(config, sname)))
		return ini_select_option(sc, oname);

	return NULL;
}

/*
 * Return the last error or "No error" if there is no error.
 */

char *
ini_get_error(void)
{
	if (ini_error[0] == '\0')
		return "No error";

	return ini_error;
}

void
ini_free(INI_Config *conf, int freeSections, int freeOptions)
{
	INI_Section *s, *snext;
	INI_Option *o, *onext;

	for (s = snext = conf->sections; snext != NULL; s = snext) {
		snext = s->next;

		for (o = onext = s->options; o != NULL; o = onext) {
			onext = o->next;

			if (freeOptions) {
				free(o->key);
				free(o->value);
				free(o);
			}
		}

		if (freeSections)
			free(s->key);

		free(s);
	}

	free(conf);
}

/*
 * This section provides a very small API to convert values
 * and store them to the dst pointer. Because you can also
 * use your own handlers, the user data here is unused.
 */

void
ini_dispatch(INI_Section *section, INI_Handler *hdrs, int length)
{
	INI_Option *option;
	int i;

	for (i = 0; i < length; ++i) {
		option = ini_select_option(section, hdrs[i].key);

		if (option != NULL)
			hdrs[i].handler(hdrs[i].dst, option->value,
			    hdrs[i].userdata);
	}
}

void
ini_convert_bool(void *dst, const char *value, void *dummy)
{
	char *p = dst;

	if (value == NULL)
		return ;

	if (strcmp(value, "yes") == 0 ||
	    strcmp(value, "true") == 0 ||
	    strcmp(value, "1") == 0)
		*p = (char)1;
	else
		*p = (char)0;
	(void)dummy;
}

void
ini_convert_int(void *dst, const char *value, void *dummy)
{
	int *p = dst;

	if (value == NULL)
		return ;

	*p = (int) strtol(value, NULL, 10);
	(void)dummy;
}

void
ini_convert_short(void *dst, const char *value, void *dummy)
{
	short *p = dst;

	if (value == NULL)
		return ;

	*p = (short) strtol(value, NULL, 10);
	(void)dummy;
}

void
ini_convert_string(void *dst, const char *value, void *dummy)
{
	char **p = dst;

	if (value == NULL)
		*p = NULL;
	else
		*p = strdup(value);

	(void)dummy;
}

/* --------------------------------------------------------
 * Private functions
 * -------------------------------------------------------- */

/*
 * Read file line per line and try to parse ini like file. Lines
 * starting with '#', ';'  or empty lines are ignored. Returns the file
 * on success and NULL on failure.
 */

static void *
ini_read(INI_Config *conf, FILE *fp)
{
	while (ini_getline(conf, fp) == 0) {
		if (ini_readline(conf) == NULL) {
			free(conf->_line);
			ini_free(conf, 1, 1);

			fclose(fp);
			return NULL;
		}

		++ conf->_lineno;
	}

	/* Clean up */
	free(conf->_line);
	fclose(fp);

	return conf;
}

/*
 * Read the next line until the next '\n' character is found. Returns 0
 * if the system had enough memory or -1 on allocation failure or on
 * end of file.
 */

static int
ini_getline(INI_Config *conf, FILE *fp)
{
	int ch, pos;

	memset(conf->_line, 0, conf->_linesize);
	pos = 0;

	while ((ch = fgetc(fp)) != '\n') {
		if (feof(fp) || ferror(fp))
			return -1;

		/* End of buffer, realloc */
		if (pos == conf->_linesize) {
			conf->_line = realloc(conf->_line, conf->_linesize + 513);
			if (conf->_line == NULL)
				return -1;

			conf->_linesize += 512;
		}

		conf->_line[pos++] = ch;
	}

	conf->_line[pos] = '\0';

	return 0;
}

/*
 * Parse the line that has been successfully read from the file.
 * Returns NULL on allocation failure (instanciation of new
 * section or option) or the file on success.
 */

static INI_Config *
ini_readline(INI_Config *conf)
{
	char *lp;
	int (*handler)(INI_Config *, char **);

	lp = conf->_line;
	SKIP_SPACES(lp);

	/* Ignore empty line or comment */
	if (*lp == '\0' || *lp == '#' || *lp == ';')
		return conf;

	while (*lp != '\0') {
		SKIP_SPACES(lp);

		/* Skip comments again and empty lines */
		if (*lp == '\0' || *lp == '#' || *lp == ';')
			return conf;

#ifdef INI_DEBUG
		printf("-- line[%d] == [%s]\n", conf->_lineno, conf->_line);
#endif

		if (*lp == '[')
			handler = &ini_switch;
		else if (!conf->_ignore)
			handler = &ini_register;
		else {
			handler = NULL;
			++ lp;
		}

		/* Success or not? */
		if (handler != NULL && handler(conf, &lp) < 0)
			return (I_FAILERROR(conf)) ? NULL : conf;

		SKIP_SPACES(lp);
	}

	return conf;
}

/* On failure, seek next space */
#define SEEK_NEXT(lp)							\
	for (; !isspace(**lp) && **lp != '\0'; ++(*lp))			\
		continue;

/*
 * Parse the new section (function called when '[' is found). Returns
 * 0 even on parse error (to avoid ini_readline from stopping) or -1 on
 * allocation failure.
 */

static int
ini_switch(INI_Config *conf, char **lp)
{
	char *endSection;

	/* Section not parsed, ignore next option to prevent breakage */
	if ((endSection = strchr(*lp, ']')) == NULL) {
		WARN(conf, "line %d: parse error\n", conf->_lineno);
		SEEK_NEXT(lp);
		return (conf->_ignore = 1) - 2;		/* single line rocks */
	}

	/* Redefinition of previous section and not allowed? */
	++(*lp);
	if (conf->_current != NULL
	    && I_NOREDEFINE(conf)
	    && strncmp(conf->_current->key, *lp, endSection - *lp) == 0)
	{
		WARN(conf, "line %d: redefining %s\n", conf->_lineno,
		    conf->_current->key);
		SEEK_NEXT(lp);
		return (conf->_ignore = 1) - 2;
	}

	if ((conf->_current = malloc(sizeof (INI_Section))) == NULL)
		return -1;

	memset(conf->_current, 0, sizeof (INI_Section));
	if ((conf->_current->key = xstrndup(*lp, endSection - *lp)) == NULL) {
		free(conf->_current);
		conf->_current = NULL;
		return -1;
	}

	*lp = endSection + 1;

#ifdef INI_DEBUG
	printf("-- current section is now [%s]\n", conf->_current->key);
#endif

	/* Finally add the new section to the config */
	conf->_current->next	= conf->sections;
	conf->sections		= conf->_current;

	return (conf->_ignore = 0);
}

/*
 * Store the new option that has been parsed. Returns 0 on success
 * even on parse error (to avoid ini_readline from stopping) and -1 on
 * allocation failure.
 */

static int
ini_register(INI_Config *conf, char **lp)
{
	INI_Option *option, tmp;
	char *assignKey, *endKey, *endValue, token = '\0';
	int length = 0;

	/* No '=' found */
	if ((assignKey = strchr(*lp, '=')) == NULL) {
		WARN(conf, "line %d: missing '='\n", conf->_lineno);
		SEEK_NEXT(lp);
		return -1;
	}

	/* Find end of option */
	endKey = *lp;
	for (; !isspace(*endKey) && *endKey != '\0' && *endKey != '='; ++endKey)
		continue;

	/* Do not register empty option name */
	if (endKey - *lp <= 0) {
		WARN(conf, "line %d: 0-length option\n", conf->_lineno);
		SEEK_NEXT(lp);
		return -1;
	}

	memset(&tmp, 0, sizeof (INI_Option));
	if ((tmp.key = xstrndup(*lp, endKey - *lp)) == NULL)
		return -1;

	endValue = &assignKey[1];
	SKIP_SPACES(endValue);

	/* Find end of option value */
	token = *endValue;
	if (token == '\'' || token == '"') {
		for (*lp = ++endValue; *endValue != token &&
		    *endValue != '\0'; ++endValue)
			continue;

		length = endValue - *lp;

		/* Correctly closed */	
		if (token != '\0' && *endValue == token)
			++ endValue;
		else
			WARN(conf, "line %d: missing '%c'\n", conf->_lineno, token);
	} else {
		for (*lp = endValue; !isspace(*endValue) && *endValue != '\0' && 
		    *endValue != '#' && *endValue != ';'; ++endValue)
			continue;

		length = endValue - *lp;
	}

	if (length != 0)
		tmp.value = xstrndup(*lp, length);

	if ((option = xoptiondup(&tmp)) == NULL)
		return -1;

#ifdef INI_DEBUG
	printf("-- next option [%s] is set to [%s]\n", tmp.key, tmp.value);
#endif

	/* Finally add the new option to the current section */
	*lp = endValue;
	option->next = conf->_current->options;
	conf->_current->options	= option;

	return 0;
}

static void *
ini_fatal(INI_Config *conf, FILE *fp, const char *fmt, ...)
{
	va_list ap;

	if (conf != NULL) {
		if (conf->_line != NULL)
			free(conf->_line);

		ini_free(conf, 1, 1);
	}

	if (fp != NULL)
		fclose(fp);

	va_start(ap, fmt);
	vsnprintf(ini_error, 1024, fmt, ap);
	va_end(ap);

	/* Directly print error if VERBOSE is enabled */
	WARN(conf, "%s\n", ini_error);

	return NULL;
}

static char *
xstrndup(const char *src, size_t max)
{
	char *res;
	size_t length;

	for (length = 0; length < max && src[length] != '\0'; ++length)
		continue;

	if ((res = malloc(length + 1)) == NULL)
		return NULL;

	memcpy(res, src, length);
	res[length] = '\0';

	return res;
}

static INI_Option *
xoptiondup(const INI_Option *option)
{
	INI_Option *res;

	/*
	 * Value may be NULL but the option key may not.
	 */
	if (option->key == NULL) {
		if (option->value != NULL)
			free(option->value);

		return NULL;
	}

	if ((res = malloc(sizeof (INI_Option))) == NULL)
		return NULL;

	memcpy(res, option, sizeof (INI_Option));

	return res;
}