/* * Part: Configuration file parser/reader. Place into the dynamic * data structure representation the conf file * * Version: $Id: parser.c,v 1.0.3 2003/05/11 02:28:03 acassen Exp $ * * Author: Alexandre Cassen, * * 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. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include "vector.h" #include "config.h" #include "parser.h" #include "memory.h" #include "debug.h" /* local vars */ static int sublevel = 0; static int line_nr; int keyword_alloc(vector keywords, char *string, int (*handler) (struct config *, vector), int (*print) (struct config *, char *, int, const void*), int unique) { struct keyword *keyword; keyword = (struct keyword *) MALLOC(sizeof (struct keyword)); if (!keyword) return 1; if (!vector_alloc_slot(keywords)) { FREE(keyword); return 1; } keyword->string = string; keyword->handler = handler; keyword->print = print; keyword->unique = unique; vector_set_slot(keywords, keyword); return 0; } void install_sublevel(void) { sublevel++; } void install_sublevel_end(void) { sublevel--; } int _install_keyword(vector keywords, char *string, int (*handler) (struct config *, vector), int (*print) (struct config *, char *, int, const void*), int unique) { int i = 0; struct keyword *keyword; /* fetch last keyword */ keyword = VECTOR_LAST_SLOT(keywords); if (!keyword) return 1; /* position to last sub level */ for (i = 0; i < sublevel; i++) { keyword = VECTOR_LAST_SLOT(keyword->sub); if (!keyword) return 1; } /* First sub level allocation */ if (!keyword->sub) keyword->sub = vector_alloc(); if (!keyword->sub) return 1; /* add new sub keyword */ return keyword_alloc(keyword->sub, string, handler, print, unique); } void free_keywords(vector keywords) { struct keyword *keyword; int i; if (!keywords) return; for (i = 0; i < VECTOR_SIZE(keywords); i++) { keyword = VECTOR_SLOT(keywords, i); if (keyword->sub) free_keywords(keyword->sub); FREE(keyword); } vector_free(keywords); } struct keyword * find_keyword(vector keywords, vector v, char * name) { struct keyword *keyword; int i; size_t len; if (!name || !keywords) return NULL; if (!v) v = keywords; len = strlen(name); for (i = 0; i < VECTOR_SIZE(v); i++) { keyword = VECTOR_SLOT(v, i); if ((strlen(keyword->string) == len) && !strcmp(keyword->string, name)) return keyword; if (keyword->sub) { keyword = find_keyword(keywords, keyword->sub, name); if (keyword) return keyword; } } return NULL; } int snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw, const void *data) { int r; int fwd = 0; char *f = fmt; struct config *conf; if (!kw || !kw->print) return 0; do { if (fwd == len || *f == '\0') break; if (*f != '%') { *(buff + fwd) = *f; fwd++; continue; } f++; switch(*f) { case 'k': fwd += snprintf(buff + fwd, len - fwd, "%s", kw->string); break; case 'v': conf = get_multipath_config(); pthread_cleanup_push(put_multipath_config, conf); r = kw->print(conf, buff + fwd, len - fwd, data); pthread_cleanup_pop(1); if (!r) { /* no output if no value */ buff[0] = '\0'; return 0; } fwd += r; break; } if (fwd > len) fwd = len; } while (*f++); return fwd; } static const char quote_marker[] = { '\0', '"', '\0' }; bool is_quote(const char* token) { return !memcmp(token, quote_marker, sizeof(quote_marker)); } vector alloc_strvec(char *string) { char *cp, *start, *token; int strlen; int in_string; vector strvec; if (!string) return NULL; cp = string; /* Skip white spaces */ while ((isspace((int) *cp) || !isascii((int) *cp)) && *cp != '\0') cp++; /* Return if there is only white spaces */ if (*cp == '\0') return NULL; /* Return if string begin with a comment */ if (*cp == '!' || *cp == '#') return NULL; /* Create a vector and alloc each command piece */ strvec = vector_alloc(); if (!strvec) return NULL; in_string = 0; while (1) { int two_quotes = 0; if (!vector_alloc_slot(strvec)) goto out; start = cp; if (*cp == '"' && !(in_string && *(cp + 1) == '"')) { cp++; token = MALLOC(sizeof(quote_marker)); if (!token) goto out; memcpy(token, quote_marker, sizeof(quote_marker)); if (in_string) in_string = 0; else in_string = 1; } else if (!in_string && (*cp == '{' || *cp == '}')) { token = MALLOC(2); if (!token) goto out; *(token) = *cp; *(token + 1) = '\0'; cp++; } else { move_on: while ((in_string || (!isspace((int) *cp) && isascii((int) *cp) && *cp != '!' && *cp != '#' && *cp != '{' && *cp != '}')) && *cp != '\0' && *cp != '"') cp++; /* Two consecutive double quotes - don't end string */ if (in_string && *cp == '"') { if (*(cp + 1) == '"') { two_quotes = 1; cp += 2; goto move_on; } } strlen = cp - start; token = MALLOC(strlen + 1); if (!token) goto out; memcpy(token, start, strlen); *(token + strlen) = '\0'; /* Replace "" by " */ if (two_quotes) { char *qq = strstr(token, "\"\""); while (qq != NULL) { memmove(qq + 1, qq + 2, strlen + 1 - (qq + 2 - token)); qq = strstr(qq + 1, "\"\""); } } } vector_set_slot(strvec, token); while ((!in_string && (isspace((int) *cp) || !isascii((int) *cp))) && *cp != '\0') cp++; if (*cp == '\0' || (!in_string && (*cp == '!' || *cp == '#'))) { return strvec; } } out: vector_free(strvec); return NULL; } static int read_line(FILE *stream, char *buf, int size) { char *p; if (fgets(buf, size, stream) == NULL) return 0; strtok_r(buf, "\n\r", &p); return 1; } void * set_value(vector strvec) { char *str = VECTOR_SLOT(strvec, 1); size_t size; int i = 0; int len = 0; char *alloc = NULL; char *tmp; if (!str) { condlog(0, "option '%s' missing value", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } if (!is_quote(str)) { size = strlen(str); if (size == 0) { condlog(0, "option '%s' has empty value", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } alloc = MALLOC(sizeof (char) * (size + 1)); if (alloc) memcpy(alloc, str, size); else goto oom; return alloc; } /* Even empty quotes counts as a value (An empty string) */ alloc = (char *) MALLOC(sizeof (char)); if (!alloc) goto oom; for (i = 2; i < VECTOR_SIZE(strvec); i++) { str = VECTOR_SLOT(strvec, i); if (!str) { free(alloc); condlog(0, "parse error for option '%s'", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } if (is_quote(str)) break; tmp = alloc; /* The first +1 is for the NULL byte. The rest are for the * spaces between words */ len += strlen(str) + 1; alloc = REALLOC(alloc, sizeof (char) * len); if (!alloc) { FREE(tmp); goto oom; } if (*alloc != '\0') strncat(alloc, " ", 1); strncat(alloc, str, len - strlen(alloc) - 1); } return alloc; oom: condlog(0, "can't allocate memory for option '%s'", (char *)VECTOR_SLOT(strvec, 0)); return NULL; } void * set_regex_value(vector strvec) { char *buff = set_value(strvec); if (buff && strcmp("*", buff) == 0) { condlog(0, "Invalid regular expression \"*\" in multipath.conf. Using \".*\""); FREE(buff); return strdup(".*"); } return buff; } /* non-recursive configuration stream handler */ static int kw_level = 0; int warn_on_duplicates(vector uniques, char *str, char *file) { char *tmp; int i; vector_foreach_slot(uniques, tmp, i) { if (!strcmp(str, tmp)) { condlog(1, "%s line %d, duplicate keyword: %s", file, line_nr, str); return 0; } } tmp = strdup(str); if (!tmp) return 1; if (!vector_alloc_slot(uniques)) { free(tmp); return 1; } vector_set_slot(uniques, tmp); return 0; } void free_uniques(vector uniques) { char *tmp; int i; vector_foreach_slot(uniques, tmp, i) free(tmp); vector_free(uniques); } int is_sublevel_keyword(char *str) { return (strcmp(str, "defaults") == 0 || strcmp(str, "blacklist") == 0 || strcmp(str, "blacklist_exceptions") == 0 || strcmp(str, "devices") == 0 || strcmp(str, "devices") == 0 || strcmp(str, "device") == 0 || strcmp(str, "multipaths") == 0 || strcmp(str, "multipath") == 0); } int validate_config_strvec(vector strvec, char *file) { char *str; int i; str = VECTOR_SLOT(strvec, 0); if (str == NULL) { condlog(0, "can't parse option on line %d of %s", line_nr, file); return -1; } if (*str == '}') { if (VECTOR_SIZE(strvec) > 1) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 1), line_nr, file); return 0; } if (*str == '{') { condlog(0, "invalid keyword '%s' on line %d of %s", str, line_nr, file); return -1; } if (is_sublevel_keyword(str)) { str = VECTOR_SLOT(strvec, 1); if (str == NULL) condlog(0, "missing '{' on line %d of %s", line_nr, file); else if (*str != '{') condlog(0, "expecting '{' on line %d of %s. found '%s'", line_nr, file, str); else if (VECTOR_SIZE(strvec) > 2) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file); return 0; } str = VECTOR_SLOT(strvec, 1); if (str == NULL) { condlog(0, "missing value for option '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 0), line_nr, file); return -1; } if (!is_quote(str)) { if (VECTOR_SIZE(strvec) > 2) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file); return 0; } for (i = 2; i < VECTOR_SIZE(strvec); i++) { str = VECTOR_SLOT(strvec, i); if (str == NULL) { condlog(0, "can't parse value on line %d of %s", line_nr, file); return -1; } if (is_quote(str)) { if (VECTOR_SIZE(strvec) > i + 1) condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, (i + 1)), line_nr, file); return 0; } } condlog(0, "missing closing quotes on line %d of %s", line_nr, file); return 0; } static int process_stream(struct config *conf, FILE *stream, vector keywords, char *file) { int i; int r = 0, t; struct keyword *keyword; char *str; char *buf; vector strvec; vector uniques; uniques = vector_alloc(); if (!uniques) return 1; buf = MALLOC(MAXBUF); if (!buf) { vector_free(uniques); return 1; } while (read_line(stream, buf, MAXBUF)) { line_nr++; strvec = alloc_strvec(buf); if (!strvec) continue; if (validate_config_strvec(strvec, file) != 0) { free_strvec(strvec); continue; } str = VECTOR_SLOT(strvec, 0); if (!strcmp(str, EOB)) { if (kw_level > 0) { free_strvec(strvec); break; } condlog(0, "unmatched '%s' at line %d of %s", EOB, line_nr, file); } for (i = 0; i < VECTOR_SIZE(keywords); i++) { keyword = VECTOR_SLOT(keywords, i); if (!strcmp(keyword->string, str)) { if (keyword->unique && warn_on_duplicates(uniques, str, file)) { r = 1; free_strvec(strvec); goto out; } if (keyword->handler) { t = (*keyword->handler) (conf, strvec); r += t; if (t) condlog(1, "multipath.conf +%d, parsing failed: %s", line_nr, buf); } if (keyword->sub) { kw_level++; r += process_stream(conf, stream, keyword->sub, file); kw_level--; } break; } } if (i >= VECTOR_SIZE(keywords)) condlog(1, "%s line %d, invalid keyword: %s", file, line_nr, str); free_strvec(strvec); } out: FREE(buf); free_uniques(uniques); return r; } /* Data initialization */ int process_file(struct config *conf, char *file) { int r; FILE *stream; if (!conf->keywords) { condlog(0, "No keywords allocated"); return 1; } stream = fopen(file, "r"); if (!stream) { condlog(0, "couldn't open configuration file '%s': %s", file, strerror(errno)); return 1; } /* Stream handling */ line_nr = 0; r = process_stream(conf, stream, conf->keywords, file); fclose(stream); //free_keywords(keywords); return r; }