changeset 139:d62f2f657768

Introducing new ini API, some changes and use sys/queue.h instead
author David Demelier <markand@malikania.fr>
date Fri, 11 May 2012 00:09:30 +0200
parents cf738da3ad60
children 0cf53c588a83
files ini.c ini.h
diffstat 2 files changed, 563 insertions(+), 555 deletions(-) [+]
line wrap: on
line diff
--- a/ini.c	Mon Mar 26 10:48:39 2012 +0200
+++ b/ini.c	Fri May 11 00:09:30 2012 +0200
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/stat.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
@@ -25,505 +26,88 @@
 
 #include "ini.h"
 
-static char	ini_error[1024 + 1];
+/*
+ * 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
  * -------------------------------------------------------- */
 
-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->ic_line = calloc(1024 + 1, 1)) == NULL)
-		return ini_fatal(conf, fp, "%s", strerror(errno));
-
-	conf->path	= path;
-	conf->flags	= flags;
-	conf->ic_ignore	= 1;
-	conf->ic_lineno	= 1;
-	conf->ic_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->ic_line);
-			ini_free(conf, 1, 1);
-
-			fclose(fp);
-			return NULL;
-		}
-
-		++ conf->ic_lineno;
-	}
-
-	/* Clean up */
-	free(conf->ic_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->ic_line, 0, conf->ic_linesize);
-	pos = 0;
-
-	while ((ch = fgetc(fp)) != '\n') {
-		if (feof(fp) || ferror(fp))
-			return -1;
-
-		/* End of buffer, realloc */
-		if (pos == conf->ic_linesize) {
-			conf->ic_line = realloc(conf->ic_line, conf->ic_linesize + 513);
-			if (conf->ic_line == NULL)
-				return -1;
-
-			conf->ic_linesize += 512;
-		}
-
-		conf->ic_line[pos++] = ch;
-	}
-
-	conf->ic_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->ic_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->ic_lineno, conf->ic_line);
-#endif
-
-		if (*lp == '[')
-			handler = &ini_switch;
-		else if (!conf->ic_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->ic_lineno);
-		SEEK_NEXT(lp);
-		return (conf->ic_ignore = 1) - 2;		/* single line rocks */
-	}
-
-	/* Redefinition of previous section and not allowed? */
-	++(*lp);
-	if (conf->ic_current != NULL
-	    && I_NOREDEFINE(conf)
-	    && strncmp(conf->ic_current->key, *lp, endSection - *lp) == 0)
-	{
-		WARN(conf, "line %d: redefining %s\n", conf->ic_lineno,
-		    conf->ic_current->key);
-		SEEK_NEXT(lp);
-		return (conf->ic_ignore = 1) - 2;
-	}
-
-	if ((conf->ic_current = malloc(sizeof (INI_Section))) == NULL)
-		return -1;
-
-	memset(conf->ic_current, 0, sizeof (INI_Section));
-	if ((conf->ic_current->key = xstrndup(*lp, endSection - *lp)) == NULL) {
-		free(conf->ic_current);
-		conf->ic_current = NULL;
-		return -1;
-	}
-
-	*lp = endSection + 1;
-
-#ifdef INI_DEBUG
-	printf("-- current section is now [%s]\n", conf->ic_current->key);
-#endif
-
-	/* Finally add the new section to the config */
-	conf->ic_current->next	= conf->sections;
-	conf->sections		= conf->ic_current;
-
-	return (conf->ic_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->ic_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->ic_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->ic_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->ic_current->options;
-	conf->ic_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->ic_line != NULL)
-			free(conf->ic_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;
-}
+#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 *
-xstrndup(const char *src, size_t max)
+sstrndup(const char *src, size_t max)
 {
 	char *res;
 	size_t length;
@@ -540,25 +124,420 @@
 	return res;
 }
 
-static INI_Option *
-xoptiondup(const INI_Option *option)
+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);
+	}
+
+	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)
 {
-	INI_Option *res;
+	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;
+}
 
-	/*
-	 * Value may be NULL but the option key may not.
-	 */
-	if (option->key == NULL) {
-		if (option->value != NULL)
-			free(option->value);
+/*
+ * 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)
+		goto bad;
+
+	if (!(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;
+	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);
 
-		return NULL;
+	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);
+
+		/* 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;
 	}
 
-	if ((res = malloc(sizeof (INI_Option))) == NULL)
+	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, int 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;
 
-	memcpy(res, option, sizeof (INI_Option));
+	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;
 
-	return res;
+	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')
+		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);
+	}
+}
--- a/ini.h	Mon Mar 26 10:48:39 2012 +0200
+++ b/ini.h	Fri May 11 00:09:30 2012 +0200
@@ -23,81 +23,108 @@
 extern "C" {
 #endif
 
-#define	INI_VERBOSE	(1 << 0)	/* be verbose */
-#define	INI_NOREDEFINE	(1 << 1)	/* do not allow redefinitions */
-#define INI_FAILERROR	(1 << 2)	/* abort parsing on first error */
+#if !defined(INI_DEFAULT_LINESIZE)
+#  define INI_DEFAULT_LINESIZE		1024
+#endif
+
+/*
+ * sys/queue.h bits.
+ */
+
+#if !defined(TAILQ_ENTRY)
+#define	TAILQ_ENTRY(type)						\
+struct {								\
+	struct type *tqe_next;	/* next element */			\
+	struct type **tqe_prev;	/* address of previous next element */	\
+}
+#endif
 
-typedef struct ini_config	INI_Config;
-typedef struct ini_section	INI_Section;
-typedef struct ini_option	INI_Option;
-typedef struct ini_handler	INI_Handler;
+#if !defined(TAILQ_HEAD)
+#define	TAILQ_HEAD(name, type)						\
+struct name {								\
+	struct type *tqh_first;	/* first element */			\
+	struct type **tqh_last;	/* addr of last next element */		\
+}
+#endif
 
-typedef void (*INI_ConvertFunc)(void *, const char *, void *);
+enum {
+	INI_VERBOSE		= (1 << 0),	/* be verbose */
+	INI_NOREDEFINE		= (1 << 1),	/* do not allow redefinitions */
+	INI_FAILERROR		= (1 << 2)	/* abort parsing on first error */
+};
+
+typedef struct ini_config	ini_config_t;
+typedef struct ini_section	ini_section_t;
+typedef struct ini_option	ini_option_t;
+typedef struct ini_handler	ini_handler_t;
+
+typedef void (*ini_open_t)(void *, const char *);
+typedef void (*ini_get_t)(void *, const char *, const ini_option_t *);
 
 /* --------------------------------------------------------
  * Structure definitions
  * -------------------------------------------------------- */
 
+struct ini_private;
+
 struct ini_config {
-	/*
-	 * General settings
-	 */
-	const char	*path;		/* (ro) file path */
-	int		flags;		/* (ro) optional flags */
+	const char		*path;		/* (ro) file path */
+	int			flags;		/* (ro) optional flags */
+	void			*data;		/* (rw) user data */
 
-	/*
-	 * Sections that have been parsed
-	 */
-	INI_Section	*sections;	/* (ro) linked-list of sections */
+	TAILQ_HEAD(, ini_section) sections;	/* (ro) linked-list of sections */
 
-	/* Private fields */
-	INI_Section	*ic_current;	/* current working section */
-	char		*ic_line;	/* line buffer */
-	int		ic_lineno;	/* number of current line */
-	int		ic_linesize;	/* initial line size */
-	int		ic_ignore;	/* must ignore (no redefine) */
+	/* Event driven method */
+	void (*open)(void *, const char *);
+	void (*get)(void *, const char *, const struct ini_option *);
+
+	/* Private data */
+	struct ini_private	*pv;
 };
 
 struct ini_option {
-	char		*key;		/* (rw) option name */
-	char		*value;		/* (rw) option value */
-	INI_Option	*next;		/* (ro) next option */
+	char			*key;		/* (rw) option name */
+	char			*value;		/* (rw) option value */
+
+	TAILQ_ENTRY(ini_option)	link;
 };
 
 struct ini_section {
-	char		*key;		/* (rw) section key */	
-	INI_Option	*options;	/* (ro) linked-list of options */
-	INI_Section	*next;		/* (ro) linked-list of sections */
-};
+	char			*key;		/* (rw) section key */	
+	TAILQ_HEAD(, ini_option) options;	/* (rw) list of options */
 
-struct ini_handler {
-	char		*key;		/* (rw) option to check */
-	void		*dst;		/* (rw) where to store */
-	INI_ConvertFunc	handler;	/* (rw) conversion function */
-	void		*userdata;	/* (rw) optional user data */
+	TAILQ_ENTRY(ini_section) link;
 };
 
 /* --------------------------------------------------------
  * Main functions
  * -------------------------------------------------------- */
 
-INI_Config *
-ini_load(const char *, int);
-
-INI_Section *
-ini_select_section(const INI_Config *, const char *);
-
-INI_Option *
-ini_select_option(const INI_Section *, const char *);
-
-INI_Option *
-ini_find(const INI_Config *, const char *, const char *);
-
-char *
-ini_get_error(void);
+struct ini_config *
+ini_create(const char *, int);
 
 void
-ini_free(INI_Config *, int, int);
+ini_set_handlers(struct ini_config *, void *, ini_open_t, ini_get_t);
+
+int
+ini_open(struct ini_config *);
+
+struct ini_section *
+ini_select_section(const struct ini_config *, const char *);
+
+struct ini_option *
+ini_select_option(const struct ini_section *, const char *);
+
+struct ini_option *
+ini_find(const struct ini_config *, const char *, const char *);
+
+const char *
+ini_get_error(const struct ini_config *);
+
+void
+ini_free(struct ini_config *, int);
+
+#if 0
 
 /* --------------------------------------------------------
  * Convenient api to query and convert data
@@ -143,6 +170,8 @@
 void
 ini_convert_string(void *, const char *, void *);
 
+#endif
+
 #ifdef __cplusplus
 }
 #endif