Fixed: [Defaults in choice does not work properly](https://github.com/clicon/clixon/issues/390)

C: Added new file clixon_xml_default.[ch] and moved all default handling there
This commit is contained in:
Olof hagsand 2022-12-22 09:15:10 +01:00
parent 7e92f67f4f
commit ffe918dd0e
16 changed files with 1015 additions and 667 deletions

View file

@ -605,45 +605,6 @@ xml_tree_prune_flags(cxobj *xt,
return retval;
}
/*! Add prefix:namespace pair to xml node, set cache, etc
* @param[in] x XML node whose namespace should change
* @param[in] xp XML node where namespace attribute should be declared (can be same)
* @param[in] prefix1 Use this prefix
* @param[in] namespace Use this namespace
* @note x and xp must be different if x is an attribute and may be different otherwise
*/
static int
add_namespace(cxobj *x,
cxobj *xp,
char *prefix,
char *namespace)
{
int retval = -1;
cxobj *xa = NULL;
/* Add binding to x1p. We add to parent due to heurestics, so we dont
* end up in adding it to large number of siblings
*/
if (nscache_set(x, prefix, namespace) < 0)
goto done;
/* Create xmlns attribute to x1p/x1 XXX same code v */
if (prefix){
if ((xa = xml_new(prefix, xp, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", xp, CX_ATTR)) == NULL)
goto done;
}
if (xml_value_set(xa, namespace) < 0)
goto done;
xml_sort(xp); /* Ensure attr is first / XXX xml_insert? */
retval = 0;
done:
return retval;
}
/*! Change namespace of XML node
*
@ -682,7 +643,7 @@ xml_namespace_change(cxobj *x,
xp = x;
else
xp = xml_parent(x);
if (add_namespace(x, xp, prefix, ns) < 0)
if (xml_add_namespace(x, xp, prefix, ns) < 0)
goto done;
/* Add prefix to x, if any */
if (prefix && xml_prefix_set(x, prefix) < 0)
@ -695,493 +656,6 @@ xml_namespace_change(cxobj *x,
return retval;
}
/*!
*/
static int
xml_default_create1(yang_stmt *y,
cxobj *xt,
cxobj **xcp)
{
int retval = -1;
char *namespace;
char *prefix;
int ret;
cxobj *xc = NULL;
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xc, y);
/* assign right prefix */
if ((namespace = yang_find_mynamespace(y)) != NULL){
prefix = NULL;
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
goto done;
if (ret){ /* Namespace found, prefix returned in prefix */
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
else{ /* Namespace does not exist in target, must add it w xmlns attr.
use source prefix */
if (add_namespace(xc, xc, prefix, namespace) < 0)
goto done;
/* Add prefix to x, if any */
if (prefix && xml_prefix_set(xc, prefix) < 0)
goto done;
}
}
if (xml_addsub(xt, xc) < 0)
goto done;
*xcp = xc;
retval = 0;
done:
return retval;
}
/*! Create leaf from default value
*
* @param[in] y Yang spec
* @param[in] xt XML tree
* @param[in] top Use default namespace (if you create xmlns statement)
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default_create(yang_stmt *y,
cxobj *xt,
int top)
{
int retval = -1;
cxobj *xc = NULL;
cxobj *xb;
char *str;
cg_var *cv;
if (xml_default_create1(y, xt, &xc) < 0)
goto done;
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL){
clicon_err(OE_UNIX, ENOENT, "No yang cv of %s", yang_argument_get(y));
goto done;
}
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
retval = 0;
done:
return retval;
}
/*! Try to see if intermediate nodes are necessary for default values, create if so
*
* @param[in] yt Yang container (no-presence)
* @param[in] state Set if global state, otherwise config
* @param[out] createp Need to create XML container
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_nopresence_try(yang_stmt *yt,
int state,
int *createp)
{
int retval = -1;
yang_stmt *y;
if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
clicon_err(OE_XML, EINVAL, "yt argument is not container");
goto done;
}
*createp = 0;
y = NULL;
while ((y = yn_each(yt, y)) != NULL) {
switch (yang_keyword_get(y)){
case Y_LEAF:
/* Default value exists */
if (!cv_flag(yang_cv_get(y), V_UNSET)){
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(y))
;
else
/* Need to create container */
*createp = 1;
goto ok;
}
break;
case Y_CONTAINER:
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
/* If this is non-presence, (and it does not exist in xt) call recursively
* and create nodes if any default value exist first. Then continue and populate?
*/
if (xml_nopresence_try(y, state, createp) < 0)
goto done;
if (*createp)
goto ok;
}
break;
default:
break;
} /* switch */
}
ok:
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on (children of) one single xml node
*
* Not recursive, except in one case with one or several non-presence containers, in which case
* XML containers may be created to host default values. That code may be a little too recursive.
* @param[in] yt Yang spec
* @param[in] xt XML tree (with yt as spec of xt, informally)
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* XXX If state, should not add config defaults
* if (state && yang_config(yc))
*/
static int
xml_default1(yang_stmt *yt,
cxobj *xt,
int state)
{
int retval = -1;
yang_stmt *yc;
cxobj *xc;
int top = 0; /* Top symbol (set default namespace) */
int create = 0;
char *xpath;
int nr = 0;
int hit = 0;
cg_var *cv;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
goto done;
}
switch (yang_keyword_get(yt)){
case Y_MODULE:
case Y_SUBMODULE:
top++;
case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
case Y_LIST:
case Y_INPUT:
case Y_OUTPUT:
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
/* If config parameter and local is config false */
if (!state && !yang_config(yc))
continue;
switch (yang_keyword_get(yc)){
case Y_LEAF:
/* Want to add state defaults, but this is config */
if (state && yang_config_ancestor(yc))
break;
if ((cv = yang_cv_get(yc)) == NULL){
clicon_err(OE_YANG,0, "Internal error: yang leaf %s not populated with cv as it should",
yang_argument_get(yc));
goto done;
}
if (!cv_flag(cv, V_UNSET)){ /* Default value exists */
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
goto done;
xml_sort(xt);
}
}
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* Check when condition */
if (yang_check_when_xpath(NULL, xt, yc, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0)
break; /* Do not create default if xpath fails */
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?
*/
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such container exist, recursively try if needed */
if (xml_nopresence_try(yc, state, &create) < 0)
goto done;
if (create){
/* Retval shows there is a default value need to create the
* container */
if (xml_default_create1(yc, xt, &xc) < 0)
goto done;
xml_sort(xt);
/* Then call it recursively */
if (xml_default1(yc, xc, state) < 0)
goto done;
}
}
}
break;
default:
break;
}
}
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on existing leaf children of this node
*
* Assume yang is bound to the tree
* @param[in] xt XML tree
* @param[in] state If set expand defaults also for state data, otherwise only config
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default(cxobj *xt,
int state)
{
int retval = -1;
yang_stmt *ys;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
if (xml_default1(ys, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Recursively fill in default values in an XML tree
* @param[in] xt XML tree
* @param[in] state If set expand defaults also for state data, otherwise only config
* @retval 0 OK
* @retval -1 Error
* @see xml_global_defaults
*/
int
xml_default_recurse(cxobj *xn,
int state)
{
int retval = -1;
cxobj *x;
yang_stmt *y;
if (xml_default(xn, state) < 0)
goto done;
x = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((y = (yang_stmt*)xml_spec(x)) != NULL){
if (!state && !yang_config(y))
continue;
}
if (xml_default_recurse(x, state) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] xt XML tree
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_global_defaults_create(cxobj *xt,
yang_stmt *yspec,
int state)
{
int retval = -1;
yang_stmt *ymod = NULL;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
goto done;
}
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (xml_default1(ymod, xt, state) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Expand and set default values of global top-level on XML tree
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] h Clixon handle
* @param[in] xt XML tree, assume already filtered with xpath
* @param[in] xpath Filter global defaults with this and merge with xt
* @param[in] yspec Top-level YANG specification tree, all modules
* @param[in] state Set if global state, otherwise config
* @retval 0 OK
* @retval -1 Error
* Uses cache?
* @see xml_default_recurse
*/
int
xml_global_defaults(clicon_handle h,
cxobj *xt,
cvec *nsc,
const char *xpath,
yang_stmt *yspec,
int state)
{
int retval = -1;
db_elmnt de0 = {0,};
db_elmnt *de = NULL;
cxobj *xcache = NULL;
cxobj *xpart = NULL;
cxobj **xvec = NULL;
size_t xlen;
int i;
cxobj *x0;
int ret;
char *key;
/* Use different keys for config and state */
key = state ? "global-defaults-state" : "global-defaults-config";
/* First get or compute global xml tree cache */
if ((de = clicon_db_elmnt_get(h, key)) == NULL){
/* Create it */
if ((xcache = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_global_defaults_create(xcache, yspec, state) < 0)
goto done;
de0.de_xml = xcache;
clicon_db_elmnt_set(h, key, &de0);
}
else
xcache = de->de_xml;
/* Here xcache has all global defaults. Now find the matching nodes
* XXX: nsc as 2nd argument
*/
if (xpath_vec(xcache, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* Iterate through match vector
* For every node found in x0, mark the tree up to t1
*/
for (i=0; i<xlen; i++){
x0 = xvec[i];
xml_flag_set(x0, XML_FLAG_MARK);
xml_apply_ancestor(x0, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
/* Create a new tree and copy over the parts from the cache that matches xpath */
if ((xpart = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
if (xml_copy_marked(xcache, xpart) < 0) /* config */
goto done;
if (xml_apply(xcache, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
if (xml_apply(xpart, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
/* Merge global pruned tree with xt */
if ((ret = xml_merge(xt, xpart, yspec, NULL)) < 1) /* XXX reason */
goto done;
retval = 0;
done:
if (xpart)
xml_free(xpart);
if (xvec)
free(xvec);
return retval;
}
/*! Recursively find empty nopresence containers and default leaves, optionally purge
*
* @param[in] xn XML tree
* @param[in] purge 0: Dont remove any nodes
* 1: Remove config sub-nodes that are empty non-presence container or default leaf
* 2: Remove all sub-nodes that are empty non-presence container or default leaf
* @retval 1 Node is an (recursive) empty non-presence container or default leaf
* @retval 0 Other node
* @retval -1 Error
* @note xn is not itself removed if purge
* @note for purge=1 are removed only if config or no yang spec(!)
*/
int
xml_defaults_nopresence(cxobj *xn,
int purge)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
yang_stmt *yn;
yang_stmt *y;
int rmx = 0; /* If set, remove this xn */
int ret;
enum rfc_6020 keyw;
int config = 1;
if ((yn = xml_spec(xn)) != NULL){
keyw = yang_keyword_get(yn);
if (keyw == Y_CONTAINER &&
yang_find(yn, Y_PRESENCE, NULL) == NULL)
rmx = 1;
else if (keyw == Y_LEAF &&
xml_flag(xn, XML_FLAG_DEFAULT))
rmx = 1;
config = yang_config_ancestor(yn);
}
/* Loop thru children */
x = NULL;
xprev = NULL;
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
if ((ret = xml_defaults_nopresence(x, purge)) < 0)
goto done;
if (ret == 1){
switch (purge){
case 1: /* config nodes only */
if (!config)
break;
if ((y = xml_spec(x)) != NULL &&
!yang_config(y))
break;
/* fall thru */
case 2: /* purge all nodes */
if (xml_purge(x) < 0)
goto done;
x = xprev;
break;
default:
break;
}
}
else if (rmx)
/* May switch an empty non-presence container (rmx=1) to non-empty non-presence container (rmx=0) */
rmx = 0;
}
retval = rmx;
done:
return retval;
}
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/
@ -1505,7 +979,7 @@ assign_namespace(cxobj *x1, /* target */
}
}
}
if (add_namespace(x1, x1, prefix1, ns) < 0)
if (xml_add_namespace(x1, x1, prefix1, ns) < 0)
goto done;
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
@ -1611,7 +1085,7 @@ assign_namespace_body(cxobj *x0, /* source */
if (namespace1 && strcmp(namespace, namespace1)==0)
continue;
/* No, add entry */
if (add_namespace(x1, x1, prefix1, namespace) < 0)
if (xml_add_namespace(x1, x1, prefix1, namespace) < 0)
goto done;
}
}
@ -2356,107 +1830,3 @@ purge_tagged_nodes(cxobj *xn,
return retval;
}
/*! Add default attribute to node with default value.
*
* Used in with-default code for report-all-tagged
* @param[in] x XML node
* @param[in] flags Flags indicatiing default nodes
* @retval 0 OK
* @retval -1 Error
*/
int
xml_add_default_tag(cxobj *x,
uint16_t flags)
{
int retval = -1;
cxobj *xattr;
if (xml_flag(x, flags)) {
/* set default attribute */
if ((xattr = xml_new("default", x, CX_ATTR)) == NULL)
goto done;
if (xml_value_set(xattr, "true") < 0)
goto done;
if (xml_prefix_set(xattr, IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Set flag on node having schema default value. (non-config)
*
* Used in with-default code for trim/report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_default_value for config default value
*/
int
xml_flag_state_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if (yang_config_ancestor(y) == 1)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}
/*! Set flag on node having schema default value. (config)
*
* Used in with-default code for trim and report-all-tagged
* @param[in] x XML node
* @param[in] flag Flag to be used
* @retval 0 OK
* @see xml_flag_state_default_value for non-config default value
*/
int
xml_flag_default_value(cxobj *x,
uint16_t flag)
{
yang_stmt *y;
cg_var *cv;
char *yv;
char *xv;
xml_flag_reset(x, flag); /* Assume not default value */
if ((xv = xml_body(x)) == NULL)
goto done;
if ((y = xml_spec(x)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if ((cv = yang_cv_get(y)) == NULL)
goto done;
if (cv_name_get(cv) == NULL)
goto done;
if ((yv = cv2str_dup(cv)) == NULL)
goto done;
if (strcmp(xv, yv) == 0)
xml_flag_set(x, flag); /* Actual value same as default value */
free(yv);
done:
return 0;
}