/* 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 #include #include #include #include #include #include #include #include #include "error.h" #include "names.h" #include "atoms.h" #include "configparser.h" struct configiterator { FILE *f; unsigned int startline, line, column, markerline, markercolumn; char *filename; 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' expected (since line %u).\n", iter->filename, iter->line, fields[i].name, iter->startline); (void)finishfunc(privdata, *this, last, false, iter); *this = NULL; return RET_ERROR_MISSING; } r = finishfunc(privdata, *this, last, complete, iter); *this = NULL; return r; } retvalue configfile_parse(const char *filename, bool ignoreunknown, configinitfunction initfunc, configfinishfunction finishfunc, const struct configfield *fields, size_t fieldcount, void *privdata) { bool found[fieldcount]; void *last = NULL, *this = NULL; char key[100]; size_t keylen; int c, ret; size_t i; struct configiterator iter; retvalue result, r; iter.filename = calc_conffile(filename); if (FAILEDTOALLOC(iter.filename)) return RET_ERROR_OOM; 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); free(iter.filename); 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') { /* 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, true); if (RET_WAS_ERROR(r)) { result = r; break; } 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 == '\0') { 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, &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, !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); } free(iter.filename); return result; } 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, time_t *time_p) { time_t 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 { 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') { currentnumber *= 365*24*60*60; } else if (c == 'm') { currentnumber *= 31*24*60*60; } else if (c == 'd') { currentnumber *= 24*60*60; } else { 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, *fullvalue; retvalue r; r = config_getonlyword(iter, name, NULL, &value); if (RET_IS_OK(r)) { assert (value != NULL && value[0] != '\0'); if (value[0] != '/') { fullvalue = calc_dirconcat(global.confdir, value); free(value); } else fullvalue = value; if (FAILEDTOALLOC(fullvalue)) return RET_ERROR_OOM; *value_p = fullvalue; } 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>='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 == '!') { r = strlist_add_dup(&data, "!"); if (RET_WAS_ERROR(r)) return r; r = config_getscript(iter, name, &value); assert (r != 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; } /* 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 IFSTUPIDCC(=NULL); 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; }