Mercurial > code
view ini.c @ 173:18ad49172e6c
Update documentation and add Unix sockets
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 12 Sep 2013 11:23:08 +0200 |
parents | 2563b3e71859 |
children |
line wrap: on
line source
/* * ini.c -- parse .ini like files * * Copyright (c) 2011, 2012, 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 <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <ctype.h> #include <errno.h> #include "ini.h" /* * sys/queue.h bits. */ #if !defined(TAILQ_FIRST) #define TAILQ_FIRST(head) ((head)->tqh_first) #endif #if !defined(TAILQ_FOREACH) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); \ (var); \ (var) = TAILQ_NEXT((var), field)) #endif #if !defined(TAILQ_FOREACH_SAFE) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #endif #if !defined(TAILQ_INIT) #define TAILQ_INIT(head) do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ } while (0) #endif #if !defined(TAILQ_INSERT_TAIL) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ } while (0) #endif #if !defined(TAILQ_NEXT) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #endif #if !defined(TAILQ_REMOVE) #define TAILQ_REMOVE(head, elm, field) do { \ if ((TAILQ_NEXT((elm), field)) != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ else { \ (head)->tqh_last = (elm)->field.tqe_prev; \ } \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ } while (0) #endif /* -------------------------------------------------------- * Structure definitions * -------------------------------------------------------- */ struct ini_private { struct ini_section *current; /* current working section */ int ignore; /* must ignore (no redefine) */ FILE *fp; /* file pointer to read */ /* Line buffer */ char *line; /* line data */ int linesize; /* line allocated size */ int lineno; /* number of line in file */ /* Error reporting */ char error[1024]; }; /* -------------------------------------------------------- * Prototypes * -------------------------------------------------------- */ #define F_VERBOSE(cg) ((cg)->flags & INI_VERBOSE) #define F_NOREDEFINE(cg) ((cg)->flags & INI_NOREDEFINE) #define F_FAILERROR(cg) ((cg)->flags & INI_FAILERROR) static char * sstrndup(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 void sskip(char **lp) { while (isspace (**lp) && **lp != '\0') ++(*lp); } static void sseek(char **lp) { while (!isspace(**lp) && **lp != '\0') ++(*lp); } static void ini_set_error(struct ini_config *cg, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (cg->pv->error) vsnprintf(cg->pv->error, sizeof (cg->pv->error), fmt, ap); va_end(ap); } static void ini_warn(struct ini_config *cg, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (F_VERBOSE(cg)) { vprintf(fmt, ap); fputc('\n', stdout); } if (F_FAILERROR(cg)) { va_start(ap, fmt); vsnprintf(cg->pv->error, sizeof (cg->pv->error), fmt, ap); va_end(ap); } va_end(ap); } /* * Add a new section, this function only returns -1 on fatal error that may * be impossible to continue parsing for the current line. */ static int ini_add_section(struct ini_config *cg, char **lp) { char *begin = *lp, *end; size_t length; struct ini_section *section = NULL; if (!(end = strchr(begin, ']'))) { ini_warn(cg, "line %d: syntax error after [", cg->pv->lineno); goto bad; } length = end - begin; /* May not redefine? */ if (cg->pv->current && F_NOREDEFINE(cg)) if (strncmp(cg->pv->current->key, begin, length) == 0) { ini_warn(cg, "line %d: redefining %s", cg->pv->lineno, cg->pv->current->key); goto bad; } /* Allocate a new section and add it */ if (!(section = calloc(1, sizeof (*section)))) goto bad; if (!(section->key = sstrndup(begin, length))) goto bad; TAILQ_INIT(§ion->options); TAILQ_INSERT_TAIL(&cg->sections, section, link); cg->pv->current = section; cg->pv->ignore = 0; /* Trigger an event for the new section */ if (cg->open) cg->open(cg->data, section->key); *lp = &end[1]; return 0; bad: if (section) free(section->key); free(section); cg->pv->ignore = 1; *lp = strchr(*lp, '\0'); return F_FAILERROR(cg) ? -1 : 0; } /* * Add a new option, the returns value is same as ini_add_section. */ static int ini_add_option(struct ini_config *cg, char **lp) { char *begin = *lp, *equal, *end; struct ini_option *option = NULL; if (!cg->pv->current) { ini_warn(cg, "line %d: option within no section", cg->pv->lineno); goto bad; } if (cg->pv->ignore || !(option = calloc(1, sizeof (*option)))) goto bad; /* Not valid */ if (!(equal = strchr(begin, '='))) { ini_warn(cg, "line %d: missing =", cg->pv->lineno); goto bad; } /* End of option */ end = begin; sskip(&end); sseek(&end); option->key = sstrndup(begin, end - begin); /* End of value */ begin = &equal[1]; sskip(&begin); end = begin; if (*end == '\'' || *end == '"') { for (++end; *end != *begin && *end != '\0'; ++end) continue; if (*end != *begin) { ini_warn(cg, "line %d: missing %c", cg->pv->lineno, *begin); *lp = end; goto bad; } else ++ begin; } else sseek(&end); option->value = sstrndup(begin, end - begin); if (!option->key || !option->value) { ini_warn(cg, "line %d: syntax error", cg->pv->lineno); goto bad; } TAILQ_INSERT_TAIL(&cg->pv->current->options, option, link); /* Trigger an event for the new section */ if (cg->get) cg->get(cg->data, cg->pv->current->key, option); *lp = &end[1]; return 0; bad: if (option) { free(option->key); free(option->value); } free(option); *lp = strchr(*lp, '\0'); return F_FAILERROR(cg) ? -1 : 0; } static int ini_read_line(struct ini_config *cg) { char *lp = cg->pv->line; int rv; /* Ignore empty line */ if (*lp == '\0' || *lp == '#' || *lp == ';') return 0; while (*lp != '\0') { sskip(&lp); if (*lp == '\0' || *lp == '#' || *lp == ';') return 0; /* Begin of section */ if (*lp == '[') { ++ lp; rv = ini_add_section(cg, &lp); /* Begin of option */ } else { rv = ini_add_option(cg, &lp); } if (rv < 0) return -1; } return 0; } /* * 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. Only for mode == INI_LINEAR */ static int ini_get_line(struct ini_config *cg) { int ch, pos; memset(cg->pv->line, 0, cg->pv->linesize); pos = 0; while ((ch = fgetc(cg->pv->fp)) != '\n') { if (feof(cg->pv->fp) || ferror(cg->pv->fp)) return -1; /* End of buffer, realloc */ if (pos == cg->pv->linesize) { cg->pv->line = realloc(cg->pv->line, cg->pv->linesize + 512); if (!cg->pv->line) return -1; cg->pv->linesize += 512; } cg->pv->line[pos++] = ch; } return 0; } static int ini_open_file(struct ini_config *cg) { if (!(cg->pv->fp = fopen(cg->path, "r"))) { ini_set_error(cg, "open: %s", cg->path); return -1; } cg->pv->linesize = INI_DEFAULT_LINESIZE; cg->pv->line = calloc(sizeof (char), cg->pv->linesize); if (!cg->pv->line) { ini_set_error(cg, "malloc: %s", strerror(errno)); return -1; } return 0; } /* -------------------------------------------------------- * Public functions * -------------------------------------------------------- */ struct ini_config * ini_create(const char *path, enum ini_flags flags) { struct ini_config *cg; cg = calloc(1, sizeof (*cg)); if (!cg) return NULL; cg->pv = calloc(1, sizeof (struct ini_private)); if (!cg->pv) return NULL; cg->path = path; cg->flags = flags; return cg; } void ini_set_handlers(struct ini_config *cg, void *data, ini_open_t open, ini_get_t get) { if (!cg) return; cg->data = data; cg->open = open; cg->get = get; } int ini_open(struct ini_config *cg) { int rv = 0; /* Open the file and prepare data for reading */ if (ini_open_file(cg) < 0) return -1; cg->pv->ignore = 1; cg->pv->lineno = 1; TAILQ_INIT(&cg->sections); while (ini_get_line(cg) != -1 && !rv) { rv = ini_read_line(cg); ++ cg->pv->lineno; } fclose(cg->pv->fp); return rv; } /* * Search the section `sectname' in the config structure. If the section * is not found, this function returns NULL. */ struct ini_section * ini_select_section(const struct ini_config *cg, const char *sectname) { struct ini_section *s; TAILQ_FOREACH(s, &cg->sections, link) if (strcmp(s->key, sectname) == 0) return s; return NULL; } /* * Search the option `optname' in the section structure, argument * section mustn't be NULL. If the option is not found, this function * returns NULL. */ struct ini_option * ini_select_option(const struct ini_section *sc, const char *name) { struct ini_option *o; TAILQ_FOREACH(o, &sc->options, link) if (strcmp(o->key, name) == 0) return o; return NULL; } /* * Find an option from the config, by section and option names. * Returns the option or NULL if not found. */ struct ini_option * ini_find(const struct ini_config *cg, const char *scname, const char *optname) { struct ini_section *sc; if ((sc = ini_select_section(cg, scname))) return ini_select_option(sc, optname); return NULL; } /* * Return the last error or "No error" if there is no error. */ const char * ini_get_error(const struct ini_config *cg) { if (!cg && errno != 0) return strerror(errno); if (cg->pv->error[0] == '\0') { if (errno != 0) return strerror(errno); return "No error"; } return cg->pv->error; } /* * Free everything, level may be set like this: * 0, only free parser and private resources. * 1, free all sections and their keys, * 2, free all sections and options keys and values. */ void ini_free(struct ini_config *cg, int level) { struct ini_section *s, *stmp; struct ini_option *o, *otmp; if (level >= 1) { TAILQ_FOREACH_SAFE(s, &cg->sections, link, stmp) { if (level >= 2) { TAILQ_FOREACH_SAFE(o, &s->options, link, otmp) { TAILQ_REMOVE(&s->options, o, link); free(o->key); free(o->value); free(o); } } TAILQ_REMOVE(&cg->sections, s, link); free(s->key); free(s); } } if (level >= 0) { free(cg->pv->line); free(cg->pv); free(cg); } }