This code was in team/murf/bug8684-trunk; it should fix bug 8684 in trunk. I didn...
authorSteve Murphy <murf@digium.com>
Wed, 29 Aug 2007 20:55:40 +0000 (20:55 +0000)
committerSteve Murphy <murf@digium.com>
Wed, 29 Aug 2007 20:55:40 +0000 (20:55 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@81361 65c4cc65-6c06-0410-ace0-fbb531ad65f3

18 files changed:
apps/app_directory.c
apps/app_minivm.c
apps/app_parkandannounce.c
apps/app_voicemail.c
channels/chan_iax2.c
channels/chan_sip.c
channels/chan_skinny.c
channels/iax2-parser.c
include/asterisk/config.h
main/channel.c
main/config.c
main/http.c
main/manager.c
pbx/pbx_spool.c
res/res_config_odbc.c
res/res_config_pgsql.c
res/res_config_sqlite.c
utils/extconf.c

index 896f9cb..05460c4 100644 (file)
@@ -386,7 +386,7 @@ static struct ast_config *realtime_directory(char *context)
        /* Does the context exist within the config file? If not, make one */
        cat = ast_category_get(cfg, context);
        if (!cat) {
-               cat = ast_category_new(context);
+               cat = ast_category_new(context, "", 99999);
                if (!cat) {
                        ast_log(LOG_WARNING, "Out of memory\n");
                        ast_config_destroy(cfg);
@@ -402,7 +402,7 @@ static struct ast_config *realtime_directory(char *context)
                snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
                         fullname ? fullname : "",
                         hidefromdir ? hidefromdir : "no");
-               var = ast_variable_new(mailbox, tmp);
+               var = ast_variable_new(mailbox, tmp, "");
                if (var)
                        ast_variable_append(cat, var);
                else
index 30fbdc7..5fc1d53 100644 (file)
@@ -2141,7 +2141,7 @@ static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
                        if (varname && (varval = strchr(varname, '='))) {
                                *varval = '\0';
                                varval++;
-                               if ((tmpvar = ast_variable_new(varname, varval))) {
+                               if ((tmpvar = ast_variable_new(varname, varval, ""))) {
                                        tmpvar->next = vmu->chanvars;
                                        vmu->chanvars = tmpvar;
                                }
index b6ca3f5..51841d8 100644 (file)
@@ -130,7 +130,7 @@ static int parkandannounce_exec(struct ast_channel *chan, void *data)
 
        snprintf(buf, sizeof(buf), "%d", lot);
        oh.parent_channel = chan;
-       oh.vars = ast_variable_new("_PARKEDAT", buf);
+       oh.vars = ast_variable_new("_PARKEDAT", buf, "");
        dchan = __ast_request_and_dial(dialtech, AST_FORMAT_SLINEAR, args.dial, 30000, &outstate, chan->cid.cid_num, chan->cid.cid_name, &oh);
 
        if (dchan) {
index 6c3d1e5..2d8d9b9 100644 (file)
@@ -960,7 +960,7 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
                        if (!strcasecmp(category, vmu->mailbox)) {
                                if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
                                        ast_debug(3, "looks like we need to make vmsecret!\n");
-                                       var = ast_variable_new("vmsecret", newpassword);
+                                       var = ast_variable_new("vmsecret", newpassword, "");
                                } 
                                new = alloca(strlen(newpassword)+1);
                                sprintf(new, "%s", newpassword);
index 73bac91..bc6f686 100644 (file)
@@ -5225,7 +5225,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                /* We found our match (use the first) */
                /* copy vars */
                for (v = user->vars ; v ; v = v->next) {
-                       if((tmpvar = ast_variable_new(v->name, v->value))) {
+                       if((tmpvar = ast_variable_new(v->name, v->value, v->file))) {
                                tmpvar->next = iaxs[callno]->vars; 
                                iaxs[callno]->vars = tmpvar;
                        }
@@ -9741,7 +9741,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                                if (varname && (varval = strchr(varname,'='))) {
                                        *varval = '\0';
                                        varval++;
-                                       if((tmpvar = ast_variable_new(varname, varval))) {
+                                       if((tmpvar = ast_variable_new(varname, varval, ""))) {
                                                tmpvar->next = user->vars; 
                                                user->vars = tmpvar;
                                        }
index b28cb0b..3325c96 100644 (file)
@@ -10028,7 +10028,7 @@ static struct ast_variable *copy_vars(struct ast_variable *src)
        struct ast_variable *res = NULL, *tmp, *v = NULL;
 
        for (v = src ; v ; v = v->next) {
-               if ((tmp = ast_variable_new(v->name, v->value))) {
+               if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
                        tmp->next = res;
                        res = tmp;
                }
@@ -17137,7 +17137,7 @@ static struct ast_variable *add_var(const char *buf, struct ast_variable *list)
        
        if ((varval = strchr(varname,'='))) {
                *varval++ = '\0';
-               if ((tmpvar = ast_variable_new(varname, varval))) {
+               if ((tmpvar = ast_variable_new(varname, varval, ""))) {
                        tmpvar->next = list;
                        list = tmpvar;
                }
index f8e22d9..2cc0917 100644 (file)
@@ -1463,7 +1463,7 @@ static struct ast_variable *add_var(const char *buf, struct ast_variable *list)
 
        if ((varval = strchr(varname,'='))) {
                *varval++ = '\0';
-               if ((tmpvar = ast_variable_new(varname, varval))) {
+               if ((tmpvar = ast_variable_new(varname, varval, ""))) {
                        tmpvar->next = list;
                        list = tmpvar;
                }
index 1c52cca..7357fce 100644 (file)
@@ -917,7 +917,7 @@ int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
                                        int len = strlen(var2->value) + strlen(tmp2) + 1;
                                        char *tmp3 = alloca(len);
                                        snprintf(tmp3, len, "%s%s", var2->value, tmp2);
-                                       var = ast_variable_new(tmp, tmp3);
+                                       var = ast_variable_new(tmp, tmp3, var2->file);
                                        var->next = var2->next;
                                        if (prev)
                                                prev->next = var;
@@ -928,7 +928,7 @@ int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
                                }
                        }
                        if (!var2) {
-                               var = ast_variable_new(tmp, tmp2);
+                               var = ast_variable_new(tmp, tmp2, "");
                                var->next = ies->vars;
                                ies->vars = var;
                        }
index bbea0c4..7ab52f1 100644 (file)
@@ -52,6 +52,7 @@ enum {
 struct ast_variable {
        char *name;
        char *value;
+       char *file;
        int lineno;
        int object;             /*!< 0 for variable, 1 for object */
        int blanklines;         /*!< Number of blanklines following entry */
@@ -61,7 +62,7 @@ struct ast_variable {
        char stuff[0];
 };
 
-typedef struct ast_config *config_load_func(const char *database, const char *table, const char *configfile, struct ast_config *config, struct ast_flags flags);
+typedef struct ast_config *config_load_func(const char *database, const char *table, const char *configfile, struct ast_config *config, struct ast_flags flags, const char *suggested_include_file);
 typedef struct ast_variable *realtime_var_get(const char *database, const char *table, va_list ap);
 typedef struct ast_config *realtime_multi_get(const char *database, const char *table, va_list ap);
 typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
@@ -242,23 +243,25 @@ struct ast_category *ast_config_get_current_category(const struct ast_config *cf
 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat);
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var);
 
-struct ast_category *ast_category_new(const char *name);
+struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno);
 void ast_category_append(struct ast_config *config, struct ast_category *cat);
 int ast_category_delete(struct ast_config *cfg, const char *category);
 void ast_category_destroy(struct ast_category *cat);
 struct ast_variable *ast_category_detach_variables(struct ast_category *cat);
 void ast_category_rename(struct ast_category *cat, const char *name);
 
-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_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);
+struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file);
+void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file);
 void ast_variable_append(struct ast_category *category, struct ast_variable *variable);
 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match);
 int ast_variable_update(struct ast_category *category, const char *variable, 
-       const char *value, const char *match, unsigned int object);
+                                               const char *value, const char *match, unsigned int object);
 
 int config_text_file_save(const char *filename, const struct ast_config *cfg, const char *generator);
 
-struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, struct ast_flags flags);
-
+struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl_file);
 /*! \brief Support code to parse config file arguments
  *
  * The function ast_parse_arg() provides a generic interface to parse
index 1b4971e..2a7b45d 100644 (file)
@@ -177,10 +177,10 @@ struct ast_variable *ast_channeltype_list(void)
        struct ast_variable *var=NULL, *prev = NULL;
        AST_LIST_TRAVERSE(&backends, cl, list) {
                if (prev)  {
-                       if ((prev->next = ast_variable_new(cl->tech->type, cl->tech->description)))
+                       if ((prev->next = ast_variable_new(cl->tech->type, cl->tech->description, "")))
                                prev = prev->next;
                } else {
-                       var = ast_variable_new(cl->tech->type, cl->tech->description);
+                       var = ast_variable_new(cl->tech->type, cl->tech->description, "");
                        prev = var;
                }
        }
index c0e1669..17faa50 100644 (file)
@@ -180,7 +180,9 @@ static struct ast_config_engine *config_engine_list;
 struct ast_category {
        char name[80];
        int ignored;                    /*!< do not let user of the config see this category */
-       int include_level;      
+       int include_level;
+       char *file;                /*!< the file name from whence this declaration was read */
+       int lineno;
        struct ast_comment *precomments;
        struct ast_comment *sameline;
        struct ast_variable *root;
@@ -192,26 +194,141 @@ 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;        
 
-       if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + sizeof(*variable)))) {
+       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);
        }
-
        return variable;
 }
 
+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;
+       
+       inc = ast_include_find(conf, included_file);
+       if (inc)
+       {
+               inc->inclusion_count++;
+               snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
+               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);
+                               }
+                       }
+               }
+       }
+}
+
+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)
@@ -281,7 +398,7 @@ const char *ast_variable_retrieve(const struct ast_config *config, const char *c
 
 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;
@@ -310,12 +427,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;
 }
 
@@ -361,9 +480,28 @@ void ast_category_append(struct ast_config *config, struct ast_category *categor
 void ast_category_destroy(struct ast_category *cat)
 {
        ast_variables_destroy(cat->root);
+       if (cat->file)
+               free(cat->file);
+       
        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)
 {
        for (; cat && cat->ignored; cat = cat->next);
@@ -494,13 +632,10 @@ int ast_variable_delete(struct ast_category *category, const char *variable, con
 }
 
 int ast_variable_update(struct ast_category *category, const char *variable, 
-       const char *value, const char *match, unsigned int object)
+                                               const char *value, const char *match, unsigned int object)
 {
        struct ast_variable *cur, *prev=NULL, *newer;
 
-       if (!(newer = ast_variable_new(variable, value)))
-               return -1;
-       
        newer->object = object;
 
        for (cur = category->root; cur; prev = cur, cur = cur->next) {
@@ -508,6 +643,9 @@ int ast_variable_update(struct ast_category *category, const char *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)
@@ -584,6 +722,8 @@ 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);
@@ -657,7 +797,7 @@ static void config_cache_attribute(const char *configfile, enum config_cache_att
 }
 
 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)
+                                                        char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size, const char *suggested_include_file)
 {
        char *c;
        char *cur = buf;
@@ -681,9 +821,11 @@ 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;
+               
                /* add comments */
                if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
                        newcat->precomments = ALLOC_COMMENT(*comment_buffer);
@@ -755,10 +897,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++;
                                /* Get rid of leading mess */
                                cur = c;
+                               cur2 = cur;
                                while (!ast_strlen_zero(cur)) {
                                        c = cur + strlen(cur) - 1;
                                        if ((*c == '>') || (*c == '<') || (*c == '\"'))
@@ -781,7 +928,10 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                        exec_file[0] = '\0';
                                }
                                /* A #include */
-                               do_include = ast_config_internal_load(cur, cfg, flags) ? 1 : 0;
+                               /* 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)
@@ -814,7 +964,7 @@ 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;
                                /* Put and reset comments */
@@ -840,7 +990,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
        return 0;
 }
 
-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)
+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];
@@ -951,7 +1101,7 @@ static struct ast_config *config_text_file_load(const char *database, const char
 #else
                                                ast_copy_string(fn2, cfinclude->include);
 #endif
-                                               if (config_text_file_load(NULL, NULL, fn2, NULL, flags) == NULL) {
+                                               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;
@@ -1058,7 +1208,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)) {
+                                               if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file)) {
                                                        cfg = NULL;
                                                        break;
                                                }
@@ -1095,60 +1245,171 @@ static struct ast_config *config_text_file_load(const char *database, const char
        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 set_fn(char *fn, int fn_size, const char *file, const char *configfile)
+{
+       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);
+}
+
 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;
 
-       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); /* 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));
+                       }
+               }
        }
-       time(&t);
-       ast_copy_string(date, ctime(&t), sizeof(date));
+
+       set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
 #ifdef __CYGWIN__      
        if ((f = fopen(fn, "w+"))) {
 #else
        if ((f = fopen(fn, "w"))) {
 #endif     
                ast_verb(2, "Saving '%s': ", fn);
-               fprintf(f, ";!\n");
-               fprintf(f, ";! Automatically generated configuration file\n");
-               if (strcmp(configfile, fn))
-                       fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
-               else
-                       fprintf(f, ";! Filename: %s\n", configfile);
-               fprintf(f, ";! Generator: %s\n", generator);
-               fprintf(f, ";! Creation Date: %s", date);
-               fprintf(f, ";!\n");
+               gen_header(f, configfile, fn, generator);
                cat = cfg->root;
+               fclose(f);
+               
+               /* 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) {
-                       /* Dump section with any appropriate comment */
-                       for (cmt = cat->precomments; cmt; cmt=cmt->next)
+                       set_fn(fn, sizeof(fn), cat->file, configfile);
+                       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));
+                               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;
+                                       }
+                               }
+                       }
+                       
+                       /* 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);
                        }
                        if (!cat->precomments)
                                fprintf(f,"\n");
                        fprintf(f, "[%s]", cat->name);
-                       for (cmt = cat->sameline; cmt; cmt=cmt->next)
-                       {
+                       for (cmt = cat->sameline; cmt; cmt=cmt->next) {
                                fprintf(f,"%s", cmt->cmt);
                        }
                        if (!cat->sameline)
                                fprintf(f,"\n");
+                       fclose(f);
+                       
                        var = cat->root;
                        while (var) {
-                               for (cmt = var->precomments; cmt; cmt=cmt->next)
+                               set_fn(fn, sizeof(fn), var->file, configfile);
+                               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));
+                                       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;
+                                               }
+                                       }
+                               }
+                               
+                               for (cmt = var->precomments; cmt; cmt=cmt->next) {
                                        if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
                                                fprintf(f,"%s", cmt->cmt);
                                }
@@ -1161,13 +1422,11 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                                        while (blanklines--)
                                                fprintf(f, "\n");
                                }
-                                       
+                               
+                               fclose(f);
+                               
                                var = var->next;
                        }
-#if 0
-                       /* Put an empty line */
-                       fprintf(f, "\n");
-#endif
                        cat = cat->next;
                }
                if (!option_debug)
@@ -1177,7 +1436,32 @@ int config_text_file_save(const char *configfile, const struct ast_config *cfg,
                ast_verb(2, "Unable to write (%s)", strerror(errno));
                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);
+                       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));
+                               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;
+               }
+       }
+                               
        return 0;
 }
 
@@ -1240,7 +1524,7 @@ int read_config_maps(void)
 
        configtmp = ast_config_new();
        configtmp->max_include_level = 1;
-       config = ast_config_internal_load(extconfig_conf, configtmp, flags);
+       config = ast_config_internal_load(extconfig_conf, configtmp, flags, "");
        if (!config) {
                ast_config_destroy(configtmp);
                return 0;
@@ -1379,7 +1663,7 @@ 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, struct ast_flags flags)
+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];
@@ -1408,7 +1692,7 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con
                }
        }
 
-       result = loader->load_func(db, table, filename, cfg, flags);
+       result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file);
 
        if (result && result != CONFIG_STATUS_FILEUNCHANGED)
                result->include_level--;
@@ -1427,7 +1711,7 @@ struct ast_config *ast_config_load(const char *filename, struct ast_flags flags)
        if (!cfg)
                return NULL;
 
-       result = ast_config_internal_load(filename, cfg, flags);
+       result = ast_config_internal_load(filename, cfg, flags, "");
        if (!result || result == CONFIG_STATUS_FILEUNCHANGED)
                ast_config_destroy(cfg);
 
index b4aac26..1a5da9c 100644 (file)
@@ -567,7 +567,7 @@ static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *s
                        else 
                                val = "";
                        ast_uri_decode(var);
-                       if ((v = ast_variable_new(var, val))) {
+                       if ((v = ast_variable_new(var, val, ""))) {
                                if (vars)
                                        prev->next = v;
                                else
@@ -778,7 +778,7 @@ static void *httpd_helper_thread(void *data)
                        value = ast_skip_blanks(value);
                        if (ast_strlen_zero(value))
                                continue;
-                       var = ast_variable_new(name, value);
+                       var = ast_variable_new(name, value, "");
                        if (!var)
                                continue;
                        var->next = headers;
@@ -818,7 +818,7 @@ static void *httpd_helper_thread(void *data)
                        vval++;
                if ( (l = strlen(vval)) )
                        vval[l - 1] = '\0';     /* trim trailing quote */
-               var = ast_variable_new(vname, vval);
+               var = ast_variable_new(vname, vval, "");
                if (var) {
                        if (prev)
                                prev->next = var;
index 9abb6c5..2a775a0 100644 (file)
@@ -791,7 +791,7 @@ struct ast_variable *astman_get_variables(const struct message *m)
                        strsep(&val, "=");
                        if (!val || ast_strlen_zero(var))
                                continue;
-                       cur = ast_variable_new(var, val);
+                       cur = ast_variable_new(var, val, "");
                        cur->next = head;
                        head = cur;
                }
@@ -1224,7 +1224,7 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 }
 
 /* helper function for action_updateconfig */
-static void handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg)
+static void handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
 {
        int x;
        char hdr[40];
@@ -1253,7 +1253,7 @@ static void handle_updates(struct mansession *s, const struct message *m, struct
                match = astman_get_header(m, hdr);
                if (!strcasecmp(action, "newcat")) {
                        if (!ast_strlen_zero(cat)) {
-                               category = ast_category_new(cat);
+                               category = ast_category_new(cat, dfn, 99999);
                                if (category) {
                                        ast_category_append(cfg, category);
                                }
@@ -1276,7 +1276,7 @@ static void handle_updates(struct mansession *s, const struct message *m, struct
                } else if (!strcasecmp(action, "append")) {
                        if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) &&
                                (category = ast_category_get(cfg, cat)) &&
-                               (v = ast_variable_new(var, value))){
+                               (v = ast_variable_new(var, value, dfn))){
                                if (object || (match && !strcasecmp(match, "object")))
                                        v->object = 1;
                                ast_variable_append(category, v);
@@ -1315,7 +1315,8 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Config file not found");
                return 0;
        }
-       handle_updates(s, m, cfg);
+       handle_updates(s, m, cfg, dfn);
+       ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
        res = config_text_file_save(dfn, cfg, "Manager");
        ast_config_destroy(cfg);
        if (res) {
index d90967b..544d434 100644 (file)
@@ -219,7 +219,7 @@ static int apply_outgoing(struct outgoing *o, char *fn, FILE *f)
                                        c2 = c;
                                        strsep(&c2, "=");
                                        if (c2) {
-                                               var = ast_variable_new(c, c2);
+                                               var = ast_variable_new(c, c2, fn);
                                                if (var) {
                                                        var->next = o->vars;
                                                        o->vars = var;
index 1faf28d..6ce00ff 100644 (file)
@@ -219,11 +219,11 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl
                        chunk = strsep(&stringp, ";");
                        if (!ast_strlen_zero(ast_strip(chunk))) {
                                if (prev) {
-                                       prev->next = ast_variable_new(coltitle, chunk);
+                                       prev->next = ast_variable_new(coltitle, chunk, "");
                                        if (prev->next)
                                                prev = prev->next;
                                } else 
-                                       prev = var = ast_variable_new(coltitle, chunk);
+                                       prev = var = ast_variable_new(coltitle, chunk, "");
                        }
                }
        }
@@ -334,7 +334,7 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
                        ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
                        continue;
                }
-               cat = ast_category_new("");
+               cat = ast_category_new("","",99999);
                if (!cat) {
                        ast_log(LOG_WARNING, "Out of memory!\n");
                        continue;
@@ -366,7 +366,7 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char *
                                if (!ast_strlen_zero(ast_strip(chunk))) {
                                        if (initfield && !strcmp(initfield, coltitle))
                                                ast_category_rename(cat, chunk);
-                                       var = ast_variable_new(coltitle, chunk);
+                                       var = ast_variable_new(coltitle, chunk, "");
                                        ast_variable_append(cat, var);
                                }
                        }
@@ -625,7 +625,7 @@ static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
        return sth;
 }
 
-static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags)
+static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl)
 {
        struct ast_variable *new_v;
        struct ast_category *cur_cat;
@@ -682,7 +682,7 @@ static struct ast_config *config_odbc(const char *database, const char *table, c
 
        while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
                if (!strcmp (q.var_name, "#include")) {
-                       if (!ast_config_internal_load(q.var_val, cfg, loader_flags)) {
+                       if (!ast_config_internal_load(q.var_val, cfg, loader_flags, "")) {
                                SQLFreeHandle(SQL_HANDLE_STMT, stmt);
                                ast_odbc_release_obj(obj);
                                return NULL;
@@ -690,7 +690,7 @@ static struct ast_config *config_odbc(const char *database, const char *table, c
                        continue;
                } 
                if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
-                       cur_cat = ast_category_new(q.category);
+                       cur_cat = ast_category_new(q.category, "", 99999);
                        if (!cur_cat) {
                                ast_log(LOG_WARNING, "Out of memory!\n");
                                break;
@@ -700,7 +700,7 @@ static struct ast_config *config_odbc(const char *database, const char *table, c
                        ast_category_append(cfg, cur_cat);
                }
 
-               new_v = ast_variable_new(q.var_name, q.var_val);
+               new_v = ast_variable_new(q.var_name, q.var_val, "");
                ast_variable_append(cur_cat, new_v);
        }
 
index 8bd2322..dc1d8a0 100644 (file)
@@ -175,12 +175,12 @@ static struct ast_variable *realtime_pgsql(const char *database, const char *tab
                                        chunk = strsep(&stringp, ";");
                                        if (!ast_strlen_zero(ast_strip(chunk))) {
                                                if (prev) {
-                                                       prev->next = ast_variable_new(fieldnames[i], chunk);
+                                                       prev->next = ast_variable_new(fieldnames[i], chunk, "");
                                                        if (prev->next) {
                                                                prev = prev->next;
                                                        }
                                                } else {
-                                                       prev = var = ast_variable_new(fieldnames[i], chunk);
+                                                       prev = var = ast_variable_new(fieldnames[i], chunk, "");
                                                }
                                        }
                                }
@@ -313,7 +313,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
 
                for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
                        var = NULL;
-                       if (!(cat = ast_category_new("")))
+                       if (!(cat = ast_category_new("","",99999)))
                                continue;
                        for (i = 0; i < numFields; i++) {
                                stringp = PQgetvalue(result, rowIndex, i);
@@ -323,7 +323,7 @@ static struct ast_config *realtime_multi_pgsql(const char *database, const char
                                                if (initfield && !strcmp(initfield, fieldnames[i])) {
                                                        ast_category_rename(cat, chunk);
                                                }
-                                               var = ast_variable_new(fieldnames[i], chunk);
+                                               var = ast_variable_new(fieldnames[i], chunk, "");
                                                ast_variable_append(cat, var);
                                        }
                                }
@@ -615,8 +615,8 @@ static int destroy_pgsql(const char *database, const char *table, const char *ke
 
 
 static struct ast_config *config_pgsql(const char *database, const char *table,
-                                          const char *file, struct ast_config *cfg,
-                                          struct ast_flags flags)
+                                                                          const char *file, struct ast_config *cfg,
+                                                                          struct ast_flags flags, const char *suggested_incl)
 {
        PGresult *result = NULL;
        long num_rows;
@@ -681,7 +681,7 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
                        char *field_var_val = PQgetvalue(result, rowIndex, 2);
                        char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
                        if (!strcmp(field_var_name, "#include")) {
-                               if (!ast_config_internal_load(field_var_val, cfg, flags)) {
+                               if (!ast_config_internal_load(field_var_val, cfg, flags, "")) {
                                        PQclear(result);
                                        ast_mutex_unlock(&pgsql_lock);
                                        return NULL;
@@ -690,14 +690,14 @@ static struct ast_config *config_pgsql(const char *database, const char *table,
                        }
 
                        if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
-                               cur_cat = ast_category_new(field_category);
+                               cur_cat = ast_category_new(field_category, "", 99999);
                                if (!cur_cat)
                                        break;
                                strcpy(last, field_category);
                                last_cat_metric = atoi(field_cat_metric);
                                ast_category_append(cfg, cur_cat);
                        }
-                       new_v = ast_variable_new(field_var_name, field_var_val);
+                       new_v = ast_variable_new(field_var_name, field_var_val, "");
                        ast_variable_append(cur_cat, new_v);
                }
        } else {
index 34912ea..2f9cded 100644 (file)
@@ -305,9 +305,8 @@ static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames);
  * \retval NULL if an error occurred
  * \see add_cfg_entry()
  */
-static struct ast_config * config_handler(const char *database,
-       const char *table, const char *file,
-       struct ast_config *cfg, struct ast_flags flags);
+static struct ast_config * config_handler(const char *database, const char *table, const char *file,
+struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl);
 
 /*!
  * \brief Helper function to parse a va_list object into 2 dynamic arrays of
@@ -736,7 +735,7 @@ static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
        args = arg;
 
        if (!args->cat_name || strcmp(args->cat_name, argv[RES_SQLITE_CONFIG_CATEGORY])) {
-               args->cat = ast_category_new(argv[RES_SQLITE_CONFIG_CATEGORY]);
+               args->cat = ast_category_new(argv[RES_SQLITE_CONFIG_CATEGORY], "", 99999);
 
                if (!args->cat) {
                        ast_log(LOG_WARNING, "Unable to allocate category\n");
@@ -755,7 +754,7 @@ static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
        }
 
        var = ast_variable_new(argv[RES_SQLITE_CONFIG_VAR_NAME],
-                argv[RES_SQLITE_CONFIG_VAR_VAL]);
+                                                  argv[RES_SQLITE_CONFIG_VAR_VAL], "");
 
        if (!var) {
                ast_log(LOG_WARNING, "Unable to allocate variable");
@@ -767,8 +766,8 @@ static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
        return 0;
 }
 
-static struct ast_config *config_handler(const char *database, 
-       const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags)
+static struct ast_config *config_handler(const char *database, const char *table, const char *file,
+struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl)
 {
        struct cfg_entry_args args;
        char *errormsg;
@@ -856,7 +855,7 @@ static int add_rt_cfg_entry(void *arg, int argc, char **argv, char **columnNames
                if (!argv[i])
                        continue;
 
-               if (!(var = ast_variable_new(columnNames[i], argv[i])))
+               if (!(var = ast_variable_new(columnNames[i], argv[i], "")))
                        return 1;
 
                if (!args->var)
@@ -990,7 +989,7 @@ static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv, char **colum
                return 1;
        }
 
-       if (!(cat = ast_category_new(cat_name))) {
+       if (!(cat = ast_category_new(cat_name, "", 99999))) {
                ast_log(LOG_WARNING, "Unable to allocate category\n");
                return 1;
        }
@@ -1001,7 +1000,7 @@ static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv, char **colum
                if (!argv[i] || !strcmp(args->initfield, columnNames[i]))
                        continue;
 
-               if (!(var = ast_variable_new(columnNames[i], argv[i]))) {
+               if (!(var = ast_variable_new(columnNames[i], argv[i], ""))) {
                        ast_log(LOG_WARNING, "Unable to allocate variable\n");
                        return 1;
                }
index 0552113..0d30cc9 100644 (file)
@@ -620,6 +620,8 @@ struct ast_category {
        char name[80];
        int ignored;                    /*!< do not let user of the config see this category */
        int include_level;      
+    char *file;                /*!< the file name from whence this declaration was read */
+    int lineno;
        struct ast_comment *precomments;
        struct ast_comment *sameline;
        struct ast_variable *root;
@@ -634,9 +636,22 @@ struct ast_config {
        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 */
 };
 
-typedef struct ast_config *config_load_func(const char *database, const char *table, const char *configfile, struct ast_config *config, int withcomments);
+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 */
+};
+
+typedef struct ast_config *config_load_func(const char *database, const char *table, const char *configfile, struct ast_config *config, int withcomments, const char *suggested_include_file);
 typedef struct ast_variable *realtime_var_get(const char *database, const char *table, va_list ap);
 typedef struct ast_config *realtime_multi_get(const char *database, const char *table, va_list ap);
 typedef int realtime_update(const char *database, const char *table, const char *keyfield, const char *entity, va_list ap);
@@ -653,11 +668,89 @@ struct ast_config_engine {
 
 static struct ast_config_engine *config_engine_list;
 
+/* taken from strings.h */
+
+static force_inline int ast_strlen_zero(const char *s)
+{
+       return (!s || (*s == '\0'));
+}
+
+#define S_OR(a, b)     (!ast_strlen_zero(a) ? (a) : (b))
+
+AST_INLINE_API(
+void ast_copy_string(char *dst, const char *src, size_t size),
+{
+       while (*src && size) {
+               *dst++ = *src++;
+               size--;
+       }
+       if (__builtin_expect(!size, 0))
+               dst--;
+       *dst = '\0';
+}
+)
+
+AST_INLINE_API(
+char *ast_skip_blanks(const char *str),
+{
+       while (*str && *str < 33)
+               str++;
+       return (char *)str;
+}
+)
+
+/*!
+  \brief Trims trailing whitespace characters from a string.
+  \param ast_trim_blanks function being used
+  \param str the input string
+  \return a pointer to the modified string
+ */
+AST_INLINE_API(
+char *ast_trim_blanks(char *str),
+{
+       char *work = str;
+
+       if (work) {
+               work += strlen(work) - 1;
+               /* It's tempting to only want to erase after we exit this loop, 
+                  but since ast_trim_blanks *could* receive a constant string
+                  (which we presumably wouldn't have to touch), we shouldn't
+                  actually set anything unless we must, and it's easier just
+                  to set each position to \0 than to keep track of a variable
+                  for it */
+               while ((work >= str) && *work < 33)
+                       *(work--) = '\0';
+       }
+       return str;
+}
+)
+
+/*!
+  \brief Strip leading/trailing whitespace from a string.
+  \param s The string to be stripped (will be modified).
+  \return The stripped string.
+
+  This functions strips all leading and trailing whitespace
+  characters from the input string, and returns a pointer to
+  the resulting string. The string is modified in place.
+*/
+AST_INLINE_API(
+char *ast_strip(char *s),
+{
+       s = ast_skip_blanks(s);
+       if (s)
+               ast_trim_blanks(s);
+       return s;
+} 
+)
+
+
 /* from config.h */
 
 struct ast_variable {
        char *name;
        char *value;
+       char *file;
        int lineno;
        int object;             /*!< 0 for variable, 1 for object */
        int blanklines;         /*!< Number of blanklines following entry */
@@ -668,31 +761,137 @@ struct ast_variable {
 };
 
 static const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable);
-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, int withcomments, const char *suggested_include_file);
 
 struct ast_config *localized_config_load_with_comments(const char *filename);
 static char *ast_category_browse(struct ast_config *config, const char *prev);
 static struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category);
 static void ast_variables_destroy(struct ast_variable *v);
 static void ast_config_destroy(struct ast_config *cfg);
+static 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);
+static struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file);
+void localized_ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file);
 
-static struct ast_variable *ast_variable_new(const char *name, const char *value);
+static struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename);
 
-static struct ast_variable *ast_variable_new(const char *name, const char *value) 
+static 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;        
 
-       if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + sizeof(*variable)))) {
+       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->value + strlen(value) + 1;           
                strcpy(variable->name,name);
                strcpy(variable->value,value);
+               strcpy(variable->file,filename);
        }
 
        return variable;
 }
 
+static 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;
+    
+       inc = ast_include_find(conf, included_file);
+       if (inc)
+       {
+               inc->inclusion_count++;
+               snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
+               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 localized_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);
+                               }
+                       }
+               }
+       }
+}
+
+static 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;
+}
+
+
 static void ast_variable_append(struct ast_category *category, struct ast_variable *variable);
 
 static void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
@@ -768,7 +967,7 @@ static const char *ast_variable_retrieve(const struct ast_config *config, const
 
 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;
@@ -791,6 +990,22 @@ static void ast_variables_destroy(struct ast_variable *v)
        }
 }
 
+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 void ast_config_destroy(struct ast_config *cfg)
 {
        struct ast_category *cat, *catn;
@@ -798,6 +1013,8 @@ static 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);
@@ -2431,83 +2648,6 @@ static int clearglobalvars_config = 0;
 static void pbx_substitute_variables_helper(struct ast_channel *c,const char *cp1,char *cp2,int count);
 
 
-/* taken from strings.h */
-
-static force_inline int ast_strlen_zero(const char *s)
-{
-       return (!s || (*s == '\0'));
-}
-
-#define S_OR(a, b)     (!ast_strlen_zero(a) ? (a) : (b))
-
-AST_INLINE_API(
-void ast_copy_string(char *dst, const char *src, size_t size),
-{
-       while (*src && size) {
-               *dst++ = *src++;
-               size--;
-       }
-       if (__builtin_expect(!size, 0))
-               dst--;
-       *dst = '\0';
-}
-)
-
-AST_INLINE_API(
-char *ast_skip_blanks(const char *str),
-{
-       while (*str && *str < 33)
-               str++;
-       return (char *)str;
-}
-)
-
-/*!
-  \brief Trims trailing whitespace characters from a string.
-  \param ast_trim_blanks function being used
-  \param str the input string
-  \return a pointer to the modified string
- */
-AST_INLINE_API(
-char *ast_trim_blanks(char *str),
-{
-       char *work = str;
-
-       if (work) {
-               work += strlen(work) - 1;
-               /* It's tempting to only want to erase after we exit this loop, 
-                  but since ast_trim_blanks *could* receive a constant string
-                  (which we presumably wouldn't have to touch), we shouldn't
-                  actually set anything unless we must, and it's easier just
-                  to set each position to \0 than to keep track of a variable
-                  for it */
-               while ((work >= str) && *work < 33)
-                       *(work--) = '\0';
-       }
-       return str;
-}
-)
-
-/*!
-  \brief Strip leading/trailing whitespace from a string.
-  \param s The string to be stripped (will be modified).
-  \return The stripped string.
-
-  This functions strips all leading and trailing whitespace
-  characters from the input string, and returns a pointer to
-  the resulting string. The string is modified in place.
-*/
-AST_INLINE_API(
-char *ast_strip(char *s),
-{
-       s = ast_skip_blanks(s);
-       if (s)
-               ast_trim_blanks(s);
-       return s;
-} 
-)
-
-
 /* stolen from callerid.c */
 
 /*! \brief Clean up phone string
@@ -3160,12 +3300,12 @@ static struct ast_config_engine *find_engine(const char *family, char *database,
                                ret = eng;
                }
        }
-
+       
        
        /* if we found a mapping, but the engine is not available, then issue a warning */
        if (map && !ret)
                ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
-
+       
        return ret;
 }
 
@@ -3176,15 +3316,17 @@ struct ast_category *ast_config_get_current_category(const struct ast_config *cf
        return cfg->current;
 }
 
-static struct ast_category *ast_category_new(const char *name);
+static struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno);
 
-static struct ast_category *ast_category_new(const char *name) 
+static 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));
-       return category;
+       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;
 }
 
 struct ast_category *localized_category_get(const struct ast_config *config, const char *category_name);
@@ -3236,6 +3378,9 @@ static void ast_category_destroy(struct ast_category *cat);
 static void ast_category_destroy(struct ast_category *cat)
 {
        ast_variables_destroy(cat->root);
+       if (cat->file)
+               free(cat->file);
+       
        free(cat);
 }
 
@@ -3245,9 +3390,9 @@ static struct ast_config_engine text_file_engine = {
 };
 
 
-static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments);
+static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments, const char *suggested_incl_file);
 
-static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments)
+static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments, const char *suggested_incl_file)
 {
        char db[256];
        char table[256];
@@ -3279,7 +3424,7 @@ static struct ast_config *ast_config_internal_load(const char *filename, struct
                }
        }
 
-       result = loader->load_func(db, table, filename, cfg, withcomments);
+       result = loader->load_func(db, table, filename, cfg, withcomments, suggested_incl_file);
        /* silence is golden 
           ast_log(LOG_WARNING, "finished internal loading file %s level=%d\n", filename, cfg->include_level);
        */
@@ -3291,7 +3436,7 @@ static struct ast_config *ast_config_internal_load(const char *filename, struct
 }
 
 
-static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, int withcomments)
+static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, int withcomments, const char *suggested_include_file)
 {
        char *c;
        char *cur = buf;
@@ -3315,9 +3460,11 @@ 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;
+        
                /* add comments */
                if (withcomments && comment_buffer && comment_buffer[0] ) {
                        newcat->precomments = ALLOC_COMMENT(comment_buffer);
@@ -3390,10 +3537,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++;
                                /* Get rid of leading mess */
                                cur = c;
+                               cur2 = cur;
                                while (!ast_strlen_zero(cur)) {
                                        c = cur + strlen(cur) - 1;
                                        if ((*c == '>') || (*c == '<') || (*c == '\"'))
@@ -3413,7 +3565,10 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                /* A #include */
                                /* ast_log(LOG_WARNING, "Reading in included file %s withcomments=%d\n", cur, withcomments); */
                                
-                               do_include = ast_config_internal_load(cur, cfg, withcomments) ? 1 : 0;
+                               /* 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, withcomments, real_inclusion_name) ? 1 : 0;
                                if(!ast_strlen_zero(exec_file))
                                        unlink(exec_file);
                                if(!do_include)
@@ -3447,7 +3602,7 @@ 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), configfile))) {
                                v->lineno = lineno;
                                v->object = object;
                                /* Put and reset comments */
@@ -3489,7 +3644,7 @@ void localized_use_conf_dir(void)
 }
 
 
-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, int withcomments, const char *suggested_include_file)
 {
        char fn[256];
        char buf[8192];
@@ -3635,7 +3790,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, filename, withcomments)) {
+                                               if (process_text_line(cfg, &cat, buf, lineno, filename, withcomments, suggested_include_file)) {
                                                        cfg = NULL;
                                                        break;
                                                }
@@ -3695,7 +3850,7 @@ struct ast_config *localized_config_load(const char *filename)
        if (!cfg)
                return NULL;
 
-       result = ast_config_internal_load(filename, cfg, 0);
+       result = ast_config_internal_load(filename, cfg, 0, "");
        if (!result)
                ast_config_destroy(cfg);
 
@@ -3713,7 +3868,7 @@ struct ast_config *localized_config_load_with_comments(const char *filename)
        if (!cfg)
                return NULL;
 
-       result = ast_config_internal_load(filename, cfg, 1);
+       result = ast_config_internal_load(filename, cfg, 1, "");
        if (!result)
                ast_config_destroy(cfg);
 
@@ -3769,26 +3924,94 @@ void ast_config_set_current_category(struct ast_config *cfg, const struct ast_ca
        cfg->current = (struct ast_category *) cat;
 }
 
+/* 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 set_fn(char *fn, int fn_size, const char *file, const char *configfile)
+{
+       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);
+}
+
 int localized_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator);
 
 int localized_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;
-
-       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); /* 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_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
+                       }
+               }
        }
-       time(&t);
-       ast_copy_string(date, ctime(&t), sizeof(date));
+       
+       set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
 #ifdef __CYGWIN__      
        if ((f = fopen(fn, "w+"))) {
 #else
@@ -3796,36 +4019,76 @@ int localized_config_text_file_save(const char *configfile, const struct ast_con
 #endif     
                if (option_verbose > 1)
                        ast_verbose(VERBOSE_PREFIX_2 "Saving '%s': ", fn);
-               fprintf(f, ";!\n");
-               fprintf(f, ";! Automatically generated configuration file\n");
-               if (strcmp(configfile, fn))
-                       fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
-               else
-                       fprintf(f, ";! Filename: %s\n", configfile);
-               fprintf(f, ";! Generator: %s\n", generator);
-               fprintf(f, ";! Creation Date: %s", date);
-               fprintf(f, ";!\n");
+
+               gen_header(f, configfile, fn, generator);
                cat = cfg->root;
+               fclose(f);
+        
+               /* 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) {
-                       /* Dump section with any appropriate comment */
-                       for (cmt = cat->precomments; cmt; cmt=cmt->next)
+                       set_fn(fn, sizeof(fn), cat->file, configfile);
+                       f = fopen(fn, "a");
+                       if (!f)
                        {
+                               ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
+                               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;
+                                       }
+                               }
+                       }
+            
+                       /* 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);
                        }
                        if (!cat->precomments)
                                fprintf(f,"\n");
                        fprintf(f, "[%s]", cat->name);
-                       for(cmt = cat->sameline; cmt; cmt=cmt->next)
-                       {
+                       for(cmt = cat->sameline; cmt; cmt=cmt->next) {
                                fprintf(f,"%s", cmt->cmt);
                        }
                        if (!cat->sameline)
                                fprintf(f,"\n");
+                       fclose(f);
+            
                        var = cat->root;
                        while(var) {
-                               for (cmt = var->precomments; cmt; cmt=cmt->next)
+                               set_fn(fn, sizeof(fn), var->file, configfile);
+                               f = fopen(fn, "a");
+                               if (!f)
                                {
+                                       ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
+                                       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;
+                                               }
+                                       }
+                               }
+                
+                               for (cmt = var->precomments; cmt; cmt=cmt->next) {
                                        if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
                                                fprintf(f,"%s", cmt->cmt);
                                }
@@ -3838,13 +4101,12 @@ int localized_config_text_file_save(const char *configfile, const struct ast_con
                                        while (blanklines--)
                                                fprintf(f, "\n");
                                }
-                                       
+                               
+                               fclose(f);
+                
+                               
                                var = var->next;
                        }
-#if 0
-                       /* Put an empty line */
-                       fprintf(f, "\n");
-#endif
                        cat = cat->next;
                }
                if ((option_verbose > 1) && !option_debug)
@@ -3856,7 +4118,31 @@ int localized_config_text_file_save(const char *configfile, const struct ast_con
                        ast_verbose(VERBOSE_PREFIX_2 "Unable to write (%s)", strerror(errno));
                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);
+                       f = fopen(fn, "a");
+                       if (!f)
+                       {
+                               ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
+                               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;
+               }
+       }
+       
        return 0;
 }