diff --git a/config.c b/config.c index 92b9976b5a7cebf8820dcfbf7264f4f58962b676..62aebe462440460c18538a96d1a30f8e529beaa7 100755 --- a/config.c +++ b/config.c @@ -62,6 +62,7 @@ struct ast_comment { struct ast_category { char name[80]; + int ignored; /* do not let user of the config see this category */ struct ast_variable *root; struct ast_variable *last; struct ast_category *next; @@ -123,13 +124,59 @@ struct ast_variable *ast_variable_new(const char *name, const char *value) return variable; } +static struct ast_variable *variable_get(const struct ast_category *category, const char *name) +{ + struct ast_variable *variable; + + for (variable = category->root; variable; variable = variable->next) + if (!strcasecmp(variable->name, name)) + return variable; + + return NULL; +} + +static void variable_remove(struct ast_category *category, const struct ast_variable *variable) +{ + struct ast_variable *prev = category->root; + + if (!prev) + return; + + if (prev == variable) { + category->root = prev->next; + if (category->last == variable) + category->last = NULL; + } else { + while (prev->next && (prev->next != variable)) prev = prev->next; + if (prev->next) { + prev->next = variable->next; + if (category->last == variable) + category->last = prev; + } + } +} + void ast_variable_append(struct ast_category *category, struct ast_variable *variable) { - if (category->last) - category->last->next = variable; - else - category->root = variable; - category->last = variable; + /* Note: this function also implements "variable replacement"... if the + new variable's value is empty, then existing variables of the same + name in the category are removed (and the new variable is destroyed) + */ + if (variable->value && !ast_strlen_zero(variable->value)) { + if (category->last) + category->last->next = variable; + else + category->root = variable; + category->last = variable; + } else { + struct ast_variable *v; + + while ((v = variable_get(category, variable->name))) { + variable_remove(category, v); + ast_variables_destroy(v); + } + ast_variables_destroy(variable); + } } void ast_variables_destroy(struct ast_variable *v) @@ -181,6 +228,34 @@ char *ast_variable_retrieve(const struct ast_config *config, const char *categor return NULL; } +static struct ast_variable *variable_clone(const struct ast_variable *old) +{ + struct ast_variable *new = ast_variable_new(old->name, old->value); + + if (new) { + new->lineno = old->lineno; + new->object = old->object; + new->blanklines = old->blanklines; + /* TODO: clone comments? */ + } + + return new; +} + +static void move_variables(struct ast_category *old, struct ast_category *new) +{ + struct ast_variable *var; + struct ast_variable *next; + + next = old->root; + old->root = NULL; + for (var = next; var; var = next) { + next = var->next; + var->next = NULL; + ast_variable_append(new, var); + } +} + struct ast_category *ast_category_new(const char *name) { struct ast_category *category; @@ -194,23 +269,28 @@ struct ast_category *ast_category_new(const char *name) return category; } -struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name) +static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored) { struct ast_category *cat; for (cat = config->root; cat; cat = cat->next) { - if (cat->name == category_name) + if (cat->name == category_name && (ignored || !cat->ignored)) return cat; } for (cat = config->root; cat; cat = cat->next) { - if (!strcasecmp(cat->name, category_name)) + if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored)) return cat; } return NULL; } +struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name) +{ + return category_get(config, category_name, 0); +} + int ast_category_exist(const struct ast_config *config, const char *category_name) { return !!ast_category_get(config, category_name); @@ -232,6 +312,13 @@ void ast_category_destroy(struct ast_category *cat) free(cat); } +static struct ast_category *next_available_category(struct ast_category *cat) +{ + for (; cat && cat->ignored; cat = cat->next); + + return cat; +} + char *ast_category_browse(struct ast_config *config, const char *prev) { struct ast_category *cat = NULL; @@ -256,6 +343,9 @@ char *ast_category_browse(struct ast_config *config, const char *prev) } } } + + if (cat) + cat = next_available_category(cat); config->last_browse = cat; if (cat) @@ -279,6 +369,19 @@ void ast_category_rename(struct ast_category *cat, const char *name) strncpy(cat->name, name, sizeof(cat->name) - 1); } +static void inherit_category(struct ast_category *new, const struct ast_category *base) +{ + struct ast_variable *var; + + for (var = base->root; var; var = var->next) { + struct ast_variable *v; + + v = variable_clone(var); + if (v) + ast_variable_append(new, v); + } +} + struct ast_config *ast_config_new(void) { struct ast_config *config; @@ -323,30 +426,69 @@ void ast_config_set_current_category(struct ast_config *cfg, const struct ast_ca static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile) { char *c; - char *cur; + char *cur = buf; struct ast_variable *v; int object; - cur = ast_strip(buf); - if (ast_strlen_zero(cur)) - return 0; - /* Actually parse the entry */ if (cur[0] == '[') { + struct ast_category *newcat = NULL; + char *catname; + /* A category header */ c = strchr(cur, ']'); if (!c) { ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile); return -1; } - *c = '\0'; + *c++ = '\0'; cur++; - *cat = ast_category_new(cur); - if (!*cat) { + if (*c++ != '(') + c = NULL; + catname = cur; + *cat = newcat = ast_category_new(catname); + if (!newcat) { ast_log(LOG_WARNING, "Out of memory, line %d of %s\n", lineno, configfile); return -1; } - ast_category_append(cfg, *cat); + /* If there are options or categories to inherit from, process them now */ + if (c) { + if (!(cur = strchr(c, ')'))) { + ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile); + return -1; + } + *cur = '\0'; + while ((cur = strsep(&c, ","))) { + if (!strcasecmp(cur, "!")) { + (*cat)->ignored = 1; + } else if (!strcasecmp(cur, "+")) { + *cat = category_get(cfg, catname, 1); + if (!*cat) { + ast_destroy(cfg); + if (newcat) + ast_category_destroy(newcat); + ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile); + return -1; + } + if (newcat) { + move_variables(newcat, *cat); + ast_category_destroy(newcat); + newcat = NULL; + } + } else { + struct ast_category *base; + + base = category_get(cfg, cur, 1); + if (!base) { + ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile); + return -1; + } + inherit_category(*cat, base); + } + } + } + if (newcat) + ast_category_append(cfg, *cat); } else if (cur[0] == '#') { /* A directive */ cur++; @@ -518,9 +660,13 @@ static struct ast_config *config_text_file_load(const char *database, const char new_buf = comment_p + 1; } } - if (process_buf && process_text_line(cfg, &cat, process_buf, lineno, filename)) { - cfg = NULL; - break; + if (process_buf) { + char *buf = ast_strip(process_buf); + if (!ast_strlen_zero(buf)) + if (process_text_line(cfg, &cat, buf, lineno, filename)) { + cfg = NULL; + break; + } } } }