Mercurial > code
changeset 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 | ad60e5ebd92c |
children | 820426789f17 f42bcb9e7b4a |
files | ini.c ini.h inifile.c inifile.h |
diffstat | 4 files changed, 706 insertions(+), 636 deletions(-) [+] |
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, §ion->options, next) + ++ i; + + if ((list = calloc(i + 1, sizeof (char *))) == NULL) + return NULL; + + if (nb != NULL) + *nb = i; + + i = 0; + STAILQ_FOREACH(o, §ion->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, §ion->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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ini.h Tue Nov 15 20:12:48 2011 +0100 @@ -0,0 +1,64 @@ +/* + * ini.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 _INI_H_ +#define _INI_H_ + +#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 */ + +struct ini_config; +struct ini_section; + +/* -------------------------------------------------------- + * struct ini_config functions. + * -------------------------------------------------------- */ + +struct ini_config * +ini_load(const char *, int); + +char ** +ini_get_sections_names(struct ini_config *, int *); + +struct ini_section ** +ini_get_sections(const struct ini_config *, const char *, int *); + +struct ini_section * +ini_select_section(struct ini_config *, const char *); + +char * +ini_get_option_once(struct ini_config *, const char *, const char *); + +void +ini_free(struct ini_config *, int, int); + +char * +ini_error(void); + +/* -------------------------------------------------------- + * struct ini_section functions. + * -------------------------------------------------------- */ + +char ** +ini_get_option_names(struct ini_section *, int *); + +char * +ini_get_option(struct ini_section *, const char *key); + +#endif /* _INI_H_ */
--- a/inifile.c Mon Nov 14 19:57:53 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,601 +0,0 @@ -/* - * 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 - * -------------------------------------------------------- */ - -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 */ - 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 flags; /* inifile_options */ - int ignore; /* must ignore (no redefine) */ - - /* Current file line properties */ - char *line; /* line buffer */ - int lineno; /* number of line */ - int linesize; /* initial line size */ - - /* For querying functions */ - struct section *q_section; /* current section query */ -}; - -static char inifileError[1024 + 1]; - -/* -------------------------------------------------------- - * prototypes - * -------------------------------------------------------- */ - -static void *inifile_read(struct inifile *, FILE *); -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)->flags & INIFILE_VERBOSE) -#define I_NOREDEFINE(file) ((file)->flags & INIFILE_NOREDEFINE) - -#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))) \ - fprintf(stderr, fmt, __VA_ARGS__) - -/* -------------------------------------------------------- - * public functions - * -------------------------------------------------------- */ - -struct inifile * -inifile_load(const char *path, int flags) -{ - 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(1024 + 1, 1)) == NULL) - return inifile_fatal(file, fp, "%s", strerror(errno)); - - STAILQ_INIT(&file->sections); - file->path = path; - file->flags = flags; - file->ignore = 1; - file->lineno = 1; - file->linesize = 1024; - - 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 multiple redefinition is allowed, seek the next - * section from this first one. - */ - if (!I_NOREDEFINE(file) && (s = file->q_section) != NULL) { - /* - * But if the user want to find an other section we - * need to start from beginning. - */ - if (strcmp(s->key, section) == 0) - s = STAILQ_NEXT(s, next); - else - s = STAILQ_FIRST(&file->sections); - } else - s = STAILQ_FIRST(&file->sections); - - while (s != NULL) { - if (strcmp(s->key, section) == 0) { - file->q_section = s; - found = 1; - break; - } - - s = STAILQ_NEXT(s, next); - } - -#ifdef INIFILE_DEBUG - if (found == 1) - printf("-- selected section [%s]\n", s->key); -#endif - - 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_once(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; -} - -/* - * Return the last error or "No error" if there is not error at all. - */ - -char * -inifile_error(void) -{ - if (inifileError[0] == '\0') - return "No error"; - - return inifileError; -} - -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) -{ - while (inifile_getline(file, fp) == 0) { - if (inifile_readline(file) == NULL) - return inifile_fatal(file, fp, "%s", strerror(errno)); - - ++ file->lineno; - } - - /* Clean up */ - free(file->line); - fclose(fp); - - return file; -} - -/* - * 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 -inifile_getline(struct inifile *file, FILE *fp) -{ - int ch, pos; - - memset(file->line, 0, file->linesize); - pos = 0; - - while ((ch = fgetc(fp)) != '\n') { - if (feof(fp) || ferror(fp)) - return -1; - - /* 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; - } - - file->line[pos++] = ch; - } - - file->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 * -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 == '#' || *lp == ';') - return file; - - while (*lp != '\0') { - SKIP_SPACES(lp); - - /* Skip comments again and empty lines */ - if (*lp == '\0' || *lp == '#' || *lp == ';') - return file; - -#ifdef INIFILE_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_switch(struct inifile *file, char **lp) -{ - char *endSection; - - /* 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 */ - } - - /* 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 ((file->current = malloc(sizeof (struct section))) == NULL) - return -1; - - if ((file->current->key = xstrndup(*lp, endSection - *lp)) == NULL) { - free(file->current); - return -1; - } - - *lp = endSection + 1; - -#ifdef INIFILE_DEBUG - printf("-- current section is now [%s]\n", file->current->key); -#endif - - /* 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); -} - -/* - * 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. - */ - -static int -inifile_register(struct inifile *file, char **lp) -{ - char *assignKey, *endKey, *endValue, token = '\0'; - int length = 0; - struct option *option, tmp; - - /* No '=' found */ - if ((assignKey = strchr(*lp, '=')) == NULL) { - WARN(file, "line %d: missing '='\n", file->lineno); - SEEK_NEXT(lp); - return 0; - } - - /* 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; - } - - memset(&tmp, 0, sizeof (struct 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(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 INIFILE_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; -} - -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; -} - -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; -}
--- a/inifile.h Mon Nov 14 19:57:53 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -/* - * 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); -char **inifile_sections(struct inifile *, int *); -int inifile_select(struct inifile *, const char *); -char *inifile_option(struct inifile *, const char *); -char *inifile_option_once(struct inifile *, const char *, const char *); -char *inifile_error(void); -void inifile_free(struct inifile *, int, int); - -#endif /* _INIFILE_H_ */