Index: pbx/pbx_spool.c =================================================================== --- pbx/pbx_spool.c (.../trunk) (revision 81359) +++ pbx/pbx_spool.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -219,7 +219,7 @@ 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: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (.../trunk) (revision 81359) +++ channels/chan_sip.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -10028,7 +10028,7 @@ 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 @@ 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: channels/chan_iax2.c =================================================================== --- channels/chan_iax2.c (.../trunk) (revision 81359) +++ channels/chan_iax2.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -5225,7 +5225,7 @@ /* 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 @@ 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: channels/chan_skinny.c =================================================================== --- channels/chan_skinny.c (.../trunk) (revision 81359) +++ channels/chan_skinny.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -1463,7 +1463,7 @@ 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: channels/iax2-parser.c =================================================================== --- channels/iax2-parser.c (.../trunk) (revision 81359) +++ channels/iax2-parser.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -917,7 +917,7 @@ 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 @@ } } if (!var2) { - var = ast_variable_new(tmp, tmp2); + var = ast_variable_new(tmp, tmp2, ""); var->next = ies->vars; ies->vars = var; } Index: apps/app_parkandannounce.c =================================================================== --- apps/app_parkandannounce.c (.../trunk) (revision 81359) +++ apps/app_parkandannounce.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -130,7 +130,7 @@ 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: apps/app_minivm.c =================================================================== --- apps/app_minivm.c (.../trunk) (revision 81359) +++ apps/app_minivm.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -2141,7 +2141,7 @@ 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: apps/app_voicemail.c =================================================================== --- apps/app_voicemail.c (.../trunk) (revision 81359) +++ apps/app_voicemail.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -960,7 +960,7 @@ 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: apps/app_directory.c =================================================================== --- apps/app_directory.c (.../trunk) (revision 81359) +++ apps/app_directory.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -386,7 +386,7 @@ /* 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 @@ 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: include/asterisk/config.h =================================================================== --- include/asterisk/config.h (.../trunk) (revision 81359) +++ include/asterisk/config.h (.../team/murf/bug8684-trunk) (revision 81359) @@ -52,6 +52,7 @@ 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 @@ 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 @@ 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: main/config.c =================================================================== --- main/config.c (.../trunk) (revision 81359) +++ main/config.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -180,7 +180,9 @@ 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_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_variable *ast_variable_new(const char *name, const char *value) +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, 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 @@ 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 @@ #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_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_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 @@ (!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 @@ if (!cfg) return; + ast_includes_destroy(cfg->includes); + cat = cfg->root; while (cat) { ast_variables_destroy(cat->root); @@ -657,7 +797,7 @@ } 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ #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 @@ 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 @@ 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) { + 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) - { + 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 @@ 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 @@ 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 @@ 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 @@ .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 @@ } } - 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 @@ 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: main/channel.c =================================================================== --- main/channel.c (.../trunk) (revision 81359) +++ main/channel.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -177,10 +177,10 @@ 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: main/manager.c =================================================================== --- main/manager.c (.../trunk) (revision 81359) +++ main/manager.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -791,7 +791,7 @@ 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 @@ } /* 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 @@ 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 @@ } 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 @@ 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: main/http.c =================================================================== --- main/http.c (.../trunk) (revision 81359) +++ main/http.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -567,7 +567,7 @@ 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 @@ 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 @@ 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: res/res_config_sqlite.c =================================================================== --- res/res_config_sqlite.c (.../trunk) (revision 81359) +++ res/res_config_sqlite.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -305,9 +305,8 @@ * \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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ 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: res/res_config_odbc.c =================================================================== --- res/res_config_odbc.c (.../trunk) (revision 81359) +++ res/res_config_odbc.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -219,11 +219,11 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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: res/res_config_pgsql.c =================================================================== --- res/res_config_pgsql.c (.../trunk) (revision 81359) +++ res/res_config_pgsql.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -175,12 +175,12 @@ 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 @@ 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 @@ 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 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 @@ 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 @@ } 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: utils/extconf.c =================================================================== --- utils/extconf.c (.../trunk) (revision 81359) +++ utils/extconf.c (.../team/murf/bug8684-trunk) (revision 81359) @@ -620,6 +620,8 @@ 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_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 @@ 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 @@ }; 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 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_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 @@ if (!cfg) return; + ast_includes_destroy(cfg->includes); + cat = cfg->root; while (cat) { ast_variables_destroy(cat->root); @@ -2431,83 +2648,6 @@ 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 @@ 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 @@ 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) { ast_variables_destroy(cat->root); + if (cat->file) + free(cat->file); + free(cat); } @@ -3245,9 +3390,9 @@ }; -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 @@ } } - 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 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 @@ 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 @@ } 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 @@ /* 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 @@ 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 @@ } -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ #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) { + 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) - { + 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 @@ 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 @@ 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; } Property changes on: . ___________________________________________________________________ Name: automerge + yes Name: svnmerge-integrated + /trunk:1-81357 Name: automerge-email + murf@digium.com