Mercurial > code
view ini.c @ 90:145493469aa0
Little bit of cosmetic
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 30 Nov 2011 11:11:51 +0100 |
parents | d27a6fd446ea |
children | dcaf2c61c902 |
line wrap: on
line source
/* * 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 ini_error[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(const struct ini_config *conf, int *number) { struct ini_section *s; char **list; int i; i = 0; STAILQ_FOREACH(s, &conf->sections, next) ++ i; /* For safety */ if (number != NULL) *number = 0; 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(const 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_get_option_once(const 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(const struct ini_section *section, int *nb) { char **list; struct ini_option *o; int i; i = 0; STAILQ_FOREACH(o, §ion->options, next) ++ i; if (nb != NULL) *nb = 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(const 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; } /* * These section provide very small API to convert value * and store them to the dst pointer. Because you can also * use your own handlers, the user data here can't be used. */ void ini_value_dispatch(struct ini_config *config, struct ini_handler *hdrs, int length) { const char *sectionName; int i; char *value; struct ini_section *section; sectionName = NULL; for (i = 0; i < length; ++i) { /* Do not select the same section for performance. */ if (sectionName == NULL || strcmp(hdrs[i].section, sectionName) != 0) { sectionName = hdrs[i].section; section = ini_select_section(config, hdrs[i].section); } /* Skip the section if does not exists in the config */ if (section == NULL) continue; value = ini_get_option(section, hdrs[i].option); hdrs[i].handler(hdrs[i].dst, value, hdrs[i].userdata); } } void ini_convert_bool(void *dst, const char *value, void *dummy) { char *p = dst; if (value == NULL) return ; if (strcmp(value, "yes") == 0 || strcmp(value, "true") == 0 || strcmp(value, "1") == 0) *p = 1; else *p = 0; (void)dummy; } void ini_convert_int(void *dst, const char *value, void *dummy) { int *p = dst; if (value == NULL) return ; *p = (int) strtol(value, NULL, 10); (void)dummy; } void ini_convert_short(void *dst, const char *value, void *dummy) { short *p = dst; if (value == NULL) return ; *p = (short) strtol(value, NULL, 10); (void)dummy; } void ini_convert_string(void *dst, const char *value, void *dummy) { char **p = dst; if (value == NULL) *p = NULL; else *p = strdup(value); (void)dummy; } /* * Return the last error or "No error" if there is not error at all. */ char * ini_get_error(void) { if (ini_error[0] == '\0') return "No error"; return ini_error; } 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; ++ lp; } /* 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 != '#' && *endValue != ';'; ++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(ini_error, 1024, fmt, ap); va_end(ap); /* Directly print error if VERBOSE is enabled */ WARN(conf, "%s\n", ini_error); 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; }