# HG changeset patch # User David Demelier # Date 1321024068 -3600 # Node ID 6981419d48c1db41831dc406419f5fe79d617bf4 # Parent 580d76781c9ecfa6281545f5eb442d6e923bc041 Added inifile.c inifile.h: Initial import of routines to parse .ini like files. diff -r 580d76781c9e -r 6981419d48c1 inifile.c --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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; +} diff -r 580d76781c9e -r 6981419d48c1 inifile.h --- /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 + * + * 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_ */