document a nice technique to exit from a block in case of errors.
[asterisk/asterisk.git] / main / config.c
index 472d6fa..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>
-#include <sys/socket.h>                /* for AF_INET */
 #define AST_INCLUDE_GLOB 1
 #ifdef AST_INCLUDE_GLOB
 #if defined(__Darwin__) || defined(__CYGWIN__)
@@ -49,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 ";--"
@@ -162,6 +157,40 @@ static struct ast_comment *ALLOC_COMMENT(const char *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;
@@ -177,14 +206,22 @@ static struct ast_config_engine *config_engine_list;
 
 #define MAX_INCLUDE_LEVEL 10
 
+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;
@@ -216,14 +253,16 @@ struct ast_variable *ast_variable_new(const char *name, const char *value, const
 {
        struct ast_variable *variable;
        int name_len = strlen(name) + 1;        
-
-       if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + strlen(filename) + 1 + sizeof(*variable)))) {
-               variable->name = variable->stuff;
-               variable->value = variable->stuff + name_len;           
-               variable->file = variable->stuff + name_len + strlen(value) + 1;
-               strcpy(variable->name,name);
-               strcpy(variable->value,value);
-               strcpy(variable->file,filename);
+       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;
 }
@@ -479,12 +518,43 @@ void ast_category_append(struct ast_config *config, struct ast_category *categor
        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);
-       if (cat->file)
+       if (cat->file) {
                free(cat->file);
-       
+               cat->file = 0;
+       }
+       ast_destroy_comments(cat);
+       ast_destroy_template_list(cat);
        ast_free(cat);
 }
 
@@ -511,6 +581,12 @@ 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);
@@ -570,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));
 }
@@ -675,7 +754,6 @@ int ast_category_delete(struct ast_config *cfg, const char *category)
        cat = cfg->root;
        while (cat) {
                if (cat->name == category) {
-                       ast_variables_destroy(cat->root);
                        if (prev) {
                                prev->next = cat->next;
                                if (cat == cfg->last)
@@ -685,7 +763,7 @@ int ast_category_delete(struct ast_config *cfg, const char *category)
                                if (cat == cfg->last)
                                        cfg->last = NULL;
                        }
-                       ast_free(cat);
+                       ast_category_destroy(cat);
                        return 0;
                }
                prev = cat;
@@ -696,7 +774,6 @@ int ast_category_delete(struct ast_config *cfg, const char *category)
        cat = cfg->root;
        while (cat) {
                if (!strcasecmp(cat->name, category)) {
-                       ast_variables_destroy(cat->root);
                        if (prev) {
                                prev->next = cat->next;
                                if (cat == cfg->last)
@@ -706,7 +783,7 @@ int ast_category_delete(struct ast_config *cfg, const char *category)
                                if (cat == cfg->last)
                                        cfg->last = NULL;
                        }
-                       ast_free(cat);
+                       ast_category_destroy(cat);
                        return 0;
                }
                prev = cat;
@@ -726,10 +803,9 @@ void ast_config_destroy(struct ast_config *cfg)
 
        cat = cfg->root;
        while (cat) {
-               ast_variables_destroy(cat->root);
                catn = cat;
                cat = cat->next;
-               ast_free(catn);
+               ast_category_destroy(catn);
        }
        ast_free(cfg);
 }
@@ -781,6 +857,12 @@ static void config_cache_attribute(const char *configfile, enum config_cache_att
 
        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);
@@ -796,21 +878,36 @@ static void config_cache_attribute(const char *configfile, enum config_cache_att
        AST_LIST_UNLOCK(&cfmtime_head);
 }
 
-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)
+/*! \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);
@@ -825,6 +922,8 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                        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] ) {
@@ -873,8 +972,9 @@ 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++;
@@ -956,6 +1056,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                }
                c = strchr(cur, '=');
                if (c) {
+                       int object;
                        *c = 0;
                        c++;
                        /* Ignore > in => */
@@ -967,6 +1068,8 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                        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);
@@ -1003,6 +1106,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;
        /*! 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 */
@@ -1048,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;
@@ -1153,6 +1263,12 @@ static struct ast_config *config_text_file_load(const char *database, const char
                                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) == '\\')) {
                                                /* Escaped semicolons aren't comments. */
@@ -1210,7 +1326,7 @@ static struct ast_config *config_text_file_load(const char *database, const char
                                if (process_buf) {
                                        char *buf = ast_strip(process_buf);
                                        if (!ast_strlen_zero(buf)) {
-                                               if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file)) {
+                                               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;
                                                }
@@ -1218,6 +1334,27 @@ 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);
        if (comment) {
@@ -1286,8 +1423,18 @@ static void gen_header(FILE *f1, const char *configfile, const char *fn, const c
        fprintf(f1, ";!\n");
 }
 
-static void set_fn(char *fn, int fn_size, const char *file, const char *configfile)
+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);
@@ -1297,6 +1444,53 @@ static void set_fn(char *fn, int fn_size, const char *file, const char *configfi
                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)
@@ -1308,6 +1502,8 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
        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;
 
        /* reset all the output flags, in case this isn't our first time saving this data */
 
@@ -1322,7 +1518,7 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                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); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
+                       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);
@@ -1331,10 +1527,12 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                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;
                }
        }
 
-       set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
+       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
@@ -1344,18 +1542,20 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                gen_header(f, configfile, fn, generator);
                cat = cfg->root;
                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);
+                       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;
                        }
 
@@ -1372,29 +1572,76 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                }
                        }
                        
+                       insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
                        /* Dump section with any appropriate comment */
                        for (cmt = cat->precomments; cmt; cmt=cmt->next) {
-                               if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
-                                       fprintf(f,"%s", cmt->cmt);
+                               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);
-                       for (cmt = cat->sameline; cmt; cmt=cmt->next) {
+                       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) {
-                               set_fn(fn, sizeof(fn), var->file, configfile);
+                               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;
                                }
                                
@@ -1411,6 +1658,7 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                        }
                                }
                                
+                               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);
@@ -1419,6 +1667,10 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                        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--)
@@ -1426,6 +1678,8 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                }
                                
                                fclose(f);
+                               ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
+                               fi = 0;
                                
                                var = var->next;
                        }
@@ -1436,6 +1690,8 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
        } else {
                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;
        }
 
@@ -1445,12 +1701,15 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
        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);
+                       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;
                        }
                        
@@ -1461,8 +1720,11 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                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;
 }
@@ -1482,7 +1744,7 @@ static void clear_config_maps(void)
        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;
@@ -1533,7 +1795,9 @@ int read_config_maps(void)
        }
 
        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, '\"')))
@@ -1977,37 +2241,42 @@ int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
        return error;
 }
 
-static int config_command(int fd, int argc, char **argv) 
+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 show config mappings\n"
-       "       Shows the filenames to config engines.\n";
-
 static struct ast_cli_entry cli_config[] = {
-       { { "core", "show", "config", "mappings", NULL },
-       config_command, "Display config mappings (file names to config engines)",
-       show_config_help },
+       AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
 };
 
 int register_config_cli()