diff ini.c @ 79:fd817a7dbf2f

Switched a lot of things in ini
author David Demelier <markand@malikania.fr>
date Tue, 15 Nov 2011 20:12:48 +0100
parents inifile.c@4c5f69f5f409
children f42bcb9e7b4a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ini.c	Tue Nov 15 20:12:48 2011 +0100
@@ -0,0 +1,642 @@
+/*
+ * 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	iniError[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(struct ini_config *conf, int *number)
+{
+	struct ini_section *s;
+	char **list;
+	int i;
+
+	i = 0;
+	STAILQ_FOREACH(s, &conf->sections, next)
+		++ i;
+
+	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(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_option_once(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(struct ini_section *section, int *nb)
+{
+	char **list;
+	struct ini_option *o;
+	int i;
+
+	i = 0;
+	STAILQ_FOREACH(o, &section->options, next)
+		++ 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(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;
+}
+
+/*
+ * Return the last error or "No error" if there is not error at all.
+ */
+
+char *
+ini_error(void)
+{
+	if (iniError[0] == '\0')
+		return "No error";
+
+	return iniError;
+}
+
+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;
+
+		/* 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)
+			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(iniError, 1024, fmt, ap);
+	va_end(ap);
+
+	/* Directly print error if VERBOSE is enabled */
+	WARN(conf, "%s\n", iniError);
+
+	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;
+}