diff options
Diffstat (limited to '')
-rw-r--r-- | configparser.c | 1532 |
1 files changed, 1532 insertions, 0 deletions
diff --git a/configparser.c b/configparser.c new file mode 100644 index 0000000..7528517 --- /dev/null +++ b/configparser.c @@ -0,0 +1,1532 @@ +/* This file is part of "reprepro" + * Copyright (C) 2007 Bernhard R. Link + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA + */ +#include <config.h> + +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> + +#include "error.h" +#include "names.h" +#include "atoms.h" +#include "filecntl.h" +#include "configparser.h" + +struct configiterator { + FILE *f; + unsigned int startline, line, column, markerline, markercolumn; + const char *filename; + const char *chunkname; + bool eol; +}; + +const char *config_filename(const struct configiterator *iter) { + return iter->filename; +} + +unsigned int config_line(const struct configiterator *iter) { + return iter->line; +} + +unsigned int config_column(const struct configiterator *iter) { + return iter->column; +} + +unsigned int config_firstline(const struct configiterator *iter) { + return iter->startline; +} + +unsigned int config_markerline(const struct configiterator *iter) { + return iter->markerline; +} + +unsigned int config_markercolumn(const struct configiterator *iter) { + return iter->markercolumn; +} + +void config_overline(struct configiterator *iter) { + int c; + + while (!iter->eol) { + c = fgetc(iter->f); + if (c == '#') { + do { + c = fgetc(iter->f); + } while (c != EOF && c != '\n'); + } + if (c == EOF || c == '\n') + iter->eol = true; + else + iter->column++; + } +} + +bool config_nextline(struct configiterator *iter) { + int c; + + assert (iter->eol); + c = fgetc(iter->f); + while (c == '#') { + do { + c = fgetc(iter->f); + } while (c != EOF && c != '\n'); + iter->line++; + c = fgetc(iter->f); + } + if (c == EOF) + return false; + if (c == ' ' || c == '\t') { + iter->line++; + iter->column = 1; + iter->eol = false; + return true; + } + (void)ungetc(c, iter->f); + return false; +} + +retvalue linkedlistfinish(UNUSED(void *privdata), void *this, void **last, UNUSED(bool complete), UNUSED(struct configiterator *dummy3)) { + *last = this; + return RET_NOTHING; +} + +static inline retvalue finishchunk(configfinishfunction finishfunc, void *privdata, struct configiterator *iter, const struct configfield *fields, size_t fieldcount, bool *found, void **this, void **last, bool complete) { + size_t i; + retvalue r; + + if (complete) + for (i = 0 ; i < fieldcount ; i++) { + if (!fields[i].required) + continue; + if (found[i]) + continue; + fprintf(stderr, +"Error parsing config file %s, line %u:\n" +"Required field '%s' not found in\n" +"%s starting in line %u and ending in line %u.\n", + iter->filename, iter->line, + fields[i].name, iter->chunkname, + iter->startline, iter->line-1); + (void)finishfunc(privdata, *this, last, false, iter); + *this = NULL; + return RET_ERROR_MISSING; + } + r = finishfunc(privdata, *this, last, complete, iter); + *this = NULL; + return r; +} + +char *configfile_expandname(const char *filename, char *fndup) { + const char *fromdir; + char *n; + + assert (fndup == NULL || fndup == filename); + + if (filename[0] == '/' || (filename[0] == '.' && filename[1] == '/')) + return fndup?fndup:strdup(filename); + if (filename[0] == '~' && filename[1] == '/') { + n = calc_dirconcat(getenv("HOME"), filename + 2); + free(fndup); + return n; + } + if (filename[0] != '+' || filename[1] == '\0' || filename[2] != '/') { + n = calc_dirconcat(global.confdir, filename); + free(fndup); + return n; + } + if (filename[1] == 'b') { + fromdir = global.basedir; + } else if (filename[1] == 'o') { + fromdir = global.outdir; + } else if (filename[1] == 'c') { + fromdir = global.confdir; + } else { + fprintf(stderr, "Warning: strange filename '%s'!\n", + filename); + return fndup?fndup:strdup(filename); + } + n = calc_dirconcat(fromdir, filename + 3); + free(fndup); + return n; +} + +static retvalue configfile_parse_multi(/*@only@*/char *, bool, configinitfunction, configfinishfunction, const char *, const struct configfield *, size_t, void *, int, void **, struct strlist *); + +static retvalue configfile_parse_single(/*@only@*/char *filename, bool ignoreunknown, configinitfunction initfunc, configfinishfunction finishfunc, const char *chunkname, const struct configfield *fields, size_t fieldcount, void *privdata, int depth, void **last_p, struct strlist *filenames) { + bool found[fieldcount]; + void *this = NULL; + char key[100]; + size_t keylen; + int c, ret; + size_t i; + struct configiterator iter; + retvalue result, r; + bool afterinclude = false; + + if (strlist_in(filenames, filename)) { + if (verbose >= 0) { + fprintf(stderr, +"Ignoring subsequent inclusion of '%s'!\n", filename); + } + free(filename); + return RET_NOTHING; + } + iter.filename = filename; + r = strlist_add(filenames, filename); + if (RET_WAS_ERROR(r)) + return r; + iter.chunkname = chunkname; + iter.line = 0; + iter.column = 0; + + iter.f = fopen(iter.filename, "r"); + if (iter.f == NULL) { + int e = errno; + fprintf(stderr, "Error opening config file '%s': %s(%d)\n", + iter.filename, strerror(e), e); + return RET_ERRNO(e); + } + result = RET_NOTHING; + do { + iter.line++; + iter.column = 1; + + c = fgetc(iter.f); + while (c == '#') { + do { + c = fgetc(iter.f); + } while (c != EOF && c != '\n'); + iter.line++; + c = fgetc(iter.f); + } + if (c == '\r') { + do { + c = fgetc(iter.f); + } while (c == '\r'); + if (c != EOF && c != '\n') { + fprintf(stderr, +"%s:%u: error parsing configuration file: CR without following LF!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + } + if (c == EOF) + break; + if (c == '\n') { + afterinclude = false; + /* Ignore multiple emptye lines */ + if (this == NULL) + continue; + /* finish this chunk, to get ready for the next: */ + r = finishchunk(finishfunc, privdata, &iter, + fields, fieldcount, found, + &this, last_p, true); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + continue; + } + if (afterinclude) { + fprintf(stderr, +"Warning parsing %s, line %u: no empty line after '!include'-sequence" +" might cause ambiguity in the future!\n", + iter.filename, iter.line); + afterinclude = false; + } + if (c == '!') { + keylen = 0; + while ((c = fgetc(iter.f)) != EOF && c >= 'a' && c <= 'z') { + iter.column++; + key[keylen++] = c; + if (keylen >= 10) + break; + } + if (c != ':') { + fprintf(stderr, +"Error parsing %s, line %u: invalid !-sequence!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + iter.column++; + if (keylen == 7 && memcmp(key, "include", 7) == 0) { + char *filetoinclude; + + if (this != NULL) { + fprintf(stderr, +"Error parsing %s, line %u: '!include' statement within unterminated %s!\n" +"(perhaps you forgot to put an empty line before this)\n", + iter.filename, iter.line, + chunkname); + result = RET_ERROR; + break; + } + if (depth > 20) { + fprintf(stderr, +"Error parsing %s, line %u: too many nested '!include' statements!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + r = config_getonlyword(&iter, "!include", + NULL, &filetoinclude); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + filetoinclude = configfile_expandname( + filetoinclude, filetoinclude); + r = configfile_parse_multi(filetoinclude, + ignoreunknown, + initfunc, finishfunc, + chunkname, + fields, fieldcount, + privdata, depth + 1, + last_p, filenames); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + afterinclude = true; + } else { + key[keylen] = '\0'; + fprintf(stderr, +"Error parsing %s, line %u: unknown !-sequence '%s'!\n", + iter.filename, iter.line, key); + result = RET_ERROR; + break; + } + /* ignore all data left of this field */ + do { + config_overline(&iter); + } while (config_nextline(&iter)); + continue; + } + if (c == '\0') { + fprintf(stderr, +"Error parsing %s, line %u: \\000 character not allowed in config files!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + if (c == ' ' || c == '\t') { + fprintf(stderr, +"Error parsing %s, line %u: unexpected white space before keyword!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + key[0] = c; + keylen = 1; + + while ((c = fgetc(iter.f)) != EOF && c != ':' && c != '\n' + && c != '#' && c != '\0') { + iter.column++; + if (c == ' ') { + fprintf(stderr, +"Error parsing %s, line %u: Unexpected space in header name!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + if (c == '\t') { + fprintf(stderr, +"Error parsing %s, line %u: Unexpected tabulator character in header name!\n", + iter.filename, iter.line); + result = RET_ERROR; + break; + } + key[keylen++] = c; + if (keylen >= 100) + break; + } + if (c != ':') { + if (c != ' ' && c != '\t') + /* newline or end-of-file */ + fprintf(stderr, +"Error parsing %s, line %u, column %u: Colon expected!\n", + iter.filename, iter.line, iter.column); + result = RET_ERROR; + break; + } + if (this == NULL) { + /* new chunk, initialize everything */ + r = initfunc(privdata, *last_p, &this); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + result = r; + break; + } + assert (this != NULL); + iter.startline = iter.line; + memset(found, 0, sizeof(found)); + } + for (i = 0 ; i < fieldcount ; i++) { + if (keylen != fields[i].namelen) + continue; + if (strncasecmp(key, fields[i].name, keylen) != 0) + continue; + break; + } + if (i >= fieldcount) { + key[keylen] = '\0'; + if (!ignoreunknown) { + fprintf(stderr, +"Error parsing %s, line %u: Unknown header '%s'!\n", + iter.filename, iter.line, key); + result = RET_ERROR_UNKNOWNFIELD; + break; + } + if (verbose >= 0) + fprintf(stderr, +"Warning parsing %s, line %u: Unknown header '%s'!\n", + iter.filename, iter.line, key); + } else if (found[i]) { + fprintf(stderr, +"Error parsing %s, line %u: Second appearance of '%s' in the same chunk!\n", + iter.filename, iter.line, fields[i].name); + result = RET_ERROR; + break; + } else + found[i] = true; + do { + c = fgetc(iter.f); + iter.column++; + } while (c == ' ' || c == '\t'); + (void)ungetc(c, iter.f); + + iter.eol = false; + if (i < fieldcount) { + r = fields[i].setfunc(privdata, fields[i].name, this, &iter); + RET_UPDATE(result, r); + if (RET_WAS_ERROR(r)) + break; + } + /* ignore all data left of this field */ + do { + config_overline(&iter); + } while (config_nextline(&iter)); + } while (true); + if (this != NULL) { + r = finishchunk(finishfunc, privdata, &iter, + fields, fieldcount, found, + &this, last_p, + !RET_WAS_ERROR(result)); + RET_UPDATE(result, r); + } + if (ferror(iter.f) != 0) { + int e = errno; + fprintf(stderr, "Error reading config file '%s': %s(%d)\n", + iter.filename, strerror(e), e); + r = RET_ERRNO(e); + RET_UPDATE(result, r); + } + ret = fclose(iter.f); + if (ret != 0) { + int e = errno; + fprintf(stderr, "Error closing config file '%s': %s(%d)\n", + iter.filename, strerror(e), e); + r = RET_ERRNO(e); + RET_UPDATE(result, r); + } + return result; +} + +static retvalue configfile_parse_multi(/*@only@*/char *fullfilename, bool ignoreunknown, configinitfunction initfunc, configfinishfunction finishfunc, const char *chunkname, const struct configfield *fields, size_t fieldcount, void *privdata, int depth, void **last_p, struct strlist *filenames) { + retvalue result = RET_NOTHING, r; + + if (isdirectory(fullfilename)) { + DIR *dir; + struct dirent *de; + int e; + char *subfilename; + + dir = opendir(fullfilename); + if (dir == NULL) { + e = errno; + fprintf(stderr, +"Error %d opening directory '%s': %s\n", + e, fullfilename, strerror(e)); + free(fullfilename); + return RET_ERRNO(e); + } + while ((errno = 0, de = readdir(dir)) != NULL) { + size_t l; + if (de->d_type != DT_REG && de->d_type != DT_LNK + && de->d_type != DT_UNKNOWN) + continue; + if (de->d_name[0] == '.') + continue; + l = strlen(de->d_name); + if (l < 5 || strcmp(de->d_name + l - 5, ".conf") != 0) + continue; + subfilename = calc_dirconcat(fullfilename, de->d_name); + if (FAILEDTOALLOC(subfilename)) { + (void)closedir(dir); + free(fullfilename); + return RET_ERROR_OOM; + } + r = configfile_parse_single(subfilename, ignoreunknown, + initfunc, finishfunc, + chunkname, fields, fieldcount, privdata, + depth, last_p, filenames); + RET_UPDATE(result, r); + if (RET_WAS_ERROR(r)) { + (void)closedir(dir); + free(fullfilename); + return r; + } + } + e = errno; + if (e != 0) { + (void)closedir(dir); + fprintf(stderr, +"Error %d reading directory '%s': %s\n", + e, fullfilename, strerror(e)); + free(fullfilename); + return RET_ERRNO(e); + } + if (closedir(dir) != 0) { + e = errno; + fprintf(stderr, +"Error %d closing directory '%s': %s\n", + e, fullfilename, strerror(e)); + free(fullfilename); + return RET_ERRNO(e); + } + free(fullfilename); + } else { + r = configfile_parse_single(fullfilename, ignoreunknown, + initfunc, finishfunc, + chunkname, fields, fieldcount, privdata, + depth, last_p, filenames); + RET_UPDATE(result, r); + } + return result; +} + +retvalue configfile_parse(const char *filename, bool ignoreunknown, configinitfunction initfunc, configfinishfunction finishfunc, const char *chunkname, const struct configfield *fields, size_t fieldcount, void *privdata) { + struct strlist filenames; + void *last = NULL; + retvalue r; + char *fullfilename; + + fullfilename = configfile_expandname(filename, NULL); + if (fullfilename == NULL) + return RET_ERROR_OOM; + + strlist_init(&filenames); + + r = configfile_parse_multi(fullfilename, ignoreunknown, + initfunc, finishfunc, + chunkname, fields, fieldcount, privdata, + 0, &last, &filenames); + + /* only free filenames last, as they might still be + * referenced while running */ + strlist_done(&filenames); + return r; +} + +static inline int config_nextchar(struct configiterator *iter) { + int c; + unsigned int realcolumn; + + c = fgetc(iter->f); + realcolumn = iter->column + 1; + if (c == '#') { + do { + c = fgetc(iter->f); + realcolumn++; + } while (c != '\n' && c != EOF && c != '\r'); + } + if (c == '\r') { + while (c == '\r') { + realcolumn++; + c = fgetc(iter->f); + } + if (c != '\n' && c != EOF) { + fprintf(stderr, +"Warning parsing config file '%s', line '%u', column %u: CR not followed by LF!\n", + config_filename(iter), + config_line(iter), + realcolumn); + + } + } + if (c == EOF) { + fprintf(stderr, +"Warning parsing config file '%s', line '%u': File ending without final LF!\n", + config_filename(iter), + config_line(iter)); + /* fake a proper text file: */ + c = '\n'; + } + iter->column++; + if (c == '\n') + iter->eol = true; + return c; +} + +static inline int config_nextnonspace(struct configiterator *iter) { + int c; + + do { + iter->markerline = iter->line; + iter->markercolumn = iter->column; + if (iter->eol) { + if (!config_nextline(iter)) + return EOF; + } + c = config_nextchar(iter); + } while (c == '\n' || c == ' ' || c == '\t'); + return c; +} + +int config_nextnonspaceinline(struct configiterator *iter) { + int c; + + do { + iter->markerline = iter->line; + iter->markercolumn = iter->column; + if (iter->eol) + return EOF; + c = config_nextchar(iter); + if (c == '\n') + return EOF; + } while (c == '\r' || c == ' ' || c == '\t'); + return c; +} + +#define configparser_errorlast(iter, message, ...) \ + fprintf(stderr, "Error parsing %s, line %u, column %u: " message "\n", \ + iter->filename, iter->markerline, \ + iter->markercolumn, ## __VA_ARGS__); +#define configparser_error(iter, message, ...) \ + fprintf(stderr, "Error parsing %s, line %u, column %u: " message "\n", \ + iter->filename, iter->line, \ + iter->column, ## __VA_ARGS__); + +retvalue config_completeword(struct configiterator *iter, char firstc, char **result_p) { + size_t size = 0, len = 0; + char *value = NULL, *nv; + int c = firstc; + + iter->markerline = iter->line; + iter->markercolumn = iter->column; + do { + if (len + 2 >= size) { + nv = realloc(value, size+128); + if (FAILEDTOALLOC(nv)) { + free(value); + return RET_ERROR_OOM; + } + size += 128; + value = nv; + } + value[len] = c; + len++; + c = config_nextchar(iter); + if (c == '\n') + break; + } while (c != ' ' && c != '\t'); + assert (len > 0); + assert (len < size); + value[len] = '\0'; + nv = realloc(value, len+1); + if (nv == NULL) + *result_p = value; + else + *result_p = nv; + return RET_OK; +} + +retvalue config_getwordinline(struct configiterator *iter, char **result_p) { + int c; + + c = config_nextnonspaceinline(iter); + if (c == EOF) + return RET_NOTHING; + return config_completeword(iter, c, result_p); +} + +retvalue config_getword(struct configiterator *iter, char **result_p) { + int c; + + c = config_nextnonspace(iter); + if (c == EOF) + return RET_NOTHING; + return config_completeword(iter, c, result_p); +} + +retvalue config_gettimespan(struct configiterator *iter, const char *header, unsigned long *time_p) { + long long currentnumber, currentsum = 0; + bool empty = true; + int c; + + do { + c = config_nextnonspace(iter); + if (c == EOF) { + if (empty) { + configparser_errorlast(iter, +"Unexpected end of %s header (value expected).", header); + return RET_ERROR; + } + *time_p = currentsum; + return RET_OK; + } + iter->markerline = iter->line; + iter->markercolumn = iter->column; + currentnumber = 0; + if (c < '0' || c > '9') { + configparser_errorlast(iter, +"Unexpected character '%c' where a digit was expected in %s header.", + (char)c, header); + return RET_ERROR; + } + empty = false; + do { + if (currentnumber > 3660) { + configparser_errorlast(iter, +"Absurdly long time span (> 100 years) in %s header.", header); + return RET_ERROR; + } + currentnumber *= 10; + currentnumber += (c - '0'); + c = config_nextchar(iter); + } while (c >= '0' && c <= '9'); + if (c == ' ' || c == '\t' || c == '\n') + c = config_nextnonspace(iter); + if (c == 'y') { + if (currentnumber > 100) { + configparser_errorlast(iter, +"Absurdly long time span (> 100 years) in %s header.", header); + return RET_ERROR; + } + currentnumber *= 365*24*60*60; + } else if (c == 'm') { + if (currentnumber > 1200) { + configparser_errorlast(iter, +"Absurdly long time span (> 100 years) in %s header.", header); + return RET_ERROR; + } + currentnumber *= 31*24*60*60; + } else if (c == 'd') { + if (currentnumber > 36600) { + configparser_errorlast(iter, +"Absurdly long time span (> 100 years) in %s header.", header); + return RET_ERROR; + } + currentnumber *= 24*60*60; + } else { + if (currentnumber > 36600) { + configparser_errorlast(iter, +"Absurdly long time span (> 100 years) in %s header.", header); + return RET_ERROR; + } + currentnumber *= 24*60*60; + if (c != EOF) { + configparser_errorlast(iter, +"Unexpected character '%c' where a 'd','m' or 'y' was expected in %s header.", + (char)c, header); + return RET_ERROR; + } + } + currentsum += currentnumber; + } while (true); +} + +retvalue config_getonlyword(struct configiterator *iter, const char *header, checkfunc check, char **result_p) { + char *value; + retvalue r; + + r = config_getword(iter, &value); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected end of %s header (value expected).", header); + return RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + if (config_nextnonspace(iter) != EOF) { + configparser_error(iter, +"End of %s header expected (but trailing garbage).", header); + free(value); + return RET_ERROR; + } + if (check != NULL) { + const char *errormessage = check(value); + if (errormessage != NULL) { + configparser_errorlast(iter, +"Malformed %s content '%s': %s", header, value, errormessage); + free(value); + checkerror_free(errormessage); + return RET_ERROR; + } + } + *result_p = value; + return RET_OK; +} + +retvalue config_getscript(struct configiterator *iter, const char *name, char **value_p) { + char *value; + retvalue r; + + r = config_getonlyword(iter, name, NULL, &value); + if (RET_IS_OK(r)) { + assert (value != NULL && value[0] != '\0'); + value = configfile_expandname(value, value); + if (FAILEDTOALLOC(value)) + return RET_ERROR_OOM; + *value_p = value; + } + return r; +} + +retvalue config_geturl(struct configiterator *iter, const char *header, char **result_p) { + char *value, *p; + retvalue r; + size_t l; + + + r = config_getword(iter, &value); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected end of %s header (value expected).", header); + return RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + // TODO: think about allowing (escaped) spaces... + if (config_nextnonspace(iter) != EOF) { + configparser_error(iter, +"End of %s header expected (but trailing garbage).", header); + free(value); + return RET_ERROR; + } + p = value; + while (*p != '\0' && (*p == '_' || *p == '-' || *p == '+' || + (*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || + (*p>='0' && *p<='9'))) { + p++; + } + if (*p != ':') { + configparser_errorlast(iter, +"Malformed %s field: no colon (must be method:path).", header); + free(value); + return RET_ERROR; + } + if (p == value) { + configparser_errorlast(iter, +"Malformed %s field: transport method name expected (colon is not allowed to be the first character)!", header); + free(value); + return RET_ERROR; + } + p++; + l = strlen(p); + /* remove one leading slash, as we always add one and some apt-methods + * are confused with //. (end with // if you really want it) */ + if (l > 0 && p[l - 1] == '/') + p[l - 1] = '\0'; + *result_p = value; + return RET_OK; +} + +retvalue config_getuniqwords(struct configiterator *iter, const char *header, checkfunc check, struct strlist *result_p) { + char *value; + retvalue r; + struct strlist data; + const char *errormessage; + + strlist_init(&data); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + if (strlist_in(&data, value)) { + configparser_errorlast(iter, +"Unexpected duplicate '%s' within %s header.", value, header); + free(value); + strlist_done(&data); + return RET_ERROR; + } else if (check != NULL && (errormessage = check(value)) != NULL) { + configparser_errorlast(iter, +"Malformed %s element '%s': %s", header, value, errormessage); + checkerror_free(errormessage); + free(value); + strlist_done(&data); + return RET_ERROR; + } else { + r = strlist_add(&data, value); + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + } + } + strlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getinternatomlist(struct configiterator *iter, const char *header, enum atom_type type, checkfunc check, struct atomlist *result_p) { + char *value; + retvalue r; + struct atomlist data; + const char *errormessage; + atom_t atom; + + atomlist_init(&data); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + if (check != NULL && (errormessage = check(value)) != NULL) { + configparser_errorlast(iter, +"Malformed %s element '%s': %s", header, value, errormessage); + checkerror_free(errormessage); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + r = atom_intern(type, value, &atom); + if (RET_WAS_ERROR(r)) + return r; + r = atomlist_add_uniq(&data, atom); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected duplicate '%s' within %s header.", value, header); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + free(value); + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + } + atomlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getatom(struct configiterator *iter, const char *header, enum atom_type type, atom_t *result_p) { + char *value; + retvalue r; + atom_t atom; + + r = config_getword(iter, &value); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected empty '%s' field.", header); + r = RET_ERROR_MISSING; + } + if (RET_WAS_ERROR(r)) + return r; + atom = atom_find(type, value); + if (!atom_defined(atom)) { + configparser_errorlast(iter, +"Not previously seen %s '%s' within '%s' field.", atomtypes[type], value, header); + free(value); + return RET_ERROR; + } + *result_p = atom; + free(value); + return RET_OK; +} + +retvalue config_getatomlist(struct configiterator *iter, const char *header, enum atom_type type, struct atomlist *result_p) { + char *value; + retvalue r; + struct atomlist data; + atom_t atom; + + atomlist_init(&data); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + atom = atom_find(type, value); + if (!atom_defined(atom)) { + configparser_errorlast(iter, +"Not previously seen %s '%s' within '%s' header.", atomtypes[type], value, header); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + r = atomlist_add_uniq(&data, atom); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected duplicate '%s' within %s header.", value, header); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + free(value); + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + } + atomlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getsplitatoms(struct configiterator *iter, const char *header, enum atom_type type, struct atomlist *from_p, struct atomlist *into_p) { + char *value, *separator; + atom_t origin, destination; + retvalue r; + struct atomlist data_from, data_into; + + atomlist_init(&data_from); + atomlist_init(&data_into); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + atomlist_done(&data_from); + atomlist_done(&data_into); + return r; + } + separator = strchr(value, '>'); + if (separator == NULL) { + separator = value; + destination = atom_find(type, value); + origin = destination;; + } else if (separator == value) { + destination = atom_find(type, separator + 1); + origin = destination;; + } else if (separator[1] == '\0') { + *separator = '\0'; + separator = value; + destination = atom_find(type, value); + origin = destination;; + } else { + *separator = '\0'; + separator++; + origin = atom_find(type, value); + destination = atom_find(type, separator); + } + if (!atom_defined(origin)) { + configparser_errorlast(iter, +"Unknown %s '%s' in %s.", atomtypes[type], value, header); + free(value); + atomlist_done(&data_from); + atomlist_done(&data_into); + return RET_ERROR; + } + if (!atom_defined(destination)) { + configparser_errorlast(iter, +"Unknown %s '%s' in %s.", atomtypes[type], separator, header); + free(value); + atomlist_done(&data_from); + atomlist_done(&data_into); + return RET_ERROR; + } + free(value); + r = atomlist_add(&data_from, origin); + if (RET_WAS_ERROR(r)) { + atomlist_done(&data_from); + atomlist_done(&data_into); + return r; + } + r = atomlist_add(&data_into, destination); + if (RET_WAS_ERROR(r)) { + atomlist_done(&data_from); + atomlist_done(&data_into); + return r; + } + } + atomlist_move(from_p, &data_from); + atomlist_move(into_p, &data_into); + return RET_OK; +} + +retvalue config_getatomsublist(struct configiterator *iter, const char *header, enum atom_type type, struct atomlist *result_p, const struct atomlist *superset, const char *superset_header) { + char *value; + retvalue r; + struct atomlist data; + atom_t atom; + + atomlist_init(&data); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + atom = atom_find(type, value); + if (!atom_defined(atom) || !atomlist_in(superset, atom)) { + configparser_errorlast(iter, +"'%s' not allowed in %s as it was not in %s.", value, header, superset_header); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + r = atomlist_add_uniq(&data, atom); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected duplicate '%s' within %s header.", value, header); + free(value); + atomlist_done(&data); + return RET_ERROR; + } + free(value); + if (RET_WAS_ERROR(r)) { + atomlist_done(&data); + return r; + } + } + atomlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getwords(struct configiterator *iter, struct strlist *result_p) { + char *value; + retvalue r; + struct strlist data; + + strlist_init(&data); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + r = strlist_add(&data, value); + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + } + strlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getsignwith(struct configiterator *iter, const char *name, struct strlist *result_p) { + char *value; + retvalue r; + struct strlist data; + int c; + + strlist_init(&data); + + c = config_nextnonspace(iter); + if (c == EOF) { + configparser_errorlast(iter, +"Missing value for %s field.", name); + return RET_ERROR; + } + /* if the first character is a '!', a script to start follows */ + if (c == '!') { + const char *type = "!"; + + iter->markerline = iter->line; + iter->markercolumn = iter->column; + c = config_nextchar(iter); + if (c == '-') { + configparser_errorlast(iter, +"'!-' in signwith lines reserved for future usage!\n"); + return RET_ERROR; + type = "!-"; + c = config_nextnonspace(iter); + } else if (c == '\n' || c == ' ' || c == '\t') + c = config_nextnonspace(iter); + if (c == EOF) { + configparser_errorlast(iter, +"Missing value for %s field.", name); + return RET_ERROR; + } + r = config_completeword(iter, c, &value); + if (RET_WAS_ERROR(r)) + return r; + if (config_nextnonspace(iter) != EOF) { + configparser_error(iter, +"End of %s header expected (but trailing garbage).", name); + free(value); + return RET_ERROR; + } + assert (value != NULL && value[0] != '\0'); + value = configfile_expandname(value, value); + if (FAILEDTOALLOC(value)) + return RET_ERROR_OOM; + r = strlist_add_dup(&data, type); + if (RET_WAS_ERROR(r)) { + free(value); + return r; + } + r = strlist_add(&data, value); + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + strlist_move(result_p, &data); + return RET_OK; + } + /* otherwise each word is stored in the strlist */ + r = config_completeword(iter, c, &value); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + r = strlist_add(&data, value); + if (RET_WAS_ERROR(r)) + return r; + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + r = strlist_add(&data, value); + if (RET_WAS_ERROR(r)) { + strlist_done(&data); + return r; + } + } + strlist_move(result_p, &data); + return RET_OK; +} + +retvalue config_getsplitwords(struct configiterator *iter, UNUSED(const char *header), struct strlist *from_p, struct strlist *into_p) { + char *value, *origin, *destination, *separator; + retvalue r; + struct strlist data_from, data_into; + + strlist_init(&data_from); + strlist_init(&data_into); + while ((r = config_getword(iter, &value)) != RET_NOTHING) { + if (RET_WAS_ERROR(r)) { + strlist_done(&data_from); + strlist_done(&data_into); + return r; + } + separator = strchr(value, '>'); + if (separator == NULL) { + destination = strdup(value); + origin = value; + } else if (separator == value) { + destination = strdup(separator+1); + origin = strdup(separator+1); + free(value); + } else if (separator[1] == '\0') { + *separator = '\0'; + destination = strdup(value); + origin = value; + } else { + origin = strndup(value, separator-value); + destination = strdup(separator+1); + free(value); + } + if (FAILEDTOALLOC(origin) || FAILEDTOALLOC(destination)) { + free(origin); free(destination); + strlist_done(&data_from); + strlist_done(&data_into); + return RET_ERROR_OOM; + } + r = strlist_add(&data_from, origin); + if (RET_WAS_ERROR(r)) { + free(destination); + strlist_done(&data_from); + strlist_done(&data_into); + return r; + } + r = strlist_add(&data_into, destination); + if (RET_WAS_ERROR(r)) { + strlist_done(&data_from); + strlist_done(&data_into); + return r; + } + } + strlist_move(from_p, &data_from); + strlist_move(into_p, &data_into); + return RET_OK; +} + +retvalue config_getconstant(struct configiterator *iter, const struct constant *constants, int *result_p) { + retvalue r; + char *value; + const struct constant *c; + + /* that could be done more in-situ, + * but is not runtime-critical at all */ + + r = config_getword(iter, &value); + if (r == RET_NOTHING) + return r; + if (RET_WAS_ERROR(r)) + return r; + for (c = constants ; c->name != NULL ; c++) { + if (strcmp(c->name, value) == 0) { + free(value); + *result_p = c->value; + return RET_OK; + } + } + free(value); + return RET_ERROR_UNKNOWNFIELD; +} + +retvalue config_getflags(struct configiterator *iter, const char *header, const struct constant *constants, bool *flags, bool ignoreunknown, const char *msg) { + retvalue r, result = RET_NOTHING; + int option = -1; + + while (true) { + r = config_getconstant(iter, constants, &option); + if (r == RET_NOTHING) + break; + if (r == RET_ERROR_UNKNOWNFIELD) { +// TODO: would be nice to have the wrong flag here to put it in the error message: + if (ignoreunknown) { + fprintf(stderr, +"Warning: ignored error parsing config file %s, line %u, column %u:\n" +"Unknown flag in %s header.%s\n", + config_filename(iter), + config_markerline(iter), + config_markercolumn(iter), + header, msg); + continue; + } + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Unknown flag in %s header.%s\n", + config_filename(iter), + config_markerline(iter), + config_markercolumn(iter), + header, msg); + } + if (RET_WAS_ERROR(r)) + return r; + assert (option >= 0); + flags[option] = true; + result = RET_OK; + option = -1; + } + return result; +} + +retvalue config_getall(struct configiterator *iter, char **result_p) { + size_t size = 0, len = 0; + char *value = NULL, *nv; + int c; + + c = config_nextnonspace(iter); + if (c == EOF) + return RET_NOTHING; + iter->markerline = iter->line; + iter->markercolumn = iter->column; + do { + if (len + 2 >= size) { + nv = realloc(value, size+128); + if (FAILEDTOALLOC(nv)) { + free(value); + return RET_ERROR_OOM; + } + size += 128; + value = nv; + } + value[len] = c; + len++; + if (iter->eol) { + if (!config_nextline(iter)) + break; + } + c = config_nextchar(iter); + } while (true); + assert (len > 0); + assert (len < size); + while (len > 0 && (value[len-1] == ' ' || value[len-1] == '\t' || + value[len-1] == '\n' || value[len-1] == '\r')) + len--; + value[len] = '\0'; + nv = realloc(value, len+1); + if (nv == NULL) + *result_p = value; + else + *result_p = nv; + return RET_OK; +} + +retvalue config_gettruth(struct configiterator *iter, const char *header, bool *result_p) { + char *value = NULL; + retvalue r; + + /* wastefull, but does not happen that often */ + + r = config_getword(iter, &value); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected empty boolean %s header (something like Yes or No expected).", header); + return RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + // TODO: check against trailing garbage + if (strcasecmp(value, "Yes") == 0) { + *result_p = true; + free(value); + return RET_OK; + } + if (strcasecmp(value, "No") == 0) { + *result_p = false; + free(value); + return RET_OK; + } + if (strcmp(value, "1") == 0) { + *result_p = true; + free(value); + return RET_OK; + } + if (strcmp(value, "0") == 0) { + *result_p = false; + free(value); + return RET_OK; + } + configparser_errorlast(iter, +"Unexpected value in boolean %s header (something like Yes or No expected).", header); + free(value); + return RET_ERROR; +} + +retvalue config_getnumber(struct configiterator *iter, const char *name, long long *result_p, long long minval, long long maxval) { + char *word = NULL; + retvalue r; + long long value; + char *e; + + r = config_getword(iter, &word); + if (r == RET_NOTHING) { + configparser_errorlast(iter, +"Unexpected end of line (%s number expected).", name); + return RET_ERROR; + } + if (RET_WAS_ERROR(r)) + return r; + + value = strtoll(word, &e, 10); + if (e == word) { + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Expected %s number but got '%s'\n", + config_filename(iter), config_markerline(iter), + config_markercolumn(iter), name, word); + free(word); + return RET_ERROR; + } + if (e != NULL && *e != '\0') { + unsigned char digit1, digit2, digit3; + digit1 = ((unsigned char)(*e))&0x7; + digit2 = (((unsigned char)(*e)) >> 3)&0x7; + digit3 = (((unsigned char)(*e)) >> 6)&0x7; + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Unexpected character \\%01hhu%01hhu%01hhu in %s number '%s'\n", + config_filename(iter), config_markerline(iter), + config_markercolumn(iter) + (int)(e-word), + digit3, digit2, digit1, + name, word); + free(word); + return RET_ERROR; + } + if (value == LLONG_MAX || value > maxval) { + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Too large %s number '%s'\n", + config_filename(iter), config_markerline(iter), + config_markercolumn(iter), name, word); + free(word); + return RET_ERROR; + } + if (value == LLONG_MIN || value < minval) { + fprintf(stderr, +"Error parsing config file %s, line %u, column %u:\n" +"Too small %s number '%s'\n", + config_filename(iter), config_markerline(iter), + config_markercolumn(iter), name, word); + free(word); + return RET_ERROR; + } + free(word); + *result_p = value; + return RET_OK; +} + +static retvalue config_getline(struct configiterator *iter, /*@out@*/char **result_p) { + size_t size = 0, len = 0; + char *value = NULL, *nv; + int c; + + c = config_nextnonspace(iter); + if (c == EOF) + return RET_NOTHING; + iter->markerline = iter->line; + iter->markercolumn = iter->column; + do { + if (len + 2 >= size) { + nv = realloc(value, size+128); + if (FAILEDTOALLOC(nv)) { + free(value); + return RET_ERROR_OOM; + } + size += 128; + value = nv; + } + value[len] = c; + len++; + c = config_nextchar(iter); + } while (c != '\n'); + assert (len > 0); + assert (len < size); + while (len > 0 && (value[len-1] == ' ' || value[len-1] == '\t' + || value[len-1] == '\r')) + len--; + assert (len > 0); + value[len] = '\0'; + nv = realloc(value, len+1); + if (nv == NULL) + *result_p = value; + else + *result_p = nv; + return RET_OK; +} + +retvalue config_getlines(struct configiterator *iter, struct strlist *result) { + char *line; + struct strlist list; + retvalue r; + + strlist_init(&list); + do { + r = config_getline(iter, &line); + if (RET_WAS_ERROR(r)) { + strlist_done(&list); + return r; + } + if (r == RET_NOTHING) + r = strlist_add_dup(&list, ""); + else + r = strlist_add(&list, line); + if (RET_WAS_ERROR(r)) { + strlist_done(&list); + return r; + } + } while (config_nextline(iter)); + strlist_move(result, &list); + return RET_OK; +} |