view ini.c @ 161:81d64d4a5473

Complete rewrite of pack.c: o more concise, o for buffer, available in autoallocation and static,
author David Demelier <markand@malikania.fr>
date Mon, 03 Sep 2012 21:21:33 +0200
parents 2563b3e71859
children
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"

/*
 * sys/queue.h bits.
 */

#if !defined(TAILQ_FIRST)
#define	TAILQ_FIRST(head)	((head)->tqh_first)
#endif

#if !defined(TAILQ_FOREACH)
#define	TAILQ_FOREACH(var, head, field)					\
	for ((var) = TAILQ_FIRST((head));				\
	    (var);							\
	    (var) = TAILQ_NEXT((var), field))
#endif

#if !defined(TAILQ_FOREACH_SAFE)
#define	TAILQ_FOREACH_SAFE(var, head, field, tvar)			\
	for ((var) = TAILQ_FIRST((head));				\
	    (var) && ((tvar) = TAILQ_NEXT((var), field), 1);		\
	    (var) = (tvar))
#endif

#if !defined(TAILQ_INIT)
#define	TAILQ_INIT(head) do {						\
	TAILQ_FIRST((head)) = NULL;					\
	(head)->tqh_last = &TAILQ_FIRST((head));			\
} while (0)
#endif

#if !defined(TAILQ_INSERT_TAIL)
#define	TAILQ_INSERT_TAIL(head, elm, field) do {			\
	TAILQ_NEXT((elm), field) = NULL;				\
	(elm)->field.tqe_prev = (head)->tqh_last;			\
	*(head)->tqh_last = (elm);					\
	(head)->tqh_last = &TAILQ_NEXT((elm), field);			\
} while (0)
#endif

#if !defined(TAILQ_NEXT)
#define	TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#endif

#if !defined(TAILQ_REMOVE)
#define	TAILQ_REMOVE(head, elm, field) do {				\
	if ((TAILQ_NEXT((elm), field)) != NULL)				\
		TAILQ_NEXT((elm), field)->field.tqe_prev = 		\
		    (elm)->field.tqe_prev;				\
	else {								\
		(head)->tqh_last = (elm)->field.tqe_prev;		\
	}								\
	*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);		\
} while (0)
#endif

/* --------------------------------------------------------
 * Structure definitions
 * -------------------------------------------------------- */

struct ini_private {
	struct ini_section	*current;	/* current working section */
	int			ignore;		/* must ignore (no redefine) */
	FILE			*fp;		/* file pointer to read */

	/* Line buffer */
	char			*line;		/* line data */
	int			linesize;	/* line allocated size */
	int			lineno;		/* number of line in file */

	/* Error reporting */
	char			error[1024];
};

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

#define F_VERBOSE(cg)		((cg)->flags & INI_VERBOSE)
#define F_NOREDEFINE(cg)	((cg)->flags & INI_NOREDEFINE)
#define F_FAILERROR(cg)		((cg)->flags & INI_FAILERROR)

static char *
sstrndup(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 void
sskip(char **lp)
{
	while (isspace (**lp) && **lp != '\0')
		++(*lp);
}

static void
sseek(char **lp)
{
	while (!isspace(**lp) && **lp != '\0')
		++(*lp);
}

static void
ini_set_error(struct ini_config *cg, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (cg->pv->error)
		vsnprintf(cg->pv->error, sizeof (cg->pv->error), fmt, ap);
	va_end(ap);
}

static void
ini_warn(struct ini_config *cg, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (F_VERBOSE(cg)) {
		vprintf(fmt, ap);
		fputc('\n', stdout);
	}

	if (F_FAILERROR(cg)) {
		va_start(ap, fmt);
		vsnprintf(cg->pv->error, sizeof (cg->pv->error), fmt, ap);
		va_end(ap);
	}

	va_end(ap);
}

/*
 * Add a new section, this function only returns -1 on fatal error that may
 * be impossible to continue parsing for the current line.
 */

static int
ini_add_section(struct ini_config *cg, char **lp)
{
	char *begin = *lp, *end;
	size_t length;
	struct ini_section *section = NULL;

	if (!(end = strchr(begin, ']'))) {
		ini_warn(cg, "line %d: syntax error after [", cg->pv->lineno);
		goto bad;
	}

	length = end - begin;

	/* May not redefine? */
	if (cg->pv->current && F_NOREDEFINE(cg))
		if (strncmp(cg->pv->current->key, begin, length) == 0) {
			ini_warn(cg, "line %d: redefining %s", cg->pv->lineno,
			    cg->pv->current->key);
			goto bad;
		}

	/* Allocate a new section and add it */
	if (!(section = calloc(1, sizeof (*section))))
		goto bad;

	if (!(section->key = sstrndup(begin, length)))
		goto bad;

	TAILQ_INIT(&section->options);
	TAILQ_INSERT_TAIL(&cg->sections, section, link);
	cg->pv->current	= section;
	cg->pv->ignore	= 0;

	/* Trigger an event for the new section */
	if (cg->open)
		cg->open(cg->data, section->key);

	*lp = &end[1];

	return 0;

bad:	if (section)
		free(section->key);
	free(section);

	cg->pv->ignore = 1;
	*lp = strchr(*lp, '\0');

	return F_FAILERROR(cg) ? -1 : 0;
}

/*
 * Add a new option, the returns value is same as ini_add_section.
 */

static int
ini_add_option(struct ini_config *cg, char **lp)
{
	char *begin = *lp, *equal, *end;
	struct ini_option *option = NULL;

	if (!cg->pv->current) {
		ini_warn(cg, "line %d: option within no section", cg->pv->lineno);
		goto bad;
	}

	if (cg->pv->ignore ||
	    !(option = calloc(1, sizeof (*option))))
		goto bad;

	/* Not valid */
	if (!(equal = strchr(begin, '='))) {
		ini_warn(cg, "line %d: missing =", cg->pv->lineno);
		goto bad;
	}

	/* End of option */
	end = begin;
	sskip(&end);
	sseek(&end);

	option->key = sstrndup(begin, end - begin);

	/* End of value */
	begin = &equal[1];
	sskip(&begin);
	end = begin;

	if (*end == '\'' || *end == '"') {
		for (++end; *end != *begin && *end != '\0'; ++end)
			continue;
		if (*end != *begin) {
			ini_warn(cg, "line %d: missing %c",
			    cg->pv->lineno, *begin);
			*lp = end;
			goto bad;
		} else
			++ begin;
	} else
		sseek(&end);

	option->value = sstrndup(begin, end - begin);

	if (!option->key || !option->value) {
		ini_warn(cg, "line %d: syntax error", cg->pv->lineno);
		goto bad;
	}

	TAILQ_INSERT_TAIL(&cg->pv->current->options, option, link);

	/* Trigger an event for the new section */
	if (cg->get)
		cg->get(cg->data, cg->pv->current->key, option);

	*lp = &end[1];

	return 0;

bad:	if (option) {
		free(option->key);
		free(option->value);
	}
	free(option);

	*lp = strchr(*lp, '\0');

	return F_FAILERROR(cg) ? -1 : 0;
}

static int
ini_read_line(struct ini_config *cg)
{
	char *lp = cg->pv->line;
	int rv;

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

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

		if (*lp == '\0' || *lp == '#' || *lp == ';')
			return 0;

		/* Begin of section */
		if (*lp == '[') {
			++ lp;
			rv = ini_add_section(cg, &lp);
		/* Begin of option */
		} else {
			rv = ini_add_option(cg, &lp);
		}

		if (rv < 0)
			return -1;
	}

	return 0;
}

/*
 * 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. Only for mode == INI_LINEAR
 */

static int
ini_get_line(struct ini_config *cg)
{
	int ch, pos;

	memset(cg->pv->line, 0, cg->pv->linesize);
	pos = 0;

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

		/* End of buffer, realloc */
		if (pos == cg->pv->linesize) {
			cg->pv->line = realloc(cg->pv->line, cg->pv->linesize + 512);
			if (!cg->pv->line)
				return -1;

			cg->pv->linesize += 512;
		}

		cg->pv->line[pos++] = ch;
	}

	return 0;
}

static int
ini_open_file(struct ini_config *cg)
{
	if (!(cg->pv->fp = fopen(cg->path, "r"))) {
		ini_set_error(cg, "open: %s", cg->path);
		return -1;
	}

	cg->pv->linesize = INI_DEFAULT_LINESIZE;
	cg->pv->line = calloc(sizeof (char), cg->pv->linesize);

	if (!cg->pv->line) {
		ini_set_error(cg, "malloc: %s", strerror(errno));
		return -1;
	}

	return 0;
}

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

struct ini_config *
ini_create(const char *path, enum ini_flags flags)
{
	struct ini_config *cg;	

	cg = calloc(1, sizeof (*cg));
	if (!cg)
		return NULL;

	cg->pv = calloc(1, sizeof (struct ini_private));
	if (!cg->pv)
		return NULL;

	cg->path	= path;
	cg->flags	= flags;

	return cg;
}

void
ini_set_handlers(struct ini_config *cg, void *data, ini_open_t open,
		 ini_get_t get)
{
	if (!cg)
		return;

	cg->data	= data;
	cg->open	= open;
	cg->get		= get;
}

int
ini_open(struct ini_config *cg)
{
	int rv = 0;

	/* Open the file and prepare data for reading */
	if (ini_open_file(cg) < 0)
		return -1;

	cg->pv->ignore	= 1;
	cg->pv->lineno	= 1;

	TAILQ_INIT(&cg->sections);

	while (ini_get_line(cg) != -1 && !rv) {
		rv = ini_read_line(cg);
		++ cg->pv->lineno;
	}

	fclose(cg->pv->fp);

	return rv;
}

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

struct ini_section *
ini_select_section(const struct ini_config *cg, const char *sectname)
{
	struct ini_section *s;

	TAILQ_FOREACH(s, &cg->sections, link)
		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.
 */

struct ini_option *
ini_select_option(const struct ini_section *sc, const char *name)
{
	struct ini_option *o;

	TAILQ_FOREACH(o, &sc->options, link)
		if (strcmp(o->key, name) == 0)
			return o;

	return NULL;
}

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

struct ini_option *
ini_find(const struct ini_config *cg, const char *scname, const char *optname)
{
	struct ini_section *sc;

	if ((sc = ini_select_section(cg, scname)))
		return ini_select_option(sc, optname);

	return NULL;
}

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

const char *
ini_get_error(const struct ini_config *cg)
{
	if (!cg && errno != 0)
		return strerror(errno);

	if (cg->pv->error[0] == '\0') {
		if (errno != 0)
			return strerror(errno);

		return "No error";
	}

	return cg->pv->error;
}

/*
 * Free everything, level may be set like this:
 * 0, only free parser and private resources.
 * 1, free all sections and their keys,
 * 2, free all sections and options keys and values.
 */

void
ini_free(struct ini_config *cg, int level)
{
	struct ini_section *s, *stmp;
	struct ini_option *o, *otmp;

	if (level >= 1) {
		TAILQ_FOREACH_SAFE(s, &cg->sections, link, stmp) {
			if (level >= 2) {
				TAILQ_FOREACH_SAFE(o, &s->options, link, otmp) {
					TAILQ_REMOVE(&s->options, o, link);
					free(o->key);
					free(o->value);
					free(o);
				}
			}

			TAILQ_REMOVE(&cg->sections, s, link);
			free(s->key);
			free(s);
		}
	}

	if (level >= 0) {
		free(cg->pv->line);
		free(cg->pv);
		free(cg);
	}
}