config.c: fix saving of deep/wide template configurations

Follow-on to #244 and #960 regarding how the ast_config_XXX APIs
handle template inheritance.

ast_config_text_file_save2() incorrectly suppressed variables if they
matched any ancestor template.  This broke deep chains (dropping values
based on distant parents) and wide inheritance (ignoring last-wins order
across multiple parents).

The function now inspects the full template hierarchy to find the nearest
effective parent (last occurrence wins).  Earlier inherited duplicates are
collapsed, explicit overrides are kept unless they exactly match the parent,
and PreserveEffectiveContext avoids writing redundant lines.

Resolves: #1451
This commit is contained in:
Allan Nathanson
2025-09-10 16:35:27 -04:00
committed by github-actions[bot]
parent 32c41efa04
commit 7bd30279de

View File

@@ -225,7 +225,8 @@ struct ast_category_template_instance {
struct ast_category { struct ast_category {
char name[80]; char name[80];
int ignored; /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */ int ignored:1; /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
int loaded:1; /*!< 0 = created in memory, 1 = loaded from disk */
int include_level; int include_level;
/*! /*!
* \brief The file name from whence this declaration was read * \brief The file name from whence this declaration was read
@@ -2084,6 +2085,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
if (!newcat) { if (!newcat) {
return -1; return -1;
} }
(*cat)->loaded = 1;
(*cat)->lineno = lineno; (*cat)->lineno = lineno;
/* add comments */ /* add comments */
@@ -3095,37 +3097,70 @@ int ast_config_text_file_save2(const char *configfile, const struct ast_config *
var = cat->root; var = cat->root;
while (var) { while (var) {
struct ast_category_template_instance *x; struct ast_category_template_instance *x;
int found = 0; struct ast_variable *parent = NULL; /* last occurrence matching variable name */
AST_LIST_TRAVERSE(&cat->template_instances, x, next) { /*
struct ast_variable *v; * When saving the configuration we have the option to preserve
for (v = x->inst->root; v; v = v->next) { * the effective category contents. If enabled, the template
* variables are materialized into the leaf categories. Here,
* we check to see if a variable is also present in the leaf
* and, if so, we do not keep just the leaf value (no point in
* duplicate "var = value" lines in the .conf.
*/
if ((flags & CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT) && cat->loaded) {
if (var->inherited) {
struct ast_variable *v;
int skip = 0;
if (flags & CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT) { for (v = var->next; v; v = v->next) {
if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) { if (!strcasecmp(var->name, v->name)) {
found = 1; /* skip earlier inherited duplicate */
skip = 1;
break; break;
} }
} else {
if (var->inherited) {
found = 1;
break;
} else {
if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
found = 1;
break;
}
}
} }
} if (skip) {
if (found) { var = var->next;
break; continue;
}
} }
} }
if (found) {
var = var->next; /*
continue; * Walk every template instance in the order Asterisk applies
* them, letting later templates override earlier ones.
*/
AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
struct ast_variable *v;
struct ast_variable *last = NULL;
/* Within a template, capture the last occurrence of the key. */
for (v = x->inst->root; v; v = v->next) {
if (!strcasecmp(var->name, v->name)) {
last = v;
}
}
if (last) {
parent = last;
}
} }
/* decide whether to write the variable */
if ((flags & CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT) && cat->loaded) {
/* materialize inherited vars; suppress only explicit exact dups */
if (!var->inherited && parent && !strcmp(var->value, parent->value)) {
var = var->next;
continue;
}
} else {
/* skip inherited, explicit exact dups */
if (var->inherited || (parent && !strcmp(var->value, parent->value))) {
var = var->next;
continue;
}
}
fi = set_fn(fn, sizeof(fn), var->file, configfile, fileset); fi = set_fn(fn, sizeof(fn), var->file, configfile, fileset);
f = fopen(fn, "a"); f = fopen(fn, "a");
if (!f) { if (!f) {