loader: Fix comments in struct ast_module.
[asterisk/asterisk.git] / main / config.c
index d9bef2e..3d8dcfb 100644 (file)
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include "asterisk/paths.h"    /* use ast_config_AST_CONFIG_DIR */
 #include "asterisk/network.h"  /* we do some sockaddr manipulation here */
+
+#include <string.h>
+#include <libgen.h>
 #include <time.h>
 #include <sys/stat.h>
 
 #include <math.h>      /* HUGE_VAL */
+#include <regex.h>
 
 #define AST_INCLUDE_GLOB 1
 
@@ -69,8 +71,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 static char *extconfig_conf = "extconfig.conf";
 
 static struct ao2_container *cfg_hooks;
-static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg);
-inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
+static void config_hook_exec(const char *filename, const char *module, const struct ast_config *cfg);
+static inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
+static int does_category_match(struct ast_category *cat, const char *category_name,
+       const char *match, char sep);
 
 /*! \brief Structure to keep comments for rewriting configuration files */
 struct ast_comment {
@@ -232,6 +236,8 @@ struct ast_category {
        struct ast_variable *root;
        /*! Last category variable in the list. */
        struct ast_variable *last;
+       /*! Previous node in the list. */
+       struct ast_category *prev;
        /*! Next node in the list. */
        struct ast_category *next;
 };
@@ -275,7 +281,7 @@ struct ast_config_include {
 static void ast_variable_destroy(struct ast_variable *doomed);
 static void ast_includes_destroy(struct ast_config_include *incls);
 
-#ifdef MALLOC_DEBUG
+#ifdef __AST_DEBUG_MALLOC
 struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno)
 #else
 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename)
@@ -292,7 +298,7 @@ struct ast_variable *ast_variable_new(const char *name, const char *value, const
        }
 
        if (
-#ifdef MALLOC_DEBUG
+#ifdef __AST_DEBUG_MALLOC
                (variable = __ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable), file, lineno, func))
 #else
                (variable = ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable)))
@@ -599,22 +605,18 @@ void ast_variables_destroy(struct ast_variable *v)
 
 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
 {
-       struct ast_category *cat = NULL;
-
-       if (!category) {
-               return NULL;
-       }
+       struct ast_category *cat;
 
        if (config->last_browse && (config->last_browse->name == category)) {
                cat = config->last_browse;
        } else {
-               cat = ast_category_get(config, category);
+               cat = ast_category_get(config, category, NULL);
        }
 
        return (cat) ? cat->root : NULL;
 }
 
-inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2)
+static inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2)
 {
     l1->next = l2->next;
     l2->next = l1;
@@ -677,8 +679,7 @@ const char *ast_config_option(struct ast_config *cfg, const char *cat, const cha
        return tmp;
 }
 
-
-const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
+const char *ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
 {
        struct ast_variable *v;
 
@@ -703,6 +704,142 @@ const char *ast_variable_retrieve(const struct ast_config *config, const char *c
        return NULL;
 }
 
+const char *ast_variable_retrieve_filtered(struct ast_config *config,
+       const char *category, const char *variable, const char *filter)
+{
+       struct ast_category *cat = NULL;
+       const char *value;
+
+       while ((cat = ast_category_browse_filtered(config, category, cat, filter))) {
+               value = ast_variable_find(cat, variable);
+               if (value) {
+                       return value;
+               }
+       }
+
+       return NULL;
+}
+
+const char *ast_variable_find(const struct ast_category *category, const char *variable)
+{
+       return ast_variable_find_in_list(category->root, variable);
+}
+
+const struct ast_variable *ast_variable_find_variable_in_list(const struct ast_variable *list, const char *variable_name)
+{
+       const struct ast_variable *v;
+
+       for (v = list; v; v = v->next) {
+               if (!strcasecmp(variable_name, v->name)) {
+                       return v;
+               }
+       }
+       return NULL;
+}
+
+int ast_variables_match(const struct ast_variable *left, const struct ast_variable *right)
+{
+       char *op;
+
+       if (left == right) {
+               return 1;
+       }
+
+       if (!(left && right)) {
+               return 0;
+       }
+
+       op = strrchr(right->name, ' ');
+       if (op) {
+               op++;
+       }
+
+       return ast_strings_match(left->value, op ? ast_strdupa(op) : NULL, right->value);
+}
+
+int ast_variable_lists_match(const struct ast_variable *left, const struct ast_variable *right, int exact_match)
+{
+       const struct ast_variable *field;
+       int right_count = 0;
+       int left_count = 0;
+
+       if (left == right) {
+               return 1;
+       }
+
+       if (!(left && right)) {
+               return 0;
+       }
+
+       for (field = right; field; field = field->next) {
+               char *space = strrchr(field->name, ' ');
+               const struct ast_variable *old;
+               char * name = (char *)field->name;
+
+               if (space) {
+                       name = ast_strdup(field->name);
+                       if (!name) {
+                               return 0;
+                       }
+                       name[space - field->name] = '\0';
+               }
+
+               old = ast_variable_find_variable_in_list(left, name);
+               if (name != field->name) {
+                       ast_free(name);
+               }
+
+               if (exact_match) {
+                       if (!old || strcmp(old->value, field->value)) {
+                               return 0;
+                       }
+               } else {
+                       if (!ast_variables_match(old, field)) {
+                               return 0;
+                       }
+               }
+
+               right_count++;
+       }
+
+       if (exact_match) {
+               for (field = left; field; field = field->next) {
+                       left_count++;
+               }
+
+               if (right_count != left_count) {
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+const char *ast_variable_find_in_list(const struct ast_variable *list, const char *variable)
+{
+       const struct ast_variable *v;
+
+       for (v = list; v; v = v->next) {
+               if (!strcasecmp(variable, v->name)) {
+                       return v->value;
+               }
+       }
+       return NULL;
+}
+
+const char *ast_variable_find_last_in_list(const struct ast_variable *list, const char *variable)
+{
+       const struct ast_variable *v;
+       const char *found = NULL;
+
+       for (v = list; v; v = v->next) {
+               if (!strcasecmp(variable, v->name)) {
+                       found = v->value;
+               }
+       }
+       return found;
+}
+
 static struct ast_variable *variable_clone(const struct ast_variable *old)
 {
        struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
@@ -726,7 +863,100 @@ static void move_variables(struct ast_category *old, struct ast_category *new)
        ast_variable_append(new, var);
 }
 
-struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
+/*! \brief Returns true if ALL of the regex expressions and category name match.
+ * Both can be NULL (I.E. no predicate) which results in a true return;
+ */
+static int does_category_match(struct ast_category *cat, const char *category_name,
+       const char *match, char sep)
+{
+       char *dupmatch;
+       char *nvp = NULL;
+       int match_found = 0, match_expressions = 0;
+       int template_ok = 0;
+
+       /* Only match on category name if it's not a NULL or empty string */
+       if (!ast_strlen_zero(category_name) && strcasecmp(cat->name, category_name)) {
+               return 0;
+       }
+
+       /* If match is NULL or empty, automatically match if not a template */
+       if (ast_strlen_zero(match)) {
+               return !cat->ignored;
+       }
+
+       dupmatch = ast_strdupa(match);
+
+       while ((nvp = ast_strsep(&dupmatch, sep, AST_STRSEP_STRIP))) {
+               struct ast_variable *v;
+               char *match_name;
+               char *match_value = NULL;
+               char *regerr;
+               int rc;
+               regex_t r_name, r_value;
+
+               match_expressions++;
+
+               match_name = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
+               match_value = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
+
+               /* an empty match value is OK.  A NULL match value (no =) is NOT. */
+               if (match_value == NULL) {
+                       break;
+               }
+
+               if (!strcmp("TEMPLATES", match_name)) {
+                       if (!strcasecmp("include", match_value)) {
+                               if (cat->ignored) {
+                                       template_ok = 1;
+                               }
+                               match_found++;
+                       } else if (!strcasecmp("restrict", match_value)) {
+                               if (cat->ignored) {
+                                       match_found++;
+                                       template_ok = 1;
+                               } else {
+                                       break;
+                               }
+                       }
+                       continue;
+               }
+
+               if ((rc = regcomp(&r_name, match_name, REG_EXTENDED | REG_NOSUB))) {
+                       regerr = ast_alloca(128);
+                       regerror(rc, &r_name, regerr, 128);
+                       ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
+                               match_name, regerr);
+                       regfree(&r_name);
+                       return 0;
+               }
+               if ((rc = regcomp(&r_value, match_value, REG_EXTENDED | REG_NOSUB))) {
+                       regerr = ast_alloca(128);
+                       regerror(rc, &r_value, regerr, 128);
+                       ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
+                               match_value, regerr);
+                       regfree(&r_name);
+                       regfree(&r_value);
+                       return 0;
+               }
+
+               for (v = cat->root; v; v = v->next) {
+                       if (!regexec(&r_name, v->name, 0, NULL, 0)
+                               && !regexec(&r_value, v->value, 0, NULL, 0)) {
+                               match_found++;
+                               break;
+                       }
+               }
+               regfree(&r_name);
+               regfree(&r_value);
+       }
+       if (match_found == match_expressions && (!cat->ignored || template_ok)) {
+               return 1;
+       }
+       return 0;
+}
+
+
+static struct ast_category *new_category(const char *name, const char *in_file, int lineno, int template)
 {
        struct ast_category *category;
 
@@ -741,66 +971,130 @@ struct ast_category *ast_category_new(const char *name, const char *in_file, int
        }
        ast_copy_string(category->name, name, sizeof(category->name));
        category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
+       category->ignored = template;
        return category;
 }
 
-static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
+struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
+{
+       return new_category(name, in_file, lineno, 0);
+}
+
+struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno)
+{
+       return new_category(name, in_file, lineno, 1);
+}
+
+static struct ast_category *category_get_sep(const struct ast_config *config,
+       const char *category_name, const char *filter, char sep)
 {
        struct ast_category *cat;
 
-       /* try exact match first, then case-insensitive match */
        for (cat = config->root; cat; cat = cat->next) {
-               if (cat->name == category_name && (ignored || !cat->ignored))
+               if (cat->name == category_name && does_category_match(cat, category_name, filter, sep)) {
                        return cat;
+               }
        }
 
        for (cat = config->root; cat; cat = cat->next) {
-               if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
+               if (does_category_match(cat, category_name, filter, sep)) {
                        return cat;
+               }
        }
 
        return NULL;
 }
 
-struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
+struct ast_category *ast_category_get(const struct ast_config *config,
+       const char *category_name, const char *filter)
+{
+       return category_get_sep(config, category_name, filter, ',');
+}
+
+const char *ast_category_get_name(const struct ast_category *category)
+{
+       return category->name;
+}
+
+int ast_category_is_template(const struct ast_category *category)
+{
+       return category->ignored;
+}
+
+struct ast_str *ast_category_get_templates(const struct ast_category *category)
 {
-       return category_get(config, category_name, 0);
+       struct ast_category_template_instance *template;
+       struct ast_str *str;
+       int first = 1;
+
+       if (AST_LIST_EMPTY(&category->template_instances)) {
+               return NULL;
+       }
+
+       str = ast_str_create(128);
+       if (!str) {
+               return NULL;
+       }
+
+       AST_LIST_TRAVERSE(&category->template_instances, template, next) {
+               ast_str_append(&str, 0, "%s%s", first ? "" : ",", template->name);
+               first = 0;
+       }
+
+       return str;
 }
 
-int ast_category_exist(const struct ast_config *config, const char *category_name)
+int ast_category_exist(const struct ast_config *config, const char *category_name,
+       const char *filter)
 {
-       return !!ast_category_get(config, category_name);
+       return !!ast_category_get(config, category_name, filter);
 }
 
 void ast_category_append(struct ast_config *config, struct ast_category *category)
 {
-       if (config->last)
+       if (config->last) {
                config->last->next = category;
-       else
+               category->prev = config->last;
+       } else {
                config->root = category;
+               category->prev = NULL;
+       }
+       category->next = NULL;
        category->include_level = config->include_level;
+
        config->last = category;
        config->current = category;
 }
 
-void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
+int ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
 {
        struct ast_category *cur_category;
 
-       if (!cat || !match)
-               return;
+       if (!config || !config->root || !cat || !match) {
+               return -1;
+       }
+
        if (!strcasecmp(config->root->name, match)) {
                cat->next = config->root;
+               cat->prev = NULL;
+               config->root->prev = cat;
                config->root = cat;
-               return;
+               return 0;
        }
-       for (cur_category = config->root; cur_category; cur_category = cur_category->next) {
-               if (!strcasecmp(cur_category->next->name, match)) {
-                       cat->next = cur_category->next;
-                       cur_category->next = cat;
-                       break;
+
+       for (cur_category = config->root->next; cur_category; cur_category = cur_category->next) {
+               if (!strcasecmp(cur_category->name, match)) {
+                       cat->prev = cur_category->prev;
+                       cat->prev->next = cat;
+
+                       cat->next = cur_category;
+                       cur_category->prev = cat;
+
+                       return 0;
                }
        }
+
+       return -1;
 }
 
 static void ast_destroy_template_list(struct ast_category *cat)
@@ -837,9 +1131,10 @@ static void ast_includes_destroy(struct ast_config_include *incls)
        }
 }
 
-static struct ast_category *next_available_category(struct ast_category *cat)
+static struct ast_category *next_available_category(struct ast_category *cat,
+       const char *name, const char *filter)
 {
-       for (; cat && cat->ignored; cat = cat->next);
+       for (; cat && !does_category_match(cat, name, filter, ','); cat = cat->next);
 
        return cat;
 }
@@ -852,7 +1147,7 @@ struct ast_variable *ast_category_first(struct ast_category *cat)
 
 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
 {
-       struct ast_category *category = ast_category_get(config, cat);
+       struct ast_category *category = ast_category_get(config, cat, NULL);
 
        if (category)
                return category->root;
@@ -1017,12 +1312,28 @@ char *ast_category_browse(struct ast_config *config, const char *prev)
        }
 
        if (cat)
-               cat = next_available_category(cat);
+               cat = next_available_category(cat, NULL, NULL);
 
        config->last_browse = cat;
        return (cat) ? cat->name : NULL;
 }
 
+struct ast_category *ast_category_browse_filtered(struct ast_config *config,
+       const char *category_name, struct ast_category *prev, const char *filter)
+{
+       struct ast_category *cat;
+
+       if (!prev) {
+               prev = config->root;
+       } else {
+               prev = prev->next;
+       }
+
+       cat = next_available_category(prev, category_name, filter);
+
+       return cat;
+}
+
 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
 {
        struct ast_variable *v;
@@ -1039,20 +1350,27 @@ void ast_category_rename(struct ast_category *cat, const char *name)
        ast_copy_string(cat->name, name, sizeof(cat->name));
 }
 
-static void inherit_category(struct ast_category *new, const struct ast_category *base)
+int ast_category_inherit(struct ast_category *new, const struct ast_category *base)
 {
        struct ast_variable *var;
        struct ast_category_template_instance *x;
 
        x = ast_calloc(1, sizeof(*x));
        if (!x) {
-               return;
+               return -1;
        }
        strcpy(x->name, base->name);
        x->inst = base;
        AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
-       for (var = base->root; var; var = var->next)
-               ast_variable_append(new, variable_clone(var));
+       for (var = base->root; var; var = var->next) {
+               struct ast_variable *cloned = variable_clone(var);
+               if (!cloned) {
+                       return -1;
+               }
+               cloned->inherited = 1;
+               ast_variable_append(new, cloned);
+       }
+       return 0;
 }
 
 struct ast_config *ast_config_new(void)
@@ -1143,65 +1461,49 @@ int ast_variable_update(struct ast_category *category, const char *variable,
        return -1;
 }
 
-int ast_category_delete(struct ast_config *cfg, const char *category)
+struct ast_category *ast_category_delete(struct ast_config *config,
+       struct ast_category *category)
 {
-       struct ast_category *prev=NULL, *cat;
+       struct ast_category *prev;
 
-       cat = cfg->root;
-       while (cat) {
-               if (cat->name == category) {
-                       if (prev) {
-                               prev->next = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = prev;
-                       } else {
-                               cfg->root = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = NULL;
-                       }
-                       ast_category_destroy(cat);
-                       return 0;
-               }
-               prev = cat;
-               cat = cat->next;
+       if (!config || !category) {
+               return NULL;
        }
 
-       prev = NULL;
-       cat = cfg->root;
-       while (cat) {
-               if (!strcasecmp(cat->name, category)) {
-                       if (prev) {
-                               prev->next = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = prev;
-                       } else {
-                               cfg->root = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = NULL;
-                       }
-                       ast_category_destroy(cat);
-                       return 0;
-               }
-               prev = cat;
-               cat = cat->next;
+       if (category->prev) {
+               category->prev->next = category->next;
+       } else {
+               config->root = category->next;
        }
-       return -1;
+
+       if (category->next) {
+               category->next->prev = category->prev;
+       } else {
+               config->last = category->prev;
+       }
+
+       prev = category->prev;
+
+       if (config->last_browse == category) {
+               config->last_browse = prev;
+       }
+
+       ast_category_destroy(category);
+
+       return prev;
 }
 
-int ast_category_empty(struct ast_config *cfg, const char *category)
+int ast_category_empty(struct ast_category *category)
 {
-       struct ast_category *cat;
-
-       for (cat = cfg->root; cat; cat = cat->next) {
-               if (strcasecmp(cat->name, category))
-                       continue;
-               ast_variables_destroy(cat->root);
-               cat->root = NULL;
-               cat->last = NULL;
-               return 0;
+       if (!category) {
+               return -1;
        }
 
-       return -1;
+       ast_variables_destroy(category->root);
+       category->root = NULL;
+       category->last = NULL;
+
+       return 0;
 }
 
 void ast_config_destroy(struct ast_config *cfg)
@@ -1444,7 +1746,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                 *              You can put a comma-separated list of categories and templates
                 *              and '!' and '+' between parentheses, with obvious meaning.
                 */
-               struct ast_category *newcat = NULL;
+               struct ast_category *newcat;
                char *catname;
 
                c = strchr(cur, ']');
@@ -1457,14 +1759,13 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                if (*c++ != '(')
                        c = NULL;
                catname = cur;
-               if (!(*cat = newcat = ast_category_new(catname,
-                               S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
-                               lineno))) {
+               *cat = newcat = ast_category_new(catname,
+                       S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
+                       lineno);
+               if (!newcat) {
                        return -1;
                }
                (*cat)->lineno = lineno;
-               *last_var = 0;
-               *last_cat = newcat;
 
                /* add comments */
                if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
@@ -1477,6 +1778,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                /* If there are options or categories to inherit from, process them now */
                if (c) {
                        if (!(cur = strchr(c, ')'))) {
+                               ast_category_destroy(newcat);
                                ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
                                return -1;
                        }
@@ -1484,15 +1786,23 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                        while ((cur = strsep(&c, ","))) {
                                if (!strcasecmp(cur, "!")) {
                                        (*cat)->ignored = 1;
-                               } else if (!strcasecmp(cur, "+")) {
-                                       *cat = category_get(cfg, catname, 1);
+                               } else if (cur[0] == '+') {
+                                       char *filter = NULL;
+
+                                       if (cur[1] != ',') {
+                                               filter = &cur[1];
+                                       }
+                                       *cat = category_get_sep(cfg, catname, filter, '&');
                                        if (!(*cat)) {
-                                               if (newcat)
+                                               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) {
+                                               ast_config_set_current_category(cfg, *cat);
+                                               (*cat)->ignored |= newcat->ignored;
                                                move_variables(newcat, *cat);
                                                ast_category_destroy(newcat);
                                                newcat = NULL;
@@ -1500,17 +1810,37 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                } else {
                                        struct ast_category *base;
 
-                                       base = category_get(cfg, cur, 1);
+                                       base = ast_category_get(cfg, cur, "TEMPLATES=include");
                                        if (!base) {
+                                               if (newcat) {
+                                                       ast_category_destroy(newcat);
+                                               }
                                                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 (ast_category_inherit(*cat, base)) {
+                                               if (newcat) {
+                                                       ast_category_destroy(newcat);
+                                               }
+                                               ast_log(LOG_ERROR, "Inheritence requested, but allocation failed\n");
+                                               return -1;
+                                       }
                                }
                        }
                }
-               if (newcat)
-                       ast_category_append(cfg, *cat);
+
+               /*
+                * We need to set *last_cat to newcat here regardless.  If the
+                * category is being appended to we have no place for trailing
+                * comments on the appended category.  The appended category
+                * may be in another file or it already has trailing comments
+                * that we would then leak.
+                */
+               *last_var = NULL;
+               *last_cat = newcat;
+               if (newcat) {
+                       ast_category_append(cfg, newcat);
+               }
        } else if (cur[0] == '#') { /* A directive - #include or #exec */
                char *cur2;
                char real_inclusion_name[256];
@@ -1666,7 +1996,7 @@ set_new_variable:
                        } else if ((v = ast_variable_new(cur, ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
                                v->lineno = lineno;
                                v->object = object;
-                               *last_cat = 0;
+                               *last_cat = NULL;
                                *last_var = v;
                                /* Put and reset comments */
                                v->blanklines = 0;
@@ -1706,8 +2036,8 @@ static struct ast_config *config_text_file_load(const char *database, const char
        struct stat statbuf;
        struct cache_file_mtime *cfmtime = NULL;
        struct cache_file_include *cfinclude;
-       struct ast_variable *last_var = 0;
-       struct ast_category *last_cat = 0;
+       struct ast_variable *last_var = NULL;
+       struct ast_category *last_cat = NULL;
        /*! Growable string buffer */
        struct ast_str *comment_buffer = NULL;  /*!< this will be a comment collector.*/
        struct ast_str *lline_buffer = NULL;    /*!< A buffer for stuff behind the ; */
@@ -2067,21 +2397,27 @@ static void inclfile_destroy(void *obj)
        ast_free(o->fname);
 }
 
-
-static struct inclfile *set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset)
+static void make_fn(char *fn, size_t fn_size, const char *file, const char *configfile)
 {
-       struct inclfile lookup;
-       struct inclfile *fi;
-
        if (ast_strlen_zero(file)) {
-               if (configfile[0] == '/')
+               if (configfile[0] == '/') {
                        ast_copy_string(fn, configfile, fn_size);
-               else
+               } else {
                        snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
-       } else if (file[0] == '/')
+               }
+       } else if (file[0] == '/') {
                ast_copy_string(fn, file, fn_size);
-       else
+       } else {
                snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
+       }
+}
+
+static struct inclfile *set_fn(char *fn, size_t fn_size, const char *file, const char *configfile, struct ao2_container *fileset)
+{
+       struct inclfile lookup;
+       struct inclfile *fi;
+
+       make_fn(fn, fn_size, file, configfile);
        lookup.fname = fn;
        fi = ao2_find(fileset, &lookup, OBJ_POINTER);
        if (fi) {
@@ -2169,11 +2505,35 @@ static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast
 
 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
 {
-       return ast_config_text_file_save(configfile, cfg, generator);
+       return ast_config_text_file_save2(configfile, cfg, generator, CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT);
 }
 
 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
 {
+       return ast_config_text_file_save2(configfile, cfg, generator, CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT);
+}
+
+static int is_writable(const char *fn)
+{
+       if (access(fn, F_OK)) {
+               char *dn = dirname(ast_strdupa(fn));
+
+               if (access(dn, R_OK | W_OK)) {
+                       ast_log(LOG_ERROR, "Unable to write to directory %s (%s)\n", dn, strerror(errno));
+                       return 0;
+               }
+       } else {
+               if (access(fn, R_OK | W_OK)) {
+                       ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+int ast_config_text_file_save2(const char *configfile, const struct ast_config *cfg, const char *generator, uint32_t flags)
+{
        FILE *f;
        char fn[PATH_MAX];
        struct ast_variable *var;
@@ -2190,11 +2550,29 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
                return -1;
        }
 
-       /* reset all the output flags, in case this isn't our first time saving this data */
+       /* Check all the files for write access before attempting to modify any of them */
        for (incl = cfg->includes; incl; incl = incl->next) {
+               /* reset all the output flags in case this isn't our first time saving this data */
                incl->output = 0;
+
+               if (!incl->exec) {
+                       /* now make sure we have write access to the include file or its parent directory */
+                       make_fn(fn, sizeof(fn), incl->included_file, configfile);
+                       /* If the file itself doesn't exist, make sure we have write access to the directory */
+                       if (!is_writable(fn)) {
+                               return -1;
+                       }
+               }
+       }
+
+       /* now make sure we have write access to the main config file or its parent directory */
+       make_fn(fn, sizeof(fn), 0, configfile);
+       if (!is_writable(fn)) {
+               return -1;
        }
 
+       /* Now that we know we have write access to all files, it's safe to start truncating them */
+
        /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
           are all truncated to zero bytes and have that nice header*/
        for (incl = cfg->includes; incl; incl = incl->next) {
@@ -2206,8 +2584,7 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
                                gen_header(f, configfile, fn, generator);
                                fclose(f); /* this should zero out the file */
                        } else {
-                               ast_debug(1, "Unable to open for writing: %s\n", fn);
-                               ast_verb(2, "Unable to write %s (%s)\n", fn, strerror(errno));
+                               ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
                        }
                        if (fi) {
                                ao2_ref(fi, -1);
@@ -2240,8 +2617,7 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
                        fi = set_fn(fn, sizeof(fn), cat->file, configfile, fileset);
                        f = fopen(fn, "a");
                        if (!f) {
-                               ast_debug(1, "Unable to open for writing: %s\n", fn);
-                               ast_verb(2, "Unable to write %s (%s)\n", fn, strerror(errno));
+                               ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
                                if (fi) {
                                        ao2_ref(fi, -1);
                                }
@@ -2313,16 +2689,31 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
                        while (var) {
                                struct ast_category_template_instance *x;
                                int found = 0;
+
                                AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
                                        struct ast_variable *v;
                                        for (v = x->inst->root; v; v = v->next) {
-                                               if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
-                                                       found = 1;
-                                                       break;
+
+                                               if (flags & CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT) {
+                                                       if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
+                                                               found = 1;
+                                                               break;
+                                                       }
+                                               } else {
+                                                       if (var->inherited) {
+                                                               found = 1;
+                                                               break;
+                                                       } else {
+                                                               if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
+                                                                       found = 1;
+                                                                       break;
+                                                               }
+                                                       }
                                                }
                                        }
-                                       if (found)
+                                       if (found) {
                                                break;
+                                       }
                                }
                                if (found) {
                                        var = var->next;
@@ -2358,10 +2749,22 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
                                        if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
                                                fprintf(f,"%s", cmt->cmt);
                                }
-                               if (var->sameline)
-                                       fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
-                               else
-                                       fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
+
+                               { /* Block for 'escaped' scope */
+                                       int escaped_len = 2 * strlen(var->value) + 1;
+                                       char escaped[escaped_len];
+
+                                       ast_escape_semicolons(var->value, escaped, escaped_len);
+
+                                       if (var->sameline) {
+                                               fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="),
+                                                       escaped, var->sameline->cmt);
+                                       } else {
+                                               fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="),
+                                                       escaped);
+                                       }
+                               }
+
                                for (cmt = var->trailing; cmt; cmt=cmt->next) {
                                        if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
                                                fprintf(f,"%s", cmt->cmt);
@@ -2425,6 +2828,9 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
        }
        ao2_ref(fileset, -1); /* this should destroy the hash container */
 
+       /* pass new configuration to any config hooks */
+       config_hook_exec(configfile, generator, cfg);
+
        return 0;
 }
 
@@ -2606,6 +3012,7 @@ int ast_realtime_is_mapping_defined(const char *family)
                        return 1;
                }
        }
+       ast_debug(5, "Failed to find a realtime mapping for %s\n", family);
 
        return 0;
 }
@@ -2739,9 +3146,22 @@ struct ast_config *ast_config_load2(const char *filename, const char *who_asked,
        return result;
 }
 
-#define realtime_arguments_to_fields(ap) realtime_arguments_to_fields2(ap, 0)
+#define realtime_arguments_to_fields(ap, result) realtime_arguments_to_fields2(ap, 0, result)
 
-static struct ast_variable *realtime_arguments_to_fields2(va_list ap, int skip)
+/*!
+ * \internal
+ * \brief
+ *
+ * \param ap list of variable arguments
+ * \param skip Skip argument pairs for this number of variables
+ * \param result Address of a variables pointer to store the results
+ *               May be NULL if no arguments are parsed
+ *               Will be NULL on failure.
+ *
+ * \retval 0 on success or empty ap list
+ * \retval -1 on failure
+ */
+static int realtime_arguments_to_fields2(va_list ap, int skip, struct ast_variable **result)
 {
        struct ast_variable *first, *fields = NULL;
        const char *newparam;
@@ -2768,10 +3188,14 @@ static struct ast_variable *realtime_arguments_to_fields2(va_list ap, int skip)
         * using the skip parameter:
         *
         *     va_start(ap, last);
-        *     x = realtime_arguments_to_fields(ap);
+        *     if (realtime_arguments_to_fields(ap, &x)) {
+        *         // FAILURE CONDITIONS
+        *     }
         *     va_end(ap);
         *     va_start(ap, last);
-        *     y = realtime_arguments_to_fields2(ap, 1);
+        *     if (realtime_arguments_to_fields2(ap, 1, &y)) {
+        *         // FAILURE CONDITIONS
+        *     }
         *     va_end(ap);
         */
        while (skip--) {
@@ -2785,10 +3209,15 @@ static struct ast_variable *realtime_arguments_to_fields2(va_list ap, int skip)
 
        /* Load up the first vars. */
        newparam = va_arg(ap, const char *);
+       if (!newparam) {
+               *result = NULL;
+               return 0;
+       }
        newval = va_arg(ap, const char *);
 
        if (!(first = ast_variable_new(newparam, newval, ""))) {
-               return NULL;
+               *result = NULL;
+               return -1;
        }
 
        while ((newparam = va_arg(ap, const char *))) {
@@ -2798,7 +3227,8 @@ static struct ast_variable *realtime_arguments_to_fields2(va_list ap, int skip)
                if (!(field = ast_variable_new(newparam, newval, ""))) {
                        ast_variables_destroy(fields);
                        ast_variables_destroy(first);
-                       return NULL;
+                       *result = NULL;
+                       return -1;
                }
 
                field->next = fields;
@@ -2808,7 +3238,8 @@ static struct ast_variable *realtime_arguments_to_fields2(va_list ap, int skip)
        first->next = fields;
        fields = first;
 
-       return fields;
+       *result = fields;
+       return 0;
 }
 
 struct ast_variable *ast_load_realtime_all_fields(const char *family, const struct ast_variable *fields)
@@ -2839,7 +3270,7 @@ struct ast_variable *ast_load_realtime_all(const char *family, ...)
        va_list ap;
 
        va_start(ap, family);
-       fields = realtime_arguments_to_fields(ap);
+       realtime_arguments_to_fields(ap, &fields);
        va_end(ap);
 
        if (fields) {
@@ -2887,12 +3318,19 @@ struct ast_variable *ast_load_realtime_fields(const char *family, const struct a
 struct ast_variable *ast_load_realtime(const char *family, ...)
 {
        RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
+       int field_res = 0;
        va_list ap;
 
        va_start(ap, family);
-       fields = realtime_arguments_to_fields(ap);
+       if (realtime_arguments_to_fields(ap, &fields)) {
+               field_res = -1;
+       }
        va_end(ap);
 
+       if (field_res) {
+               return NULL;
+       }
+
        if (!fields) {
                return NULL;
        }
@@ -2996,7 +3434,7 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...)
        va_list ap;
 
        va_start(ap, family);
-       fields = realtime_arguments_to_fields(ap);
+       realtime_arguments_to_fields(ap, &fields);
        va_end(ap);
 
        if (!fields) {
@@ -3033,7 +3471,7 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo
        va_list ap;
 
        va_start(ap, lookup);
-       fields = realtime_arguments_to_fields(ap);
+       realtime_arguments_to_fields(ap, &fields);
        va_end(ap);
 
        if (!fields) {
@@ -3073,11 +3511,11 @@ int ast_update2_realtime(const char *family, ...)
        /* XXX: If we wanted to pass no lookup fields (select all), we'd be
         * out of luck. realtime_arguments_to_fields expects at least one key
         * value pair. */
-       lookup_fields = realtime_arguments_to_fields(ap);
+       realtime_arguments_to_fields(ap, &lookup_fields);
        va_end(ap);
 
        va_start(ap, family);
-       update_fields = realtime_arguments_to_fields2(ap, 1);
+       realtime_arguments_to_fields2(ap, 1, &update_fields);
        va_end(ap);
 
        if (!lookup_fields || !update_fields) {
@@ -3114,7 +3552,7 @@ int ast_store_realtime(const char *family, ...)
        va_list ap;
 
        va_start(ap, family);
-       fields = realtime_arguments_to_fields(ap);
+       realtime_arguments_to_fields(ap, &fields);
        va_end(ap);
 
        if (!fields) {
@@ -3147,13 +3585,16 @@ int ast_destroy_realtime_fields(const char *family, const char *keyfield, const
 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
 {
        RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
+       int res = 0;
        va_list ap;
 
        va_start(ap, lookup);
-       fields = realtime_arguments_to_fields(ap);
+       if (realtime_arguments_to_fields(ap, &fields)) {
+               res = -1;
+       }
        va_end(ap);
 
-       if (!fields) {
+       if (res) {
                return -1;
        }
 
@@ -3300,6 +3741,55 @@ uint32_done:
                break;
        }
 
+       case PARSE_TIMELEN:
+       {
+               int x = 0;
+               int *result = p_result;
+               int def = result ? *result : 0;
+               int high = INT_MAX;
+               int low = INT_MIN;
+               enum ast_timelen defunit;
+
+               defunit = va_arg(ap, enum ast_timelen);
+               /* optional arguments: default value and/or (low, high) */
+               if (flags & PARSE_DEFAULT) {
+                       def = va_arg(ap, int);
+               }
+               if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) {
+                       low = va_arg(ap, int);
+                       high = va_arg(ap, int);
+               }
+               if (ast_strlen_zero(arg)) {
+                       error = 1;
+                       goto timelen_done;
+               }
+               error = ast_app_parse_timelen(arg, &x, defunit);
+               if (error || x < INT_MIN || x > INT_MAX) {
+                       /* Parse error, or type out of int bounds */
+                       error = 1;
+                       goto timelen_done;
+               }
+               error = (x < low) || (x > high);
+               if (flags & PARSE_RANGE_DEFAULTS) {
+                       if (x < low) {
+                               def = low;
+                       } else if (x > high) {
+                               def = high;
+                       }
+               }
+               if (flags & PARSE_OUT_RANGE) {
+                       error = !error;
+               }
+timelen_done:
+               if (result) {
+                       *result  = error ? def : x;
+               }
+
+               ast_debug(3, "extract timelen from [%s] in [%d, %d] gives [%d](%d)\n",
+                               arg, low, high, result ? *result : x, error);
+               break;
+       }
+
        case PARSE_DOUBLE:
        {
                double *result = p_result;
@@ -3544,12 +4034,17 @@ static void config_shutdown(void)
        AST_LIST_UNLOCK(&cfmtime_head);
 
        ast_cli_unregister_multiple(cli_config, ARRAY_LEN(cli_config));
+
+       clear_config_maps();
+
+       ao2_cleanup(cfg_hooks);
+       cfg_hooks = NULL;
 }
 
 int register_config_cli(void)
 {
        ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
-       ast_register_atexit(config_shutdown);
+       ast_register_cleanup(config_shutdown);
        return 0;
 }
 
@@ -3592,7 +4087,7 @@ void ast_config_hook_unregister(const char *name)
        ao2_find(cfg_hooks, &tmp, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
 }
 
-static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg)
+static void config_hook_exec(const char *filename, const char *module, const struct ast_config *cfg)
 {
        struct ao2_iterator it;
        struct cfg_hook *hook;
@@ -3632,5 +4127,6 @@ int ast_config_hook_register(const char *name,
        hook->module = ast_strdup(module);
 
        ao2_link(cfg_hooks, hook);
+       ao2_ref(hook, -1);
        return 0;
 }