/* 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);
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
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;
}
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) {
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);
/* 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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
}
if (!var2) {
- var = ast_variable_new(tmp, tmp2);
+ var = ast_variable_new(tmp, tmp2, "");
var->next = ies->vars;
ies->vars = var;
}
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 */
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);
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
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;
}
}
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;
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)
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;
#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;
}
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);
}
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) {
(!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)
if (!cfg)
return;
+ ast_includes_destroy(cfg->includes);
+
cat = cfg->root;
while (cat) {
ast_variables_destroy(cat->root);
}
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;
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);
}
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 == '\"'))
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)
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 */
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];
#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;
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;
}
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);
}
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)
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;
}
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;
.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];
}
}
- 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--;
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);
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
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;
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;
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;
}
}
/* 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];
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);
}
} 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);
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) {
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;
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, "");
}
}
}
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;
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);
}
}
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;
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;
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;
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);
}
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, "");
}
}
}
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);
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);
}
}
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;
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;
}
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 {
* \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
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");
}
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");
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;
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)
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;
}
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;
}
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;
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);
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 */
};
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)
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;
}
}
+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;
if (!cfg)
return;
+ ast_includes_destroy(cfg->includes);
+
cat = cfg->root;
while (cat) {
ast_variables_destroy(cat->root);
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
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;
}
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);
static void ast_category_destroy(struct ast_category *cat)
{
ast_variables_destroy(cat->root);
+ if (cat->file)
+ free(cat->file);
+
free(cat);
}
};
-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];
}
}
- 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);
*/
}
-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;
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);
}
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 == '\"'))
/* 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)
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 */
}
-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];
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;
}
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);
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);
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
#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);
}
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)
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;
}