changeset 71:6981419d48c1

Added inifile.c inifile.h: Initial import of routines to parse .ini like files.
author David Demelier <markand@malikania.fr>
date Fri, 11 Nov 2011 16:07:48 +0100
parents 580d76781c9e
children 262c053206f6
files inifile.c inifile.h
diffstat 2 files changed, 539 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inifile.c	Fri Nov 11 16:07:48 2011 +0100
@@ -0,0 +1,504 @@
+/*
+ * inifile.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 "inifile.h"
+
+/* --------------------------------------------------------
+ * structure definitions
+ * -------------------------------------------------------- */
+
+/*
+ * We use STAILQ as stack, it means if the user supplied once
+ * or more times the same options the last one is taken.
+ */
+
+struct option {
+	char		*key;		/* option name */
+	char		*value;		/* option value */
+	STAILQ_ENTRY(option) next;
+};
+
+typedef STAILQ_HEAD(, option) OptionList;
+
+struct section {
+	char		*key;		/* section key */	
+	int		length;		/* number of options */
+	OptionList	options;	/* list of options */
+	STAILQ_ENTRY(section) next;
+};
+
+typedef STAILQ_HEAD(, section) SectionList;
+
+struct inifile {
+	const char	*path;		/* file path */
+	SectionList	sections;	/* list of sections */
+	struct section	*current;	/* current section */
+	int		lineno;		/* number of line */
+	int		linesize;	/* size of line */
+	int		options;	/* inifile_options */
+	int		ignore;		/* must ignore (no redefine) */
+	char		*line;		/* line buffer */
+
+	/* For querying functions */
+	struct section	*q_section;	/* current section query */
+};
+
+static char	inifileError[1024 + 1];
+
+/* --------------------------------------------------------
+ * prototypes
+ * -------------------------------------------------------- */
+
+static void	*inifile_read(struct inifile *, FILE *);
+static void	inifile_switch(struct inifile *, char *);
+static int	inifile_register(struct inifile *, char *);
+static void	*inifile_fatal(struct inifile *, FILE *, const char *, ...);
+static char	*xstrndup(const char *, size_t);
+
+#define I_VERBOSE(file)		((file)->options & INIFILE_VERBOSE)
+#define I_NOREDEFINE(file)	((file)->options & INIFILE_NOREDEFINE)
+
+#define SKIP_SPACES(lp)		while (isspace(*(lp)))	++(lp)
+#define NFREE(p)		if ((p) != NULL) free((p))
+#define WARN(file, fmt, ...)						\
+	if (I_VERBOSE((file)))						\
+		fprintf(stderr, fmt, __VA_ARGS__)
+
+/* --------------------------------------------------------
+ * public functions
+ * -------------------------------------------------------- */
+
+struct inifile *
+inifile_load(const char *path, int options, int linesize)
+{
+	FILE *fp;
+	struct inifile *file;	
+
+	if ((file = calloc(1, sizeof (struct inifile))) == NULL)
+		return inifile_fatal(file, fp, "%s", strerror(errno));
+
+	if ((fp = fopen(path, "r")) == NULL)
+		return inifile_fatal(file, fp, "%s: %s", path, strerror(errno));
+
+	if ((file->line = calloc(linesize + 1, 1)) == NULL)
+		return inifile_fatal(file, fp, "%s", strerror(errno));
+
+	STAILQ_INIT(&file->sections);
+	file->path	= path;
+	file->options	= options;
+	file->linesize	= (linesize == 0) ? 1024 : linesize;
+	file->lineno	= 1;
+
+	return inifile_read(file, fp);
+}
+
+/*
+ * Return 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 **
+inifile_sections(struct inifile *file, int *number)
+{
+	char **list;
+	int i;
+	struct section *s;
+
+	i = 0;
+	STAILQ_FOREACH(s, &file->sections, next)
+		++i;
+
+	if (number != NULL)
+		*number = i;
+
+	if ((list = calloc(i + 1, sizeof (char *))) == NULL)
+		return NULL;
+
+	i = 0;
+	STAILQ_FOREACH(s, &file->sections, next)
+		list[i] = s->key;
+
+	return list;
+}
+
+/*
+ * Select the section for further query. This improves performance as it does
+ * not need to seek each time the section. If there are multiple redefinitions
+ * of section then the function seek the next redefined section.
+ */
+
+int
+inifile_select(struct inifile *file, const char *section)
+{
+	struct section *s;
+	int found = -1;
+
+	if (file->q_section == NULL)
+		file->q_section = s = STAILQ_FIRST(&file->sections);
+	else
+		s = STAILQ_NEXT(file->q_section, next);
+
+	while (s != NULL) {
+		if (strcmp(s->key, section) == 0) {
+			file->q_section = s;
+			found = 0;
+			break;
+		}
+
+		s = STAILQ_NEXT(s, next);
+	}
+
+	if (found < 0)
+		file->q_section = NULL;
+
+	return found;
+}
+
+/*
+ * Get the option in the current selected option. Note, you must call
+ * inifile_select() before using this function. Returns the value of
+ * key or NULL if the option does not exists.
+ */
+
+char *
+inifile_option(struct inifile *file, const char *key)
+{
+	struct option *o;
+
+	STAILQ_FOREACH(o, &file->q_section->options, next)
+		if (strcmp(o->key, key) == 0)
+			return o->value;
+
+	return NULL;
+}
+
+/*
+ * Wrapper for a simpler usage of inifile_select and inifile_option. This
+ * does not modify the selector so you can safely continue the
+ * call to inifile_option independantly. Warning, this function returns the
+ * first occurence of the option so it is not advised to use it 
+ */
+
+char *
+inifile_option_simple(struct inifile *file, const char *sect, const char *key)
+{
+	struct option *o;
+	struct section *s;
+
+	STAILQ_FOREACH(s, &file->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;
+}
+
+void
+inifile_free(struct inifile *file, int freeSections, int freeOptions)
+{
+	struct section *s, *stmp;
+	struct option *o, *otmp;
+
+	STAILQ_FOREACH_SAFE(s, &file->sections, next, stmp) {
+		STAILQ_FOREACH_SAFE(o, &s->options, next, otmp) {
+			if (freeOptions) {
+				free(o->key);
+				if (o->value)
+					free(o->value);
+
+				STAILQ_REMOVE(&s->options, o, option, next);
+				free(o);
+			}
+		}
+
+		if (freeSections)
+			free(s->key);
+
+		STAILQ_REMOVE(&file->sections, s, section, next);
+		free(s);
+	}
+
+	free(file);
+}
+
+/* --------------------------------------------------------
+ * 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 *
+inifile_read(struct inifile *file, FILE *fp)
+{
+	char *lp;
+	int status;
+
+	for (; fgets(file->line, file->linesize, fp); ++file->lineno) {
+		/* Move the file char pointer to the next '\n' if truncated */
+		if ((lp = strchr(file->line, '\n')) != NULL)
+			*lp = '\0';
+		else {
+			int ch;
+			while ((ch = fgetc(fp)) != '\n' && ch != EOF);
+		}
+
+		lp = file->line;
+		SKIP_SPACES(lp);
+
+		if (*lp == '\0' || *lp == '#')
+			continue;
+
+		if (*lp == '[') {
+			inifile_switch(file, lp);
+
+			/* Fail to create a new section? */
+			if (file->current == NULL)
+				return inifile_fatal(file, fp, "%s",
+				    strerror(errno));
+		} else if (!file->ignore)
+			if (inifile_register(file, lp) < 0)
+				return inifile_fatal(file, fp, "%s",
+				    strerror(errno));
+	}
+
+	/* Clean up */
+	free(file->line);
+	fclose(fp);
+
+	return file;
+}
+
+/*
+ * Switch to the next section. If option NOREDEFINE is set and
+ * user wrote two or more times the same section, every options are
+ * simply ignore and a warning may be issued if VERBOSE is set.
+ *
+ * This function set file->current to NULL on allocation failure.
+ */
+
+static void
+inifile_switch(struct inifile *file, char *lp)
+{
+	char *endlp;
+	int length;
+	struct section *newsection;
+
+	/* Is complete? [example] */
+	if ((endlp = strchr(lp, ']')) != NULL) {
+		length = endlp - &lp[1];		/* geek */
+		++ lp;
+
+		if (length == 0) {
+			WARN(file, "line %d: 0-length section\n", file->lineno);;
+			return ;
+		}
+
+		/*
+		 * Do not redefine? If yes then the next options must
+		 * be ignored.
+		 */
+		if (file->current != NULL && I_NOREDEFINE(file) &&
+		    strncmp(file->current->key, lp, length) == 0) {
+			WARN(file, "line %d: warning redefining '%s'\n",
+			    file->lineno, file->current->key);
+			file->ignore = 1;
+		} else {
+			newsection = malloc(sizeof (struct section));
+
+			/* Add a new section */
+			if (newsection != NULL) {
+				newsection->key = xstrndup(lp, length);
+
+				if (newsection->key == NULL)
+					free(newsection);
+				else {
+					STAILQ_INIT(&newsection->options);
+					newsection->length	= 0;
+					file->current		= newsection;
+
+					STAILQ_INSERT_TAIL(&file->sections,
+					    newsection, next);
+				}
+			}
+		}
+	} else {
+		/*
+		 * The section has not been parsed, next options must
+		 * be ignored until a new section is found.
+		 */
+		file->ignore = 1;
+	}
+}
+
+/*
+ * Register a new option in the current section. The value may be emptym then
+ * the value is set to NULL. If a string is not correctly quoted a warning
+ * may be printed if VERBOSE is enabled, if the assigment key is missing
+ * a warning may be printed too.
+ */
+
+static int
+inifile_register(struct inifile *file, char *lp)
+{
+	char *endlp, *endvalue, tok;
+	int length;
+	struct option *newoption;
+
+	/* Parse until '=' is found to store the key */
+	for (endlp = lp; !isspace(*endlp) && *endlp != '='; ++endlp)
+		continue;
+
+	for (endvalue = lp; *endvalue != '=' && *endvalue != '\0'; ++ endvalue)
+		continue;
+
+	/* Missing = */
+	if (*endvalue != '=') {
+		WARN(file, "line %d: missing '='\n", file->lineno);
+		return 0;
+	}
+
+	/* Empty option key */
+	if (endlp - lp == 0) {
+		WARN(file, "line %d: missing keyword before '='\n", file->lineno);
+		return 0;
+	}
+
+	if ((newoption = malloc(sizeof (struct option))) == NULL)
+		return -1;
+
+	if ((newoption->key = xstrndup(lp, endlp - lp)) == NULL) {
+		free(newoption);
+		return -1;
+	}
+
+	/* Parse until "" '' or space is found. */
+	++ endvalue;
+	SKIP_SPACES(endvalue);
+	lp = endvalue;
+
+	tok = *endvalue;
+	if (tok == '"' || tok == '\'') {
+		++ endvalue;
+		while (*endvalue != tok && *endvalue != '\0' && *endvalue != '#')
+			++ endvalue;
+
+		/* Print a warning but store what have been parsed */
+		if (*endvalue != tok)
+			WARN(file, "line %d: missing '%c'\n", file->lineno, tok);
+
+		length = endvalue - lp++ - 1;
+	} else {
+		while (!isspace(*endvalue) && *endvalue != '\0')
+			++ endvalue;
+
+		length = endvalue - lp;
+	}
+
+	if (length > 0) {
+		newoption->value = xstrndup(lp, length);
+		if (newoption->value == NULL) {
+			free(newoption->key);
+			free(newoption);
+			return -1;
+		}
+	} else
+		newoption->value = NULL;
+
+	/* And finally add the option to the current section */
+	file->current->length ++;
+	STAILQ_INSERT_TAIL(&file->current->options, newoption, next);
+
+	return 0;
+}
+
+static void *
+inifile_fatal(struct inifile *file, FILE *fp, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (file) {
+		struct section *s, *stmp;
+		struct option *o, *otmp;
+
+		if (file->line != NULL)
+			free(file->line);
+
+		STAILQ_FOREACH_SAFE(s, &file->sections, next, stmp) {
+			STAILQ_FOREACH_SAFE(o, &s->options, next, otmp) {
+				free(o->key);
+				free(o->value);
+
+				STAILQ_REMOVE(&s->options, o, option, next);
+
+				free(o);
+			}
+
+			free(s->key);
+			free(s);
+		}
+
+	}
+
+	if (fp != NULL)
+		fclose(fp);
+
+	va_start(ap, fmt);
+	vsnprintf(inifileError, 1024, fmt, ap);
+	va_end(ap);
+
+	/* Directly print error if VERBOSE is enabled */
+	WARN(file, "%s\n", inifileError);
+
+	free(file);
+
+	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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inifile.h	Fri Nov 11 16:07:48 2011 +0100
@@ -0,0 +1,35 @@
+/*
+ * inifile.h -- 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.
+ */
+
+#ifndef _INIFILE_H_
+#define _INIFILE_H_
+
+#define	INIFILE_VERBOSE		(1 << 0)
+#define	INIFILE_NOREDEFINE	(1 << 1)
+
+struct inifile;
+
+struct inifile	*inifile_load(const char *, int, int);
+char		**inifile_sections(struct inifile *, int *);
+int		inifile_select(struct inifile *, const char *);
+char		*inifile_option(struct inifile *, const char *);
+char		*inifile_option_simple(struct inifile *,
+		     const char *, const char *);
+void		inifile_free(struct inifile *, int, int);
+
+#endif /* _INIFILE_H_ */