view ini.c @ 90:145493469aa0

Little bit of cosmetic
author David Demelier <markand@malikania.fr>
date Wed, 30 Nov 2011 11:11:51 +0100
parents d27a6fd446ea
children dcaf2c61c902
line wrap: on
line source

/*
 * ini.c -- parse .ini like files
 *
 * Copyright (c) 2011, 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 <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "ini.h"

/* -------------------------------------------------------- */
/* structure definitions				    */
/* -------------------------------------------------------- */

struct ini_option {
	char		*key;		/* option name */
	char		*value;		/* option value */
	STAILQ_ENTRY(ini_option) next;
};

struct ini_section {
	char		*key;		/* section key */	
	STAILQ_HEAD(, ini_option) options;
	STAILQ_ENTRY(ini_section) next;
};

struct ini_config {
	const char	*path;		/* file path */
	int		flags;		/* optional flags */
	int		ignore;		/* must ignore (no redefine) */

	/* Current section in file */
	struct ini_section *current;

	/* Current file line properties */
	char		*line;		/* line buffer */
	int		lineno;		/* number of line */
	int		linesize;	/* initial line size */

	/* For querying functions */
	STAILQ_HEAD(, ini_section) sections;
};

static char	ini_error[1024 + 1];

/* -------------------------------------------------------- */
/* prototypes						    */
/* -------------------------------------------------------- */

static void	*ini_read(struct ini_config *, FILE *);
static int	ini_getline(struct ini_config *, FILE *);
static void	*ini_readline(struct ini_config *);
static int	ini_switch(struct ini_config *, char **);
static int	ini_register(struct ini_config *, char **);
static void	*ini_fatal(struct ini_config *, FILE *, const char *, ...);
static char	*xstrndup(const char *, size_t);
static struct ini_option *xoptiondup(const struct 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
 * -------------------------------------------------------- */

struct ini_config *
ini_load(const char *path, int flags)
{
	FILE *fp;
	struct ini_config *conf;	

	if ((conf = calloc(1, sizeof (struct 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));

	STAILQ_INIT(&conf->sections);
	conf->path	= path;
	conf->flags	= flags;
	conf->ignore	= 1;
	conf->lineno	= 1;
	conf->linesize	= 1024;

	return ini_read(conf, fp);
}

/*
 * Returns a NULL terminated list of section names. If parameter
 * number is not NULL, number is set to the total number of
 * section correctly parsed.
 */

char **
ini_get_sections_names(const struct ini_config *conf, int *number)
{
	struct ini_section *s;
	char **list;
	int i;

	i = 0;
	STAILQ_FOREACH(s, &conf->sections, next)
		++ i;

	/* For safety */
	if (number != NULL)
		*number = 0;

	if ((list = calloc(i + 1, sizeof (char *))) == NULL)
		return NULL;

	if (number != NULL)
		*number = i;

	i = 0;
	STAILQ_FOREACH(s, &conf->sections, next)
		list[i++] = s->key;

	return list;
}

/*
 * Returns a NULL terminated list of struct ini_section that could be used
 * with ini_get_option(). This function makes sense only when the config
 * has multiple definition enabled.
 */

struct ini_section **
ini_get_sections(const struct ini_config *conf, const char *key, int *nb)
{
	struct ini_section **list;
	struct ini_section *s;
	int i;

	/* Do not use this function on simple config */
	if (I_NOREDEFINE(conf)) {
		errno = EINVAL;
		return NULL;
	}

	i = 0;
	STAILQ_FOREACH(s, &conf->sections, next)
		if (strcmp(s->key, key) == 0)
			++ i;

	if ((list = calloc(i + 1, sizeof (struct ini_section *))) == NULL)
		return NULL;

	if (nb != NULL)
		*nb = i;

	i = 0;
	STAILQ_FOREACH(s, &conf->sections, next)
		if (strcmp(s->key, key) == 0)
			list[i++] = s;

	return list;
}

/*
 * Select the section for further query. This improves performance as it does
 * not need to seek each time the section. It can't be used when multiple
 * definition is enabled because it seek from the beginning, you should use
 * ini_get_sections() and then ini_get_option() for each section.
 */

struct ini_section *
ini_select_section(const struct ini_config *conf, const char *section)
{
	struct ini_section *s;

	STAILQ_FOREACH(s, &conf->sections, next)
		if (strcmp(s->key, section) == 0)
			return s;

	return NULL;
}

/*
 * Wrapper for a simpler usage of ini_select and ini_option. This
 * does not modify the selector so you can safely continue the
 * call to ini_option independantly. Warning, this function returns the
 * first occurence of the option so it is not advised to use it 
 */

char *
ini_get_option_once(const struct ini_config *conf, const char *sect, const char *key)
{
	struct ini_option *o;
	struct ini_section *s;

	STAILQ_FOREACH(s, &conf->sections, next)
		if (strcmp(s->key, sect) == 0)
			break;

	STAILQ_FOREACH(o, &s->options, next)
		if (strcmp(o->key, key) == 0)
			return o->value;

	return NULL;
}

/*
 * Return a NULL list of all available options in the section.
 */

char **
ini_get_option_names(const struct ini_section *section, int *nb)
{
	char **list;
	struct ini_option *o;
	int i;

	i = 0;
	STAILQ_FOREACH(o, &section->options, next)
		++ i;

	if (nb != NULL)
		*nb = i;

	if ((list = calloc(i + 1, sizeof (char *))) == NULL)
		return NULL;

	if (nb != NULL)
		*nb = i;

	i = 0;
	STAILQ_FOREACH(o, &section->options, next)
		list[i++] = o->key;

	return list;
}

/*
 * Get the option in the current selected option. Note, you must call
 * ini_select() before using this function. Returns the value of
 * key or NULL if the option does not exists.
 */

char *
ini_get_option(const struct ini_section *section, const char *key)
{
	struct ini_option *o;

	STAILQ_FOREACH(o, &section->options, next)
		if (strcmp(o->key, key) == 0)
			return o->value;

	return NULL;
}

/*
 * These section provide very small API to convert value
 * and store them to the dst pointer. Because you can also
 * use your own handlers, the user data here can't be used.
 */

void
ini_value_dispatch(struct ini_config *config, struct ini_handler *hdrs, int length)
{
	const char *sectionName;
	int i;
	char *value;
	struct ini_section *section;

	sectionName = NULL;
	for (i = 0; i < length; ++i) {
		/* Do not select the same section for performance. */
		if (sectionName == NULL || strcmp(hdrs[i].section, sectionName) != 0) {
			sectionName	= hdrs[i].section;
			section		= ini_select_section(config, hdrs[i].section);
		}

		/* Skip the section if does not exists in the config */
		if (section == NULL)
			continue;

		value = ini_get_option(section, hdrs[i].option);
		hdrs[i].handler(hdrs[i].dst, 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 = 1;
	else
		*p = 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;
}

/*
 * Return the last error or "No error" if there is not error at all.
 */

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

	return ini_error;
}

void
ini_free(struct ini_config *conf, int freeSections, int freeOptions)
{
	struct ini_section *s, *stmp;
	struct ini_option *o, *otmp;

	STAILQ_FOREACH_SAFE(s, &conf->sections, next, stmp) {
		STAILQ_FOREACH_SAFE(o, &s->options, next, otmp) {
			if (freeOptions) {
				free(o->key);
				free(o->value);

				STAILQ_REMOVE(&s->options, o, ini_option, next);
				free(o);
			}
		}

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

		STAILQ_REMOVE(&conf->sections, s, ini_section, next);
		free(s);
	}

	free(conf);
}

/* --------------------------------------------------------
 * private functions
 * -------------------------------------------------------- */

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

static void *
ini_read(struct 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(struct 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;
}

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

static void *
ini_readline(struct ini_config *conf)
{
	char *lp;
	int (*handler)(struct 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;

/* On other failure seek until the next line '\0' */
#define SEEK_NL(lp)							\
	for (; !isspace(**lp) && **lp != '\0'; ++(*lp))

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

static int
ini_switch(struct 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 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 (struct ini_section))) == NULL)
		return -1;

	if ((conf->current->key = xstrndup(*lp, endSection - *lp)) == NULL) {
		free(conf->current);
		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 */
	STAILQ_INIT(&conf->current->options);
	STAILQ_INSERT_TAIL(&conf->sections, conf->current, next);

	return (conf->ignore = 0);
}

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

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

	/* 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_NL(lp);
		return -1;
	}

	memset(&tmp, 0, sizeof (struct 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;
	STAILQ_INSERT_TAIL(&conf->current->options, option, next);

	return 0;
}

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

	if (conf != NULL) {
		struct ini_section *s, *stmp;
		struct ini_option *o, *otmp;

		if (conf->line != NULL)
			free(conf->line);

		STAILQ_FOREACH_SAFE(s, &conf->sections, next, stmp) {
			STAILQ_FOREACH_SAFE(o, &s->options, next, otmp) {
				free(o->key);
				free(o->value);

				STAILQ_REMOVE(&s->options, o, ini_option, next);

				free(o);
			}

			free(s->key);
			free(s);
		}

	}

	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);

	free(conf);

	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 struct ini_option *
xoptiondup(const struct ini_option *option)
{
	struct ini_option *res;

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

		return NULL;
	}

	if ((res = malloc(sizeof (struct ini_option))) == NULL)
		return NULL;

	res->key	= option->key;
	res->value	= option->value;

	return res;
}