diff inifile.c @ 72:262c053206f6

Cleaned up lot of things in inifile.c: o now possible to have [section] option = "val" on the same line o _register _switch function are used with a function pointer o line may be infinite size o user does not need to specify a line size
author David Demelier <markand@malikania.fr>
date Sun, 13 Nov 2011 15:43:30 +0100
parents 6981419d48c1
children 653d583376c9
line wrap: on
line diff
--- a/inifile.c	Fri Nov 11 16:07:48 2011 +0100
+++ b/inifile.c	Sun Nov 13 15:43:30 2011 +0100
@@ -30,11 +30,6 @@
  * 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 */
@@ -45,7 +40,6 @@
 
 struct section {
 	char		*key;		/* section key */	
-	int		length;		/* number of options */
 	OptionList	options;	/* list of options */
 	STAILQ_ENTRY(section) next;
 };
@@ -56,11 +50,13 @@
 	const char	*path;		/* file path */
 	SectionList	sections;	/* list of sections */
 	struct section	*current;	/* current section */
+	int		flags;		/* inifile_options */
+	int		ignore;		/* must ignore (no redefine) */
+
+	/* Current file line properties */
+	char		*line;		/* line buffer */
 	int		lineno;		/* number of line */
-	int		linesize;	/* size of line */
-	int		options;	/* inifile_options */
-	int		ignore;		/* must ignore (no redefine) */
-	char		*line;		/* line buffer */
+	int		linesize;	/* initial line size */
 
 	/* For querying functions */
 	struct section	*q_section;	/* current section query */
@@ -73,15 +69,18 @@
  * -------------------------------------------------------- */
 
 static void	*inifile_read(struct inifile *, FILE *);
-static void	inifile_switch(struct inifile *, char *);
-static int	inifile_register(struct inifile *, char *);
+static int	inifile_getline(struct inifile *, FILE *);
+static void	*inifile_readline(struct inifile *);
+static int	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);
+static struct option *xoptiondup(const struct option *option);
 
-#define I_VERBOSE(file)		((file)->options & INIFILE_VERBOSE)
-#define I_NOREDEFINE(file)	((file)->options & INIFILE_NOREDEFINE)
+#define I_VERBOSE(file)		((file)->flags & INIFILE_VERBOSE)
+#define I_NOREDEFINE(file)	((file)->flags & INIFILE_NOREDEFINE)
 
-#define SKIP_SPACES(lp)		while (isspace(*(lp)))	++(lp)
+#define SKIP_SPACES(lp)		while (isspace(*lp) && *lp != '\0') ++lp
 #define NFREE(p)		if ((p) != NULL) free((p))
 #define WARN(file, fmt, ...)						\
 	if (I_VERBOSE((file)))						\
@@ -92,7 +91,7 @@
  * -------------------------------------------------------- */
 
 struct inifile *
-inifile_load(const char *path, int options, int linesize)
+inifile_load(const char *path, int flags)
 {
 	FILE *fp;
 	struct inifile *file;	
@@ -103,14 +102,15 @@
 	if ((fp = fopen(path, "r")) == NULL)
 		return inifile_fatal(file, fp, "%s: %s", path, strerror(errno));
 
-	if ((file->line = calloc(linesize + 1, 1)) == NULL)
+	if ((file->line = calloc(1024 + 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->flags	= flags;
+	file->ignore	= 1;
 	file->lineno	= 1;
+	file->linesize	= 1024;
 
 	return inifile_read(file, fp);
 }
@@ -261,35 +261,13 @@
 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);
+	while (inifile_getline(file, fp) == 0) {
+		if (inifile_readline(file) == NULL)
+			return inifile_fatal(file, fp, "%s", strerror(errno));
 
-		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));
+		++ file->lineno;
 	}
 
 	/* Clean up */
@@ -300,144 +278,211 @@
 }
 
 /*
- * 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.
+ * 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 void
-inifile_switch(struct inifile *file, char *lp)
+static int
+inifile_getline(struct inifile *file, FILE *fp)
 {
-	char *endlp;
-	int length;
-	struct section *newsection;
+	int ch, pos;
+
+	memset(file->line, 0, file->linesize);
+	pos = 0;
 
-	/* Is complete? [example] */
-	if ((endlp = strchr(lp, ']')) != NULL) {
-		length = endlp - &lp[1];		/* geek */
-		++ lp;
+	while ((ch = fgetc(fp)) != '\n') {
+		if (feof(fp) || ferror(fp))
+			return -1;
 
-		if (length == 0) {
-			WARN(file, "line %d: 0-length section\n", file->lineno);;
-			return ;
+		/* End of buffer, realloc */
+		if (pos == file->linesize) {
+			file->line = realloc(file->line, file->linesize + 513);
+			if (file->line == NULL)
+				return -1;
+
+			file->linesize += 512;
 		}
 
-		/*
-		 * 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);
+		file->line[pos++] = ch;
+	}
 
-				if (newsection->key == NULL)
-					free(newsection);
-				else {
-					STAILQ_INIT(&newsection->options);
-					newsection->length	= 0;
-					file->current		= newsection;
+	file->line[pos] = '\0';
 
-					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;
-	}
+	return 0;
 }
 
 /*
- * 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.
+ * 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 *
+inifile_readline(struct inifile *file)
+{
+	char *lp;
+	int (*handler)(struct inifile *, char **);
+
+	lp = file->line;
+	SKIP_SPACES(lp);
+
+	/* Ignore empty line or comment */
+	if (*lp == '\0' || *lp == '#')
+		return file;
+
+	while (*lp != '\0') {
+		SKIP_SPACES(lp);
+
+		/* Skip comments again and empty lines */
+		if (*lp == '\0' || *lp == '#' || *lp == ';')
+			return file;
+
+#ifdef DEBUG
+		printf("-- line[%d] == [%s]\n", file->lineno, file->line);
+#endif
+
+		if (*lp == '[')
+			handler = &inifile_switch;
+		else if (!file->ignore)
+			handler = &inifile_register;
+
+		/* Success or not? */
+		if (handler(file, &lp) < 0)
+			return NULL;
+
+		SKIP_SPACES(lp);
+	}
+	return file;
+}
+
+/* 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 inifile_readling from stopping) or -1 on
+ * allocation failure.
  */
 
 static int
-inifile_register(struct inifile *file, char *lp)
+inifile_switch(struct inifile *file, char **lp)
 {
-	char *endlp, *endvalue, tok;
-	int length;
-	struct option *newoption;
+	char *endSection;
 
-	/* 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;
+	/* Section not parsed, ignore next option to prevent breakage */
+	if ((endSection = strchr(*lp, ']')) == NULL) {
+		WARN(file, "line %d: parse error\n", file->lineno);
+		SEEK_NEXT(lp);
+		return (file->ignore = 1) - 1;		/* single line rocks */
 	}
 
-	/* Empty option key */
-	if (endlp - lp == 0) {
-		WARN(file, "line %d: missing keyword before '='\n", file->lineno);
-		return 0;
+	/* Redefinition of previous and not allowed */
+	++(*lp);
+	if (file->current != NULL && I_NOREDEFINE(file) &&
+	    (strncmp(file->current->key, *lp, endSection - *lp)) == 0) {
+		WARN(file, "line %d: redefining %s\n", file->lineno, file->current->key);
+		SEEK_NEXT(lp);
+		return (file->ignore = 1) - 1;
 	}
 
-	if ((newoption = malloc(sizeof (struct option))) == NULL)
+	if ((file->current = malloc(sizeof (struct section))) == NULL)
 		return -1;
 
-	if ((newoption->key = xstrndup(lp, endlp - lp)) == NULL) {
-		free(newoption);
+	if ((file->current->key = xstrndup(*lp, endSection - *lp)) == NULL) {
+		free(file->current);
 		return -1;
 	}
 
-	/* Parse until "" '' or space is found. */
-	++ endvalue;
-	SKIP_SPACES(endvalue);
-	lp = endvalue;
+	*lp = endSection + 1;
+
+#ifdef DEBUG
+	printf("-- current section is now [%s]\n", file->current->key);
+#endif
 
-	tok = *endvalue;
-	if (tok == '"' || tok == '\'') {
-		++ endvalue;
-		while (*endvalue != tok && *endvalue != '\0' && *endvalue != '#')
-			++ endvalue;
+	/* Finally add the new section to the config */
+	STAILQ_INIT(&file->current->options);
+	STAILQ_INSERT_TAIL(&file->sections, file->current, next);
+
+	return (file->ignore = 0);
+}
 
-		/* Print a warning but store what have been parsed */
-		if (*endvalue != tok)
-			WARN(file, "line %d: missing '%c'\n", file->lineno, tok);
+/*
+ * Store the new option that have been parsed. Returns 0 on success or
+ * on parse error (to avoid inifile_readline from stopping) and -1 on
+ * allocation failure.
+ */
 
-		length = endvalue - lp++ - 1;
-	} else {
-		while (!isspace(*endvalue) && *endvalue != '\0')
-			++ endvalue;
+static int
+inifile_register(struct inifile *file, char **lp)
+{
+	char *assignKey, *endKey, *endValue, token = '\0';
+	int length = 0;
+	struct option *option, tmp = { NULL };
 
-		length = endvalue - lp;
+	/* No '=' found */
+	if ((assignKey = strchr(*lp, '=')) == NULL) {
+		WARN(file, "line %d: missing '='\n", file->lineno);
+		SEEK_NEXT(lp);
+		return 0;
 	}
 
-	if (length > 0) {
-		newoption->value = xstrndup(lp, length);
-		if (newoption->value == NULL) {
-			free(newoption->key);
-			free(newoption);
-			return -1;
-		}
-	} else
-		newoption->value = NULL;
+	/* Find end of option */
+	for (endKey = *lp; !isspace(*endKey) && *endKey != '\0'; ++endKey)
+		continue;
+
+	/* Do not register empty option name */
+	if (endKey - *lp == 0) {
+		WARN(file, "line %d: 0-length option\n", file->lineno);
+		SEEK_NL(lp);
+		return 0;
+	}
+
+	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;
 
-	/* And finally add the option to the current section */
-	file->current->length ++;
-	STAILQ_INSERT_TAIL(&file->current->options, newoption, next);
+		/* Correctly closed */	
+		if (token != '\0' && *endValue == token)
+			++ endValue;
+		else
+			WARN(file, "line %d: missing '%c'\n", file->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 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(&file->current->options, option, next);
 
 	return 0;
 }
@@ -502,3 +547,27 @@
 
 	return res;
 }
+
+static struct option *
+xoptiondup(const struct option *option)
+{
+	struct 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 option))) == NULL)
+		return NULL;
+
+	res->key	= option->key;
+	res->value	= option->value;
+
+	return res;
+}