document a nice technique to exit from a block in case of errors.
[asterisk/asterisk.git] / main / config.c
index ffd58b5..1f29ee1 100644 (file)
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
+#include "asterisk/paths.h"    /* use ast_config_AST_CONFIG_DIR */
+#include "asterisk/network.h"  /* we do some sockaddr manipulation here */
 #include <time.h>
 #include <sys/stat.h>
 #define AST_INCLUDE_GLOB 1
@@ -48,11 +45,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/config.h"
 #include "asterisk/cli.h"
 #include "asterisk/lock.h"
-#include "asterisk/options.h"
-#include "asterisk/logger.h"
 #include "asterisk/utils.h"
 #include "asterisk/channel.h"
 #include "asterisk/app.h"
+#include "asterisk/astobj2.h"
 
 #define MAX_NESTED_COMMENTS 128
 #define COMMENT_START ";--"
@@ -62,6 +58,140 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 static char *extconfig_conf = "extconfig.conf";
 
+
+/*! \brief Structure to keep comments for rewriting configuration files */
+struct ast_comment {
+       struct ast_comment *next;
+       char cmt[0];
+};
+
+/*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
+struct cache_file_include {
+       AST_LIST_ENTRY(cache_file_include) list;
+       char include[0];
+};
+
+struct cache_file_mtime {
+       AST_LIST_ENTRY(cache_file_mtime) list;
+       AST_LIST_HEAD(includes, cache_file_include) includes;
+       unsigned int has_exec:1;
+       time_t mtime;
+       char filename[0];
+};
+
+static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
+
+#define CB_INCR 250
+
+static void CB_INIT(char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size)
+{
+       if (!(*comment_buffer)) {
+               *comment_buffer = ast_malloc(CB_INCR);
+               if (!(*comment_buffer))
+                       return;
+               (*comment_buffer)[0] = 0;
+               *comment_buffer_size = CB_INCR;
+               *lline_buffer = ast_malloc(CB_INCR);
+               if (!(*lline_buffer))
+                       return;
+               (*lline_buffer)[0] = 0;
+               *lline_buffer_size = CB_INCR;
+       } else {
+               (*comment_buffer)[0] = 0;
+               (*lline_buffer)[0] = 0;
+       }
+}
+
+static void  CB_ADD(char **comment_buffer, int *comment_buffer_size, char *str)
+{
+       int rem = *comment_buffer_size - strlen(*comment_buffer) - 1;
+       int siz = strlen(str);
+       if (rem < siz+1) {
+               *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + siz + 1);
+               if (!(*comment_buffer))
+                       return;
+               *comment_buffer_size += CB_INCR+siz+1;
+       }
+       strcat(*comment_buffer,str);
+}
+
+static void  CB_ADD_LEN(char **comment_buffer, int *comment_buffer_size, char *str, int len)
+{
+       int cbl = strlen(*comment_buffer) + 1;
+       int rem = *comment_buffer_size - cbl;
+       if (rem < len+1) {
+               *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + len + 1);
+               if (!(*comment_buffer))
+                       return;
+               *comment_buffer_size += CB_INCR+len+1;
+       }
+       strncat(*comment_buffer,str,len);
+       (*comment_buffer)[cbl+len-1] = 0;
+}
+
+static void  LLB_ADD(char **lline_buffer, int *lline_buffer_size, char *str)
+{
+       int rem = *lline_buffer_size - strlen(*lline_buffer) - 1;
+       int siz = strlen(str);
+       if (rem < siz+1) {
+               *lline_buffer = ast_realloc(*lline_buffer, *lline_buffer_size + CB_INCR + siz + 1);
+               if (!(*lline_buffer)) 
+                       return;
+               *lline_buffer_size += CB_INCR + siz + 1;
+       }
+       strcat(*lline_buffer,str);
+}
+
+static void CB_RESET(char **comment_buffer, char **lline_buffer)  
+{ 
+       (*comment_buffer)[0] = 0; 
+       (*lline_buffer)[0] = 0;
+}
+
+
+static struct ast_comment *ALLOC_COMMENT(const char *buffer)
+{ 
+       struct ast_comment *x;
+       x = ast_calloc(1, sizeof(*x)+strlen(buffer)+1);
+       strcpy(x->cmt, buffer);
+       return x;
+}
+
+/* I need to keep track of each config file, and all its inclusions,
+   so that we can track blank lines in each */
+
+struct inclfile
+{
+       char *fname;
+       int lineno;
+};
+
+static int hash_string(const void *obj, const int flags)
+{
+       char *str = ((struct inclfile*)obj)->fname;
+       int total;
+
+       for (total=0; *str; str++)
+       {
+               unsigned int tmp = total;
+               total <<= 1; /* multiply by 2 */
+               total += tmp; /* multiply by 3 */
+               total <<= 2; /* multiply by 12 */
+               total += tmp; /* multiply by 13 */
+        
+               total += ((unsigned int)(*str));
+       }
+       if (total < 0)
+               total = -total;
+       return total;
+}
+
+static int hashtab_compare_strings(void *a, void *b, int flags)
+{
+       const struct inclfile *ae = a, *be = b;
+       return !strcmp(ae->fname, be->fname) ? CMP_MATCH : 0;
+}
+
 static struct ast_config_map {
        struct ast_config_map *next;
        char *name;
@@ -76,14 +206,22 @@ static struct ast_config_engine *config_engine_list;
 
 #define MAX_INCLUDE_LEVEL 10
 
-struct ast_comment {
-       struct ast_comment *next;
-       char cmt[0];
+struct ast_category_template_instance {
+       char name[80]; /* redundant? */
+       const struct ast_category *inst;
+       AST_LIST_ENTRY(ast_category_template_instance) next;
 };
 
 struct ast_category {
        char name[80];
-       int ignored;                    /* do not let user of the config see this category */
+       int ignored;                    /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
+       int include_level;
+       char *file;                /*!< the file name from whence this declaration was read */
+       int lineno;
+       AST_LIST_HEAD_NOLOCK(template_instance_list, ast_category_template_instance) template_instances;
+       struct ast_comment *precomments;
+       struct ast_comment *sameline;
+       struct ast_comment *trailing; /*!< the last object in the list will get assigned any trailing comments when EOF is hit */
        struct ast_variable *root;
        struct ast_variable *last;
        struct ast_category *next;
@@ -93,26 +231,145 @@ struct ast_config {
        struct ast_category *root;
        struct ast_category *last;
        struct ast_category *current;
-       struct ast_category *last_browse;               /* used to cache the last category supplied via category_browse */
+       struct ast_category *last_browse;     /*!< used to cache the last category supplied via category_browse */
        int include_level;
        int max_include_level;
+       struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
+};
+
+struct ast_config_include {
+       char *include_location_file;     /*!< file name in which the include occurs */
+       int  include_location_lineno;    /*!< lineno where include occurred */
+       int  exec;                       /*!< set to non-zero if itsa #exec statement */
+       char *exec_file;                 /*!< if it's an exec, you'll have both the /var/tmp to read, and the original script */
+       char *included_file;             /*!< file name included */
+       int inclusion_count;             /*!< if the file is included more than once, a running count thereof -- but, worry not,
+                                             we explode the instances and will include those-- so all entries will be unique */
+       int output;                      /*!< a flag to indicate if the inclusion has been output */
+       struct ast_config_include *next; /*!< ptr to next inclusion in the list */
 };
 
-struct ast_variable *ast_variable_new(const char *name, const char *value) 
+struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename) 
 {
        struct ast_variable *variable;
        int name_len = strlen(name) + 1;        
+       int val_len = strlen(value) + 1;        
+       int fn_len = strlen(filename) + 1;      
+
+       if ((variable = ast_calloc(1, name_len + val_len + fn_len + sizeof(*variable)))) {
+               char *dst = variable->stuff;    /* writable space starts here */
+               variable->name = strcpy(dst, name);
+               dst += name_len;
+               variable->value = strcpy(dst, value);
+               dst += val_len;
+               variable->file = strcpy(dst, filename);
+       }
+       return variable;
+}
 
-       if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + sizeof(*variable)))) {
-               variable->name = variable->stuff;
-               variable->value = variable->stuff + name_len;           
-               strcpy(variable->name,name);
-               strcpy(variable->value,value);
+struct ast_config_include *ast_include_new(struct ast_config *conf, const char *from_file, const char *included_file, int is_exec, const char *exec_file, int from_lineno, char *real_included_file_name, int real_included_file_name_size)
+{
+       /* a file should be included ONCE. Otherwise, if one of the instances is changed,
+       then all be changed. -- how do we know to include it? -- Handling modified 
+       instances is possible, I'd have
+       to create a new master for each instance. */
+       struct ast_config_include *inc;
+       struct stat statbuf;
+       
+       inc = ast_include_find(conf, included_file);
+       if (inc) {
+               do {
+                       inc->inclusion_count++;
+                       snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
+               } while (stat(real_included_file_name, &statbuf) == 0);
+               ast_log(LOG_WARNING,"'%s', line %d:  Same File included more than once! This data will be saved in %s if saved back to disk.\n", from_file, from_lineno, real_included_file_name);
+       } else
+               *real_included_file_name = 0;
+       
+       inc = ast_calloc(1,sizeof(struct ast_config_include));
+       inc->include_location_file = ast_strdup(from_file);
+       inc->include_location_lineno = from_lineno;
+       if (!ast_strlen_zero(real_included_file_name))
+               inc->included_file = ast_strdup(real_included_file_name);
+       else
+               inc->included_file = ast_strdup(included_file);
+       
+       inc->exec = is_exec;
+       if (is_exec)
+               inc->exec_file = ast_strdup(exec_file);
+       
+       /* attach this new struct to the conf struct */
+       inc->next = conf->includes;
+       conf->includes = inc;
+       
+       return inc;
+}
+
+void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
+{
+       struct ast_config_include *incl;
+       struct ast_category *cat;
+       struct ast_variable *v;
+       
+       int from_len = strlen(from_file);
+       int to_len = strlen(to_file);
+       
+       if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
+               return;
+       
+       /* the manager code allows you to read in one config file, then
+       write it back out under a different name. But, the new arrangement
+          ties output lines to the file name. So, before you try to write
+       the config file to disk, better riffle thru the data and make sure
+       the file names are changed.
+       */
+       /* file names are on categories, includes (of course), and on variables. So,
+          traverse all this and swap names */
+
+       for (incl = conf->includes; incl; incl=incl->next) {
+               if (strcmp(incl->include_location_file,from_file) == 0) {
+                       if (from_len >= to_len)
+                               strcpy(incl->include_location_file, to_file);
+                       else {
+                               free(incl->include_location_file);
+                               incl->include_location_file = strdup(to_file);
+                       }
+               }
        }
+       for (cat = conf->root; cat; cat = cat->next) {
+               if (strcmp(cat->file,from_file) == 0) {
+                       if (from_len >= to_len)
+                               strcpy(cat->file, to_file);
+                       else {
+                               free(cat->file);
+                               cat->file = strdup(to_file);
+                       }
+               }
+               for (v = cat->root; v; v = v->next) {
+                       if (strcmp(v->file,from_file) == 0) {
+                               if (from_len >= to_len)
+                                       strcpy(v->file, to_file);
+                               else {
+                                       free(v->file);
+                                       v->file = strdup(to_file);
+                               }
+                       }
+               }
+       }
+}
 
-       return variable;
+struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
+{
+       struct ast_config_include *x;
+       for (x=conf->includes;x;x=x->next)
+       {
+               if (strcmp(x->included_file,included_file) == 0)
+                       return x;
+       }
+       return 0;
 }
 
+
 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
 {
        if (!variable)
@@ -122,16 +379,18 @@ void ast_variable_append(struct ast_category *category, struct ast_variable *var
        else
                category->root = variable;
        category->last = variable;
+       while (category->last->next)
+               category->last = category->last->next;
 }
 
 void ast_variables_destroy(struct ast_variable *v)
 {
        struct ast_variable *vn;
 
-       while(v) {
+       while (v) {
                vn = v;
                v = v->next;
-               free(vn);
+               ast_free(vn);
        }
 }
 
@@ -147,9 +406,9 @@ struct ast_variable *ast_variable_browse(const struct ast_config *config, const
        return (cat) ? cat->root : NULL;
 }
 
-char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
+const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
 {
-       char *tmp;
+       const char *tmp;
        tmp = ast_variable_retrieve(cfg, cat, var);
        if (!tmp)
                tmp = ast_variable_retrieve(cfg, "general", var);
@@ -157,7 +416,7 @@ char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var
 }
 
 
-char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
+const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
 {
        struct ast_variable *v;
 
@@ -180,7 +439,7 @@ char *ast_variable_retrieve(const struct ast_config *config, const char *categor
 
 static struct ast_variable *variable_clone(const struct ast_variable *old)
 {
-       struct ast_variable *new = ast_variable_new(old->name, old->value);
+       struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
 
        if (new) {
                new->lineno = old->lineno;
@@ -209,12 +468,14 @@ static void move_variables(struct ast_category *old, struct ast_category *new)
 #endif
 }
 
-struct ast_category *ast_category_new(const char *name) 
+struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) 
 {
        struct ast_category *category;
 
        if ((category = ast_calloc(1, sizeof(*category))))
                ast_copy_string(category->name, name, sizeof(category->name));
+       category->file = strdup(in_file);
+       category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
        return category;
 }
 
@@ -252,14 +513,65 @@ void ast_category_append(struct ast_config *config, struct ast_category *categor
                config->last->next = category;
        else
                config->root = category;
+       category->include_level = config->include_level;
        config->last = category;
        config->current = category;
 }
 
+static void ast_destroy_comments(struct ast_category *cat)
+{
+       struct ast_comment *n, *p;
+       for (p=cat->precomments; p; p=n) {
+               n = p->next;
+               free(p);
+       }
+       for (p=cat->sameline; p; p=n) {
+               n = p->next;
+               free(p);
+       }
+       for (p=cat->trailing; p; p=n) {
+               n = p->next;
+               free(p);
+       }
+       cat->precomments = NULL;
+       cat->sameline = NULL;
+       cat->trailing = NULL;
+}
+
+static void ast_destroy_template_list(struct ast_category *cat)
+{
+       struct ast_category_template_instance *x;
+
+       while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
+               free(x);
+}
+
 void ast_category_destroy(struct ast_category *cat)
 {
        ast_variables_destroy(cat->root);
-       free(cat);
+       if (cat->file) {
+               free(cat->file);
+               cat->file = 0;
+       }
+       ast_destroy_comments(cat);
+       ast_destroy_template_list(cat);
+       ast_free(cat);
+}
+
+static void ast_includes_destroy(struct ast_config_include *incls)
+{
+       struct ast_config_include *incl,*inclnext;
+       
+       for (incl=incls; incl; incl = inclnext) {
+               inclnext = incl->next;
+               if (incl->include_location_file)
+                       free(incl->include_location_file);
+               if (incl->exec_file)
+                       free(incl->exec_file);
+               if (incl->included_file)
+                       free(incl->included_file);
+               free(incl);
+       }
 }
 
 static struct ast_category *next_available_category(struct ast_category *cat)
@@ -269,6 +581,20 @@ static struct ast_category *next_available_category(struct ast_category *cat)
        return cat;
 }
 
+/*! return the first var of a category */
+struct ast_variable *ast_category_first(struct ast_category *cat)
+{
+       return (cat) ? cat->root : NULL;
+}
+
+struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
+{
+       struct ast_category *category = ast_category_get(config, cat);
+       if (category)
+               return category->root;
+       return NULL;
+}
+
 char *ast_category_browse(struct ast_config *config, const char *prev)
 {      
        struct ast_category *cat = NULL;
@@ -307,6 +633,7 @@ struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
 
        v = cat->root;
        cat->root = NULL;
+       cat->last = NULL;
 
        return v;
 }
@@ -319,7 +646,10 @@ void ast_category_rename(struct ast_category *cat, const char *name)
 static void inherit_category(struct ast_category *new, const struct ast_category *base)
 {
        struct ast_variable *var;
-
+       struct ast_category_template_instance *x = ast_calloc(1,sizeof(struct ast_category_template_instance));
+       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));
 }
@@ -333,7 +663,7 @@ struct ast_config *ast_config_new(void)
        return config;
 }
 
-int ast_variable_delete(struct ast_category *category, char *variable, char *match)
+int ast_variable_delete(struct ast_category *category, const char *variable, const char *match)
 {
        struct ast_variable *cur, *prev=NULL, *curn;
        int res = -1;
@@ -382,64 +712,48 @@ int ast_variable_delete(struct ast_category *category, char *variable, char *mat
        return res;
 }
 
-int ast_variable_update(struct ast_category *category, char *variable, char *value, char *match)
+int ast_variable_update(struct ast_category *category, const char *variable, 
+                                               const char *value, const char *match, unsigned int object)
 {
-       struct ast_variable *cur, *prev=NULL, *newer;
-       newer = ast_variable_new(variable, value);
-       if (!newer)
-               return -1;
-       cur = category->root;
-       while (cur) {
-               if (cur->name == variable) {
-                       newer->next = cur->next;
-                       newer->object = cur->object;
-                       if (prev)
-                               prev->next = newer;
-                       else
-                               category->root = newer;
-                       if (category->last == cur)
-                               category->last = newer;
-                       cur->next = NULL;
-                       ast_variables_destroy(cur);
-                       return 0;
-               }
-               prev = cur;
-               cur = cur->next;
-       }
+       struct ast_variable *cur, *prev=NULL, *newer=NULL;
 
-       prev = NULL;
-       cur = category->root;
-       while (cur) {
-               if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) {
-                       newer->next = cur->next;
-                       newer->object = cur->object;
-                       if (prev)
-                               prev->next = newer;
-                       else
-                               category->root = newer;
-                       if (category->last == cur)
-                               category->last = newer;
-                       cur->next = NULL;
-                       ast_variables_destroy(cur);
-                       return 0;
-               }
-               prev = cur;
-               cur = cur->next;
+       for (cur = category->root; cur; prev = cur, cur = cur->next) {
+               if (strcasecmp(cur->name, variable) ||
+                       (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
+                       continue;
+
+               if (!(newer = ast_variable_new(variable, value, cur->file)))
+                       return -1;
+       
+               newer->next = cur->next;
+               newer->object = cur->object || object;
+               if (prev)
+                       prev->next = newer;
+               else
+                       category->root = newer;
+               if (category->last == cur)
+                       category->last = newer;
+
+               cur->next = NULL;
+               ast_variables_destroy(cur);
+
+               return 0;
        }
+
        if (prev)
                prev->next = newer;
        else
                category->root = newer;
+
        return 0;
 }
 
-int ast_category_delete(struct ast_config *cfg, char *category)
+int ast_category_delete(struct ast_config *cfg, const char *category)
 {
        struct ast_category *prev=NULL, *cat;
        cat = cfg->root;
-       while(cat) {
+       while (cat) {
                if (cat->name == category) {
-                       ast_variables_destroy(cat->root);
                        if (prev) {
                                prev->next = cat->next;
                                if (cat == cfg->last)
@@ -449,7 +763,7 @@ int ast_category_delete(struct ast_config *cfg, char *category)
                                if (cat == cfg->last)
                                        cfg->last = NULL;
                        }
-                       free(cat);
+                       ast_category_destroy(cat);
                        return 0;
                }
                prev = cat;
@@ -458,9 +772,8 @@ int ast_category_delete(struct ast_config *cfg, char *category)
 
        prev = NULL;
        cat = cfg->root;
-       while(cat) {
+       while (cat) {
                if (!strcasecmp(cat->name, category)) {
-                       ast_variables_destroy(cat->root);
                        if (prev) {
                                prev->next = cat->next;
                                if (cat == cfg->last)
@@ -470,7 +783,7 @@ int ast_category_delete(struct ast_config *cfg, char *category)
                                if (cat == cfg->last)
                                        cfg->last = NULL;
                        }
-                       free(cat);
+                       ast_category_destroy(cat);
                        return 0;
                }
                prev = cat;
@@ -486,14 +799,15 @@ void ast_config_destroy(struct ast_config *cfg)
        if (!cfg)
                return;
 
+       ast_includes_destroy(cfg->includes);
+
        cat = cfg->root;
-       while(cat) {
-               ast_variables_destroy(cat->root);
+       while (cat) {
                catn = cat;
                cat = cat->next;
-               free(catn);
+               ast_category_destroy(catn);
        }
-       free(cfg);
+       ast_free(cfg);
 }
 
 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
@@ -507,20 +821,93 @@ void ast_config_set_current_category(struct ast_config *cfg, const struct ast_ca
        cfg->current = (struct ast_category *) cat;
 }
 
-static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, int withcomments)
+enum config_cache_attribute_enum {
+       ATTRIBUTE_INCLUDE = 0,
+       ATTRIBUTE_EXEC = 1,
+};
+
+static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename)
+{
+       struct cache_file_mtime *cfmtime;
+       struct cache_file_include *cfinclude;
+       struct stat statbuf = { 0, };
+
+       /* Find our cached entry for this configuration file */
+       AST_LIST_LOCK(&cfmtime_head);
+       AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
+               if (!strcmp(cfmtime->filename, configfile))
+                       break;
+       }
+       if (!cfmtime) {
+               cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1);
+               if (!cfmtime) {
+                       AST_LIST_UNLOCK(&cfmtime_head);
+                       return;
+               }
+               AST_LIST_HEAD_INIT(&cfmtime->includes);
+               strcpy(cfmtime->filename, configfile);
+               /* Note that the file mtime is initialized to 0, i.e. 1970 */
+               AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
+       }
+
+       if (!stat(configfile, &statbuf))
+               cfmtime->mtime = 0;
+       else
+               cfmtime->mtime = statbuf.st_mtime;
+
+       switch (attrtype) {
+       case ATTRIBUTE_INCLUDE:
+               AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
+                       if (!strcmp(cfinclude->include, filename)) {
+                               AST_LIST_UNLOCK(&cfmtime_head);
+                               return;
+                       }
+               }
+               cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
+               if (!cfinclude) {
+                       AST_LIST_UNLOCK(&cfmtime_head);
+                       return;
+               }
+               strcpy(cfinclude->include, filename);
+               AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
+               break;
+       case ATTRIBUTE_EXEC:
+               cfmtime->has_exec = 1;
+               break;
+       }
+       AST_LIST_UNLOCK(&cfmtime_head);
+}
+
+/*! \brief parse one line in the configuration.
+ * We can have a category header       [foo](...)
+ * a directive                         #include / #exec
+ * or a regular line                   name = value
+ */
+static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
+       char *buf, int lineno, const char *configfile, struct ast_flags flags,
+       char **comment_buffer, int *comment_buffer_size,
+       char **lline_buffer, int *lline_buffer_size,
+       const char *suggested_include_file,
+       struct ast_category **last_cat, struct ast_variable **last_var)
 {
        char *c;
        char *cur = buf;
        struct ast_variable *v;
        char cmd[512], exec_file[512];
-       int object, do_exec, do_include;
 
        /* Actually parse the entry */
-       if (cur[0] == '[') {
+       if (cur[0] == '[') { /* A category header */
+               /* format is one of the following:
+                * [foo]        define a new category named 'foo'
+                * [foo](!)     define a new template category named 'foo'
+                * [foo](+)     append to category 'foo', error if foo does not exist.
+                * [foo](a)     define a new category and inherit from template a.
+                *              You can put a comma-separated list of templates and '!' and '+'
+                *              between parentheses, with obvious meaning.
+                */
                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);
@@ -531,9 +918,23 @@ 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))) {
+               if (!(*cat = newcat = ast_category_new(catname, ast_strlen_zero(suggested_include_file)?configfile:suggested_include_file, lineno))) {
                        return -1;
                }
+               (*cat)->lineno = lineno;
+               *last_var = 0;
+               *last_cat = newcat;
+               
+               /* add comments */
+               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
+                       newcat->precomments = ALLOC_COMMENT(*comment_buffer);
+               }
+               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
+                       newcat->sameline = ALLOC_COMMENT(*lline_buffer);
+               }
+               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
+                       CB_RESET(comment_buffer, lline_buffer);
+               
                /* If there are options or categories to inherit from, process them now */
                if (c) {
                        if (!(cur = strchr(c, ')'))) {
@@ -546,8 +947,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                        (*cat)->ignored = 1;
                                } else if (!strcasecmp(cur, "+")) {
                                        *cat = category_get(cfg, catname, 1);
-                                       if (!*cat) {
-                                               ast_config_destroy(cfg);
+                                       if (!(*cat)) {
                                                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);
@@ -572,21 +972,22 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                }
                if (newcat)
                        ast_category_append(cfg, *cat);
-       } else if (cur[0] == '#') {
-               /* A directive */
+       } else if (cur[0] == '#') { /* A directive - #include or #exec */
+               int do_exec, do_include;
+
                cur++;
                c = cur;
-               while(*c && (*c > 32)) c++;
+               while (*c && (*c > 32)) c++;
                if (*c) {
                        *c = '\0';
                        /* Find real argument */
                        c = ast_skip_blanks(c + 1);
-                       if (!*c)
+                       if (!(*c))
                                c = NULL;
                } else 
                        c = NULL;
                do_include = !strcasecmp(cur, "include");
-               if(!do_include)
+               if (!do_include)
                        do_exec = !strcasecmp(cur, "exec");
                else
                        do_exec = 0;
@@ -596,10 +997,15 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                }
                if (do_include || do_exec) {
                        if (c) {
+                               char *cur2;
+                               char real_inclusion_name[256];
+                               struct ast_config_include *inclu;
+                               
                                /* Strip off leading and trailing "'s and <>'s */
-                               while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
+                               while ((*c == '<') || (*c == '>') || (*c == '\"')) c++;
                                /* Get rid of leading mess */
                                cur = c;
+                               cur2 = cur;
                                while (!ast_strlen_zero(cur)) {
                                        c = cur + strlen(cur) - 1;
                                        if ((*c == '>') || (*c == '<') || (*c == '\"'))
@@ -609,18 +1015,26 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                }
                                /* #exec </path/to/executable>
                                   We create a tmp file, then we #include it, then we delete it. */
-                               if (do_exec) { 
+                               if (do_exec) {
+                                       if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
+                                               config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL);
                                        snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self());
                                        snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
                                        ast_safe_system(cmd);
                                        cur = exec_file;
-                               } else
+                               } else {
+                                       if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
+                                               config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur);
                                        exec_file[0] = '\0';
+                               }
                                /* A #include */
-                               do_include = ast_config_internal_load(cur, cfg, withcomments) ? 1 : 0;
-                               if(!ast_strlen_zero(exec_file))
+                               /* record this inclusion */
+                               inclu = ast_include_new(cfg, configfile, cur, do_exec, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
+
+                               do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name) ? 1 : 0;
+                               if (!ast_strlen_zero(exec_file))
                                        unlink(exec_file);
-                               if(!do_include)
+                               if (!do_include)
                                        return 0;
 
                        } else {
@@ -635,13 +1049,14 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                        ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
        } else {
                /* Just a line (variable = value) */
-               if (!*cat) {
+               if (!(*cat)) {
                        ast_log(LOG_WARNING,
                                "parse error: No category context for line %d of %s\n", lineno, configfile);
                        return -1;
                }
                c = strchr(cur, '=');
                if (c) {
+                       int object;
                        *c = 0;
                        c++;
                        /* Ignore > in => */
@@ -650,24 +1065,35 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                c++;
                        } else
                                object = 0;
-                       if ((v = ast_variable_new(ast_strip(cur), ast_strip(c)))) {
+                       if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), *suggested_include_file ? suggested_include_file : configfile))) {
                                v->lineno = lineno;
                                v->object = object;
+                               *last_cat = 0;
+                               *last_var = v;
                                /* Put and reset comments */
                                v->blanklines = 0;
                                ast_variable_append(*cat, v);
+                               /* add comments */
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
+                                       v->precomments = ALLOC_COMMENT(*comment_buffer);
+                               }
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
+                                       v->sameline = ALLOC_COMMENT(*lline_buffer);
+                               }
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
+                                       CB_RESET(comment_buffer, lline_buffer);
+                               
                        } else {
                                return -1;
                        }
                } else {
                        ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
                }
-
        }
        return 0;
 }
 
-static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, int withcomments)
+static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
 {
        char fn[256];
        char buf[8192];
@@ -678,8 +1104,19 @@ static struct ast_config *config_text_file_load(const char *database, const char
        struct ast_category *cat = NULL;
        int count = 0;
        struct stat statbuf;
-       
-       cat = ast_config_get_current_category(cfg);
+       struct cache_file_mtime *cfmtime = NULL;
+       struct cache_file_include *cfinclude;
+       struct ast_variable *last_var = 0;
+       struct ast_category *last_cat = 0;
+       /*! Growable string buffer */
+       char *comment_buffer=0;   /*!< this will be a comment collector.*/
+       int   comment_buffer_size=0;  /*!< the amount of storage so far alloc'd for the comment_buffer */
+
+       char *lline_buffer=0;    /*!< A buffer for stuff behind the ; */
+       int  lline_buffer_size=0;
+
+       if (cfg)
+               cat = ast_config_get_current_category(cfg);
 
        if (filename[0] == '/') {
                ast_copy_string(fn, filename, sizeof(fn));
@@ -687,6 +1124,13 @@ static struct ast_config *config_text_file_load(const char *database, const char
                snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename);
        }
 
+       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
+               CB_INIT(&comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size);
+               if (!lline_buffer || !comment_buffer) {
+                       ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
+                       return NULL;
+               }
+       }
 #ifdef AST_INCLUDE_GLOB
        {
                int glob_ret;
@@ -709,6 +1153,11 @@ static struct ast_config *config_text_file_load(const char *database, const char
                        for (i=0; i<globbuf.gl_pathc; i++) {
                                ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
 #endif
+       /*
+        * The following is not a loop, but just a convenient way to define a block
+        * (using do { } while(0) ), and be able to exit from it with 'continue'
+        * or 'break' in case of errors. Nice trick.
+        */
        do {
                if (stat(fn, &statbuf))
                        continue;
@@ -717,36 +1166,114 @@ static struct ast_config *config_text_file_load(const char *database, const char
                        ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
                        continue;
                }
-               if ((option_verbose > 1) && !option_debug) {
-                       ast_verbose(VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
-                       fflush(stdout);
+
+               if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
+                       /* Find our cached entry for this configuration file */
+                       AST_LIST_LOCK(&cfmtime_head);
+                       AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
+                               if (!strcmp(cfmtime->filename, fn))
+                                       break;
+                       }
+                       if (!cfmtime) {
+                               cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1);
+                               if (!cfmtime)
+                                       continue;
+                               AST_LIST_HEAD_INIT(&cfmtime->includes);
+                               strcpy(cfmtime->filename, fn);
+                               /* Note that the file mtime is initialized to 0, i.e. 1970 */
+                               AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
+                       }
+               }
+
+               if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
+                       /* File is unchanged, what about the (cached) includes (if any)? */
+                       int unchanged = 1;
+                       AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
+                               /* We must glob here, because if we did not, then adding a file to globbed directory would
+                                * incorrectly cause no reload to be necessary. */
+                               char fn2[256];
+#ifdef AST_INCLUDE_GLOB
+                               int glob_ret;
+                               glob_t globbuf = { .gl_offs = 0 };
+#ifdef SOLARIS
+                               glob_ret = glob(cfinclude->include, GLOB_NOCHECK, NULL, &globbuf);
+#else
+                               glob_ret = glob(cfinclude->include, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
+#endif
+                               /* On error, we reparse */
+                               if (glob_ret == GLOB_NOSPACE || glob_ret  == GLOB_ABORTED)
+                                       unchanged = 0;
+                               else  {
+                                       /* loop over expanded files */
+                                       int j;
+                                       for (j = 0; j < globbuf.gl_pathc; j++) {
+                                               ast_copy_string(fn2, globbuf.gl_pathv[j], sizeof(fn2));
+#else
+                                               ast_copy_string(fn2, cfinclude->include);
+#endif
+                                               if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "") == NULL) { /* that last field needs to be looked at in this case... TODO */
+                                                       unchanged = 0;
+                                                       /* One change is enough to short-circuit and reload the whole shebang */
+                                                       break;
+                                               }
+#ifdef AST_INCLUDE_GLOB
+                                       }
+                               }
+#endif
+                       }
+
+                       if (unchanged) {
+                               AST_LIST_UNLOCK(&cfmtime_head);
+                               return CONFIG_STATUS_FILEUNCHANGED;
+                       }
                }
+               if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
+                       AST_LIST_UNLOCK(&cfmtime_head);
+
+               /* If cfg is NULL, then we just want an answer */
+               if (cfg == NULL)
+                       return NULL;
+
+               if (cfmtime)
+                       cfmtime->mtime = statbuf.st_mtime;
+
+               ast_verb(2, "Parsing '%s': ", fn);
+                       fflush(stdout);
                if (!(f = fopen(fn, "r"))) {
-                       if (option_debug)
-                               ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
-                       else if (option_verbose > 1)
-                               ast_verbose( "Not found (%s)\n", strerror(errno));
+                       ast_debug(1, "No file to parse: %s\n", fn);
+                       ast_verb(2, "Not found (%s)\n", strerror(errno));
                        continue;
                }
                count++;
-               if (option_debug)
-                       ast_log(LOG_DEBUG, "Parsing %s\n", fn);
-               else if (option_verbose > 1)
-                       ast_verbose("Found\n");
-               while(!feof(f)) {
+               /* If we get to this point, then we're loading regardless */
+               ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
+               ast_debug(1, "Parsing %s\n", fn);
+               ast_verb(2, "Found\n");
+               while (!feof(f)) {
                        lineno++;
                        if (fgets(buf, sizeof(buf), f)) {
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
+                                       CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
+                                       lline_buffer[0] = 0;        /* erase the lline buffer */
+                               }
+                               
                                new_buf = buf;
-                               if (comment)
+                               if (comment) 
                                        process_buf = NULL;
                                else
                                        process_buf = buf;
+                               
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] && (ast_strlen_zero(buf) || strlen(buf) == strspn(buf," \t\n\r"))) {
+                                       /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
+                                       CB_ADD(&comment_buffer, &comment_buffer_size, "\n");       /* add a newline to the comment buffer */
+                                       continue; /* go get a new line, then */
+                               }
+                               
                                while ((comment_p = strchr(new_buf, COMMENT_META))) {
                                        if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
-                                               /* Yuck, gotta memmove */
-                                               memmove(comment_p - 1, comment_p, strlen(comment_p) + 1);
-                                               new_buf = comment_p;
-                                       } else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
+                                               /* Escaped semicolons aren't comments. */
+                                               new_buf = comment_p + 1;
+                                       } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
                                                /* Meta-Comment start detected ";--" */
                                                if (comment < MAX_NESTED_COMMENTS) {
                                                        *comment_p = '\0';
@@ -768,6 +1295,11 @@ static struct ast_config *config_text_file_load(const char *database, const char
                                                                /* Actually have to move what's left over the top, then continue */
                                                                char *oldptr;
                                                                oldptr = process_buf + strlen(process_buf);
+                                                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
+                                                                       CB_ADD(&comment_buffer, &comment_buffer_size, ";");
+                                                                       CB_ADD_LEN(&comment_buffer, &comment_buffer_size, oldptr+1, new_buf-oldptr-1);
+                                                               }
+                                                               
                                                                memmove(oldptr, new_buf, strlen(new_buf) + 1);
                                                                new_buf = oldptr;
                                                        } else
@@ -777,16 +1309,24 @@ static struct ast_config *config_text_file_load(const char *database, const char
                                                if (!comment) {
                                                        /* If ; is found, and we are not nested in a comment, 
                                                           we immediately stop all comment processing */
+                                                       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
+                                                               LLB_ADD(&lline_buffer, &lline_buffer_size, comment_p);
+                                                       }
                                                        *comment_p = '\0'; 
                                                        new_buf = comment_p;
                                                } else
                                                        new_buf = comment_p + 1;
                                        }
                                }
+                               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf )
+                               {
+                                       CB_ADD(&comment_buffer, &comment_buffer_size, buf);  /* the whole line is a comment, store it */
+                               }
+                               
                                if (process_buf) {
                                        char *buf = ast_strip(process_buf);
                                        if (!ast_strlen_zero(buf)) {
-                                               if (process_text_line(cfg, &cat, buf, lineno, filename, withcomments)) {
+                                               if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file, &last_cat, &last_var)) {
                                                        cfg = NULL;
                                                        break;
                                                }
@@ -794,89 +1334,398 @@ static struct ast_config *config_text_file_load(const char *database, const char
                                }
                        }
                }
+               /* end of file-- anything in a comment buffer? */
+               if (last_cat) {
+                       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] ) {
+                               CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
+                               lline_buffer[0] = 0;        /* erase the lline buffer */
+                               last_cat->trailing = ALLOC_COMMENT(comment_buffer);
+                       }
+               } else if (last_var) {
+                       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] ) {
+                               CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
+                               lline_buffer[0] = 0;        /* erase the lline buffer */
+                               last_var->trailing = ALLOC_COMMENT(comment_buffer);
+                       }
+               } else {
+                       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && (comment_buffer)[0] ) {
+                               ast_debug(1, "Nothing to attach comments to, discarded: %s\n", comment_buffer);
+                       }
+               }
+               if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
+                       CB_RESET(&comment_buffer, &lline_buffer);
+
                fclose(f);              
-       } while(0);
+       } while (0);
        if (comment) {
-               ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]);
+               ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
        }
 #ifdef AST_INCLUDE_GLOB
-                                       if (!cfg)
+                                       if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
                                                break;
                                }
                                globfree(&globbuf);
                        }
                }
 #endif
+
+       if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer) {
+               ast_free(comment_buffer);
+               ast_free(lline_buffer);
+               comment_buffer = NULL;
+               lline_buffer = NULL;
+               comment_buffer_size = 0;
+               lline_buffer_size = 0;
+       }
+       
        if (count == 0)
                return NULL;
 
        return cfg;
 }
 
+
+/* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
+   which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
+   recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
+   be shocked and mystified as to why things are not showing up in the files! 
+
+   Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
+   and line number are stored for each include, plus the name of the file included, so that these statements may be
+   included in the output files on a file_save operation. 
+
+   The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
+   are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
+   the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
+   and a header gets added.
+
+   vars and category heads are output in the order they are stored in the config file. So, if the software
+   shuffles these at all, then the placement of #include directives might get a little mixed up, because the
+   file/lineno data probably won't get changed.
+
+*/
+
+static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
+{
+       char date[256]="";
+       time_t t;
+       time(&t);
+       ast_copy_string(date, ctime(&t), sizeof(date));
+
+       fprintf(f1, ";!\n");
+       fprintf(f1, ";! Automatically generated configuration file\n");
+       if (strcmp(configfile, fn))
+               fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
+       else
+               fprintf(f1, ";! Filename: %s\n", configfile);
+       fprintf(f1, ";! Generator: %s\n", generator);
+       fprintf(f1, ";! Creation Date: %s", date);
+       fprintf(f1, ";!\n");
+}
+
+static void   inclfile_destroy(void *obj)
+{
+       const struct inclfile *o = obj;
+       if (o->fname)
+               free(o->fname);
+}
+
+
+static void set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset, struct inclfile **fi)
+{
+       struct inclfile lookup;
+       
+       if (!file || file[0] == 0) {
+               if (configfile[0] == '/')
+                       ast_copy_string(fn, configfile, fn_size);
+               else
+                       snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
+       } else if (file[0] == '/') 
+               ast_copy_string(fn, file, fn_size);
+       else
+               snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
+       lookup.fname = fn;
+       *fi = ao2_find(fileset, &lookup, OBJ_POINTER);
+       if (!(*fi)) {
+               /* set up a file scratch pad */
+               struct inclfile *fx = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
+               fx->fname = ast_strdup(fn);
+               fx->lineno = 1;
+               *fi = fx;
+               ao2_link(fileset, fx);
+       }
+}
+
+static int count_linefeeds(char *str)
+{
+       int count = 0;
+       while (*str) {
+               if (*str =='\n')
+                       count++;
+               str++;
+       }
+       return count;
+}
+
+static int count_linefeeds_in_comments(struct ast_comment *x)
+{
+       int count = 0;
+       while (x)
+       {
+               count += count_linefeeds(x->cmt);
+               x = x->next;
+       }
+       return count;
+}
+
+static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
+{
+       int precomment_lines = count_linefeeds_in_comments(precomments);
+       int i;
+
+       /* I don't have to worry about those ;! comments, they are
+          stored in the precomments, but not printed back out.
+          I did have to make sure that comments following
+          the ;! header comments were not also deleted in the process */
+       for (i=fi->lineno;i<lineno - precomment_lines; i++) {
+               fprintf(fp,"\n");
+       }
+       fi->lineno = lineno+1; /* Advance the file lineno */
+}
+
 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
 {
        FILE *f;
        char fn[256];
-       char date[256]="";
-       time_t t;
        struct ast_variable *var;
        struct ast_category *cat;
+       struct ast_comment *cmt;
+       struct ast_config_include *incl;
        int blanklines = 0;
+       struct ao2_container *fileset = ao2_container_alloc(180000, hash_string, hashtab_compare_strings);
+       struct inclfile *fi = 0;
 
-       if (configfile[0] == '/') {
-               ast_copy_string(fn, configfile, sizeof(fn));
-       } else {
-               snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
+       /* reset all the output flags, in case this isn't our first time saving this data */
+
+       for (incl=cfg->includes; incl; incl = incl->next)
+               incl->output = 0;
+
+       /* 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)
+       {
+               if (!incl->exec) { /* leave the execs alone -- we'll write out the #exec directives, but won't zero out the include files or exec files*/
+                       FILE *f1;
+
+                       set_fn(fn, sizeof(fn), incl->included_file, configfile, fileset, &fi); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
+                       f1 = fopen(fn,"w");
+                       if (f1) {
+                               gen_header(f1, configfile, fn, generator);
+                               fclose(f1); /* 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)", fn, strerror(errno));
+                       }
+                       ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                       fi = 0;
+               }
        }
-       time(&t);
-       ast_copy_string(date, ctime(&t), sizeof(date));
+
+       set_fn(fn, sizeof(fn), 0, configfile, fileset, &fi); /* just set fn to absolute ver of configfile */
 #ifdef __CYGWIN__      
        if ((f = fopen(fn, "w+"))) {
 #else
        if ((f = fopen(fn, "w"))) {
 #endif     
-               if ((option_verbose > 1) && !option_debug)
-                       ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
-               fprintf(f, ";!\n");
-               fprintf(f, ";! Automatically generated configuration file\n");
-               fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
-               fprintf(f, ";! Generator: %s\n", generator);
-               fprintf(f, ";! Creation Date: %s", date);
-               fprintf(f, ";!\n");
+               ast_verb(2, "Saving '%s': ", fn);
+               gen_header(f, configfile, fn, generator);
                cat = cfg->root;
-               while(cat) {
+               fclose(f);
+               ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+               
+               /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
+               /* since each var, cat, and associated comments can come from any file, we have to be 
+                  mobile, and open each file, print, and close it on an entry-by-entry basis */
+
+               while (cat) {
+                       set_fn(fn, sizeof(fn), cat->file, configfile, fileset, &fi);
+                       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)", fn, strerror(errno));
+                               ao2_ref(fileset, -1);
+                               return -1;
+                       }
+
+                       /* dump any includes that happen before this category header */
+                       for (incl=cfg->includes; incl; incl = incl->next) {
+                               if (strcmp(incl->include_location_file, cat->file) == 0){
+                                       if (cat->lineno > incl->include_location_lineno && !incl->output) {
+                                               if (incl->exec)
+                                                       fprintf(f,"#exec \"%s\"\n", incl->exec_file);
+                                               else
+                                                       fprintf(f,"#include \"%s\"\n", incl->included_file);
+                                               incl->output = 1;
+                                       }
+                               }
+                       }
+                       
+                       insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
                        /* Dump section with any appropriate comment */
-                       fprintf(f, "\n[%s]\n", cat->name);
+                       for (cmt = cat->precomments; cmt; cmt=cmt->next) {
+                               char *cmtp = cmt->cmt;
+                               while (*cmtp == ';' && *(cmtp+1) == '!') {
+                                       char *cmtp2 = strchr(cmtp+1, '\n');
+                                       if (cmtp2)
+                                               cmtp = cmtp2+1;
+                                       else cmtp = 0;
+                               }
+                               if (cmtp)
+                                       fprintf(f,"%s", cmtp);
+                       }
+                       if (!cat->precomments)
+                               fprintf(f,"\n");
+                       fprintf(f, "[%s]", cat->name);
+                       if (cat->ignored)
+                               fprintf(f, "(!)");
+                       if (!AST_LIST_EMPTY(&cat->template_instances)) {
+                               struct ast_category_template_instance *x;
+                               fprintf(f, "(");
+                               AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
+                                       fprintf(f,"%s",x->name);
+                                       if (x != AST_LIST_LAST(&cat->template_instances))
+                                               fprintf(f,",");
+                               }
+                               fprintf(f, ")");
+                       }
+                       for(cmt = cat->sameline; cmt; cmt=cmt->next)
+                       {
+                               fprintf(f,"%s", cmt->cmt);
+                       }
+                       if (!cat->sameline)
+                               fprintf(f,"\n");
+                       for (cmt = cat->trailing; cmt; cmt=cmt->next) {
+                               if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
+                                       fprintf(f,"%s", cmt->cmt);
+                       }
+                       fclose(f);
+                       ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                       fi = 0;
+                       
                        var = cat->root;
-                       while(var) {
+                       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 (found)
+                                               break;
+                               }
+                               if (found) {
+                                       var = var->next;
+                                       continue;
+                               }
+                               set_fn(fn, sizeof(fn), var->file, configfile, fileset, &fi);
+                               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)", fn, strerror(errno));
+                                       ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                                       fi = 0;
+                                       ao2_ref(fileset, -1);
+                                       return -1;
+                               }
+                               
+                               /* dump any includes that happen before this category header */
+                               for (incl=cfg->includes; incl; incl = incl->next) {
+                                       if (strcmp(incl->include_location_file, var->file) == 0){
+                                               if (var->lineno > incl->include_location_lineno && !incl->output) {
+                                                       if (incl->exec)
+                                                               fprintf(f,"#exec \"%s\"\n", incl->exec_file);
+                                                       else
+                                                               fprintf(f,"#include \"%s\"\n", incl->included_file);
+                                                       incl->output = 1;
+                                               }
+                                       }
+                               }
+                               
+                               insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
+                               for (cmt = var->precomments; cmt; cmt=cmt->next) {
+                                       if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
+                                               fprintf(f,"%s", cmt->cmt);
+                               }
                                if (var->sameline) 
-                                       fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
+                                       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);
+                               for (cmt = var->trailing; cmt; cmt=cmt->next) {
+                                       if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
+                                               fprintf(f,"%s", cmt->cmt);
+                               }
                                if (var->blanklines) {
                                        blanklines = var->blanklines;
                                        while (blanklines--)
                                                fprintf(f, "\n");
                                }
-                                       
+                               
+                               fclose(f);
+                               ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                               fi = 0;
+                               
                                var = var->next;
                        }
-#if 0
-                       /* Put an empty line */
-                       fprintf(f, "\n");
-#endif
                        cat = cat->next;
                }
-               if ((option_verbose > 1) && !option_debug)
-                       ast_verbose("Saved\n");
+               if (!option_debug)
+                       ast_verb(2, "Saved\n");
        } else {
-               if (option_debug)
-                       printf("Unable to open for writing: %s\n", fn);
-               else if (option_verbose > 1)
-                       printf( "Unable to write (%s)", strerror(errno));
+               ast_debug(1, "Unable to open for writing: %s\n", fn);
+               ast_verb(2, "Unable to write (%s)", strerror(errno));
+               ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+               ao2_ref(fileset, -1);
                return -1;
        }
-       fclose(f);
+
+       /* Now, for files with trailing #include/#exec statements,
+          we have to make sure every entry is output */
+
+       for (incl=cfg->includes; incl; incl = incl->next) {
+               if (!incl->output) {
+                       /* open the respective file */
+                       set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset, &fi);
+                       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)", fn, strerror(errno));
+                               ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                               fi = 0;
+                               ao2_ref(fileset, -1);
+                               return -1;
+                       }
+                       
+                       /* output the respective include */
+                       if (incl->exec)
+                               fprintf(f,"#exec \"%s\"\n", incl->exec_file);
+                       else
+                               fprintf(f,"#include \"%s\"\n", incl->included_file);
+                       fclose(f);
+                       incl->output = 1;
+                       ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                       fi = 0;
+               }
+       }
+       ao2_ref(fileset, -1); /* this should destroy the hash container */
+                               
        return 0;
 }
 
@@ -889,13 +1738,13 @@ static void clear_config_maps(void)
        while (config_maps) {
                map = config_maps;
                config_maps = config_maps->next;
-               free(map);
+               ast_free(map);
        }
                
        ast_mutex_unlock(&config_lock);
 }
 
-static int append_mapping(char *name, char *driver, char *database, char *table)
+static int append_mapping(const char *name, const char *driver, const char *database, const char *table)
 {
        struct ast_config_map *map;
        int length;
@@ -922,9 +1771,7 @@ static int append_mapping(char *name, char *driver, char *database, char *table)
        }
        map->next = config_maps;
 
-       if (option_verbose > 1)
-               ast_verbose(VERBOSE_PREFIX_2 "Binding %s to %s/%s/%s\n",
-                           map->name, map->driver, map->database, map->table ? map->table : map->name);
+       ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
 
        config_maps = map;
        return 0;
@@ -934,22 +1781,28 @@ int read_config_maps(void)
 {
        struct ast_config *config, *configtmp;
        struct ast_variable *v;
-       char *driver, *table, *database, *stringp;
+       char *driver, *table, *database, *stringp, *tmp;
+       struct ast_flags flags = { 0 };
 
        clear_config_maps();
 
        configtmp = ast_config_new();
        configtmp->max_include_level = 1;
-       config = ast_config_internal_load(extconfig_conf, configtmp, 0);
+       config = ast_config_internal_load(extconfig_conf, configtmp, flags, "");
        if (!config) {
                ast_config_destroy(configtmp);
                return 0;
        }
 
        for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
-               stringp = v->value;
+               char buf[512];
+               ast_copy_string(buf, v->value, sizeof(buf));
+               stringp = buf;
                driver = strsep(&stringp, ",");
 
+               if ((tmp = strchr(stringp, '\"')))
+                       stringp = tmp;
+
                /* check if the database text starts with a double quote */
                if (*stringp == '"') {
                        stringp++;
@@ -1076,12 +1929,12 @@ static struct ast_config_engine text_file_engine = {
        .load_func = config_text_file_load,
 };
 
-struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments)
+struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
 {
        char db[256];
        char table[256];
        struct ast_config_engine *loader = &text_file_engine;
-       struct ast_config *result;
+       struct ast_config *result; 
 
        if (cfg->include_level == cfg->max_include_level) {
                ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
@@ -1105,15 +1958,17 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con
                }
        }
 
-       result = loader->load_func(db, table, filename, cfg, withcomments);
+       result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file);
 
-       if (result)
+       if (result && result != CONFIG_STATUS_FILEUNCHANGED)
                result->include_level--;
+       else
+               cfg->include_level--;
 
        return result;
 }
 
-struct ast_config *ast_config_load(const char *filename)
+struct ast_config *ast_config_load(const char *filename, struct ast_flags flags)
 {
        struct ast_config *cfg;
        struct ast_config *result;
@@ -1122,43 +1977,65 @@ struct ast_config *ast_config_load(const char *filename)
        if (!cfg)
                return NULL;
 
-       result = ast_config_internal_load(filename, cfg, 0);
-       if (!result)
+       result = ast_config_internal_load(filename, cfg, flags, "");
+       if (!result || result == CONFIG_STATUS_FILEUNCHANGED)
                ast_config_destroy(cfg);
 
        return result;
 }
 
-struct ast_config *ast_config_load_with_comments(const char *filename)
+static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
 {
-       struct ast_config *cfg;
-       struct ast_config *result;
+       struct ast_config_engine *eng;
+       char db[256]="";
+       char table[256]="";
+       struct ast_variable *res=NULL;
 
-       cfg = ast_config_new();
-       if (!cfg)
-               return NULL;
+       eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+       if (eng && eng->realtime_func) 
+               res = eng->realtime_func(db, table, ap);
 
-       result = ast_config_internal_load(filename, cfg, 1);
-       if (!result)
-               ast_config_destroy(cfg);
+       return res;
+}
 
-       return result;
+struct ast_variable *ast_load_realtime_all(const char *family, ...)
+{
+       struct ast_variable *res;
+       va_list ap;
+
+       va_start(ap, family);
+       res = ast_load_realtime_helper(family, ap);
+       va_end(ap);
+
+       return res;
 }
 
 struct ast_variable *ast_load_realtime(const char *family, ...)
 {
-       struct ast_config_engine *eng;
-       char db[256]="";
-       char table[256]="";
-       struct ast_variable *res=NULL;
+       struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
        va_list ap;
 
        va_start(ap, family);
-       eng = find_engine(family, db, sizeof(db), table, sizeof(table));
-       if (eng && eng->realtime_func) 
-               res = eng->realtime_func(db, table, ap);
+       res = ast_load_realtime_helper(family, ap);
        va_end(ap);
 
+       /* Eliminate blank entries */
+       for (cur = res; cur; cur = cur->next) {
+               if (freeme) {
+                       ast_free(freeme);
+                       freeme = NULL;
+               }
+
+               if (ast_strlen_zero(cur->value)) {
+                       if (prev)
+                               prev->next = cur->next;
+                       else
+                               res = cur->next;
+                       freeme = cur;
+               } else {
+                       prev = cur;
+               }
+       }
        return res;
 }
 
@@ -1174,6 +2051,12 @@ int ast_check_realtime(const char *family)
 
 }
 
+/*! \brief Check if there's any realtime engines loaded */
+int ast_realtime_enabled()
+{
+       return config_maps ? 1 : 0;
+}
+
 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
 {
        struct ast_config_engine *eng;
@@ -1208,42 +2091,192 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo
        return res;
 }
 
-static int config_command(int fd, int argc, char **argv) 
+int ast_store_realtime(const char *family, ...) {
+       struct ast_config_engine *eng;
+       int res = -1;
+       char db[256]="";
+       char table[256]="";
+       va_list ap;
+
+       va_start(ap, family);
+       eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+       if (eng && eng->store_func) 
+               res = eng->store_func(db, table, ap);
+       va_end(ap);
+
+       return res;
+}
+
+int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) {
+       struct ast_config_engine *eng;
+       int res = -1;
+       char db[256]="";
+       char table[256]="";
+       va_list ap;
+
+       va_start(ap, lookup);
+       eng = find_engine(family, db, sizeof(db), table, sizeof(table));
+       if (eng && eng->destroy_func) 
+               res = eng->destroy_func(db, table, keyfield, lookup, ap);
+       va_end(ap);
+
+       return res;
+}
+
+/*! \brief Helper function to parse arguments
+ * See documentation in config.h
+ */
+int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
+        void *p_result, ...)
+{
+       va_list ap;
+       int error = 0;
+
+       va_start(ap, p_result);
+       switch (flags & PARSE_TYPE) {
+       case PARSE_INT32:
+           {
+               int32_t *result = p_result;
+               int32_t x, def = result ? *result : 0,
+                       high = (int32_t)0x7fffffff,
+                       low  = (int32_t)0x80000000;
+               /* optional argument: first default value, then range */
+               if (flags & PARSE_DEFAULT)
+                       def = va_arg(ap, int32_t);
+               if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+                       /* range requested, update bounds */
+                       low = va_arg(ap, int32_t);
+                       high = va_arg(ap, int32_t);
+               }
+               x = strtol(arg, NULL, 0);
+               error = (x < low) || (x > high);
+               if (flags & PARSE_OUT_RANGE)
+                       error = !error;
+               if (result)
+                       *result  = error ? def : x;
+               ast_debug(3,
+                       "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
+                       arg, low, high,
+                       result ? *result : x, error);
+               break;
+           }
+
+       case PARSE_UINT32:
+           {
+               uint32_t *result = p_result;
+               uint32_t x, def = result ? *result : 0,
+                       low = 0, high = (uint32_t)~0;
+               /* optional argument: first default value, then range */
+               if (flags & PARSE_DEFAULT)
+                       def = va_arg(ap, uint32_t);
+               if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+                       /* range requested, update bounds */
+                       low = va_arg(ap, uint32_t);
+                       high = va_arg(ap, uint32_t);
+               }
+               x = strtoul(arg, NULL, 0);
+               error = (x < low) || (x > high);
+               if (flags & PARSE_OUT_RANGE)
+                       error = !error;
+               if (result)
+                       *result  = error ? def : x;
+               ast_debug(3,
+                       "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
+                       arg, low, high,
+                       result ? *result : x, error);
+               break;
+           }
+
+       case PARSE_INADDR:
+           {
+               char *port, *buf;
+               struct sockaddr_in _sa_buf;     /* buffer for the result */
+               struct sockaddr_in *sa = p_result ?
+                       (struct sockaddr_in *)p_result : &_sa_buf;
+               /* default is either the supplied value or the result itself */
+               struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
+                       va_arg(ap, struct sockaddr_in *) : sa;
+               struct hostent *hp;
+               struct ast_hostent ahp;
+
+               bzero(&_sa_buf, sizeof(_sa_buf)); /* clear buffer */
+               /* duplicate the string to strip away the :port */
+               port = ast_strdupa(arg);
+               buf = strsep(&port, ":");
+               sa->sin_family = AF_INET;       /* assign family */
+               /*
+                * honor the ports flag setting, assign default value
+                * in case of errors or field unset.
+                */
+               flags &= PARSE_PORT_MASK; /* the only flags left to process */
+               if (port) {
+                       if (flags == PARSE_PORT_FORBID) {
+                               error = 1;      /* port was forbidden */
+                               sa->sin_port = def->sin_port;
+                       } else if (flags == PARSE_PORT_IGNORE)
+                               sa->sin_port = def->sin_port;
+                       else /* accept or require */
+                               sa->sin_port = htons(strtol(port, NULL, 0));
+               } else {
+                       sa->sin_port = def->sin_port;
+                       if (flags == PARSE_PORT_REQUIRE)
+                               error = 1;
+               }
+               /* Now deal with host part, even if we have errors before. */
+               hp = ast_gethostbyname(buf, &ahp);
+               if (hp) /* resolved successfully */
+                       memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
+               else {
+                       error = 1;
+                       sa->sin_addr = def->sin_addr;
+               }
+               ast_debug(3,
+                       "extract inaddr from [%s] gives [%s:%d](%d)\n",
+                       arg, ast_inet_ntoa(sa->sin_addr),
+                       ntohs(sa->sin_port), error);
+               break;
+           }
+       }
+       va_end(ap);
+       return error;
+}
+
+static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_config_engine *eng;
        struct ast_config_map *map;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "core show config mappings";
+               e->usage =
+                       "Usage: core show config mappings\n"
+                       "       Shows the filenames to config engines.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
        
        ast_mutex_lock(&config_lock);
 
-       ast_cli(fd, "\n\n");
+       ast_cli(a->fd, "\n\n");
        for (eng = config_engine_list; eng; eng = eng->next) {
-               ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
+               ast_cli(a->fd, "\nConfig Engine: %s\n", eng->name);
                for (map = config_maps; map; map = map->next)
                        if (!strcasecmp(map->driver, eng->name)) {
-                               ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
+                               ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
                                        map->table ? map->table : map->name);
                        }
        }
-       ast_cli(fd,"\n\n");
+       ast_cli(a->fd,"\n\n");
        
        ast_mutex_unlock(&config_lock);
 
-       return 0;
+       return CLI_SUCCESS;
 }
 
-static char show_config_help[] =
-       "Usage: core list config mappings\n"
-       "       Shows the filenames to config engines.\n";
-
-static struct ast_cli_entry cli_show_config_mappings_deprecated = {
-       { "show", "config", "mappings", NULL },
-       config_command, NULL,
-       NULL };
-
 static struct ast_cli_entry cli_config[] = {
-       { { "core", "list", "config", "mappings", NULL },
-       config_command, "Display config mappings (file names to config engines)",
-       show_config_help, NULL, &cli_show_config_mappings_deprecated },
+       AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
 };
 
 int register_config_cli()