* Top-level default leafs assigned.
* Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values. * NACM default behaviour is read-only (empty configs are dead-lockedd) * This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal` * Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111)
This commit is contained in:
parent
65733ffe69
commit
794d51a365
30 changed files with 593 additions and 167 deletions
|
|
@ -54,7 +54,8 @@
|
|||
typedef struct {
|
||||
uint32_t de_id; /* session id */
|
||||
cxobj *de_xml; /* cache */
|
||||
int de_modified;
|
||||
int de_modified; /* Dirty since loaded/copied/committed/etc XXX:nocache? */
|
||||
int de_empty; /* Empty on read from file, xmldb_readfile and xmldb_put sets it */
|
||||
} db_elmnt;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ cxobj *xmldb_cache_get(clicon_handle h, const char *db);
|
|||
|
||||
int xmldb_modified_get(clicon_handle h, const char *db);
|
||||
int xmldb_modified_set(clicon_handle h, const char *db, int value);
|
||||
|
||||
int xmldb_empty_get(clicon_handle h, const char *db);
|
||||
int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt);
|
||||
|
||||
#endif /* _CLIXON_DATASTORE_H */
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
|
|||
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
|
||||
int xml_default(cxobj *x);
|
||||
int xml_default_recurse(cxobj *xn);
|
||||
int xml_default_yspec(yang_stmt *yspec, cxobj *xn);
|
||||
int xml_nopresence_default(cxobj *xt);
|
||||
int xml_nopresence_default_mark(cxobj *x, void *arg);
|
||||
int xml_sanity(cxobj *x, void *arg);
|
||||
int xml_non_config_data(cxobj *xt, void *arg);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,13 @@
|
|||
|
||||
/* Struct containing module state differences between two modules or two
|
||||
* revisions of same module.
|
||||
* This is in state of flux so it needs to be contained and easily changed.
|
||||
* The most significant usecase is one module-state is a loaded datastore and the other
|
||||
* is the one loaded by the server by its YANG files.
|
||||
*/
|
||||
typedef struct {
|
||||
int md_status; /* 0 if no module-state in a datastore, 1 if there is */
|
||||
char *md_set_id; /* server-specific identifier */
|
||||
cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
|
||||
cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
|
||||
} modstate_diff_t;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -636,12 +636,11 @@ clicon_argv_set(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Get xml database element including pid and xml cache
|
||||
/*! Get xml database element including id, xml cache, empty on startup and dirty bit
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database
|
||||
* @retval de Database element
|
||||
* @retval NULL None found
|
||||
* @note these use db_elmnt hash, not data
|
||||
*/
|
||||
db_elmnt *
|
||||
clicon_db_elmnt_get(clicon_handle h,
|
||||
|
|
@ -655,14 +654,12 @@ clicon_db_elmnt_get(clicon_handle h,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*! Set xml database element including pid and xml cache
|
||||
/*! Set xml database element including id, xml cache, empty on startup and dirty bit
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database
|
||||
* @param[in] de Database element
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* XXX add prefix to db to ensure uniqueness?
|
||||
* @note these use db_elmnt hash, not data
|
||||
*/
|
||||
int
|
||||
clicon_db_elmnt_set(clicon_handle h,
|
||||
|
|
|
|||
|
|
@ -518,6 +518,26 @@ xmldb_modified_get(clicon_handle h,
|
|||
return de->de_modified;
|
||||
}
|
||||
|
||||
/*! Get empty flag from datastore (the datastore was empty ON LOAD)
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Database name
|
||||
* @retval -1 Error (datastore does not exist)
|
||||
* @retval 0 Db was not empty on load
|
||||
* @retval 1 Db was empty on load
|
||||
*/
|
||||
int
|
||||
xmldb_empty_get(clicon_handle h,
|
||||
const char *db)
|
||||
{
|
||||
db_elmnt *de;
|
||||
|
||||
if ((de = clicon_db_elmnt_get(h, db)) == NULL){
|
||||
clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
|
||||
return -1;
|
||||
}
|
||||
return de->de_empty;
|
||||
}
|
||||
|
||||
/*! Get modified flag from datastore
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Database name
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
#include "clixon_xpath.h"
|
||||
#include "clixon_json.h"
|
||||
#include "clixon_nacm.h"
|
||||
#include "clixon_path.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_xml_map.h"
|
||||
|
|
@ -104,7 +105,7 @@ singleconfigroot(cxobj *xt,
|
|||
}
|
||||
}
|
||||
if (i != 1){
|
||||
clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
|
||||
clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
|
||||
goto done;
|
||||
}
|
||||
x = NULL;
|
||||
|
|
@ -443,33 +444,63 @@ text_read_modstate(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
disable_nacm_on_empty(cxobj *x0,
|
||||
yang_stmt *yspec)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ymod;
|
||||
cxobj **vec = NULL;
|
||||
int len = 0;
|
||||
cxobj *xb;
|
||||
|
||||
if ((ymod = yang_find(yspec, Y_MODULE, "ietf-netconf-acm")) == NULL)
|
||||
goto ok;
|
||||
if (clixon_xml_find_instance_id(x0, yspec, &vec, &len, "/nacm:nacm/nacm:enable-nacm") < 1)
|
||||
goto done;
|
||||
if (len){
|
||||
if ((xb = xml_body_get(vec[0])) == NULL)
|
||||
goto done;
|
||||
if (xml_value_set(xb, "false") < 0)
|
||||
goto done;
|
||||
}
|
||||
if (vec)
|
||||
free(vec);
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Common read function that reads an XML tree from file
|
||||
* @param[in] th Datastore text handle
|
||||
* @param[in] db Symbolic database name, eg "candidate", "running"
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Top-level yang spec
|
||||
* @param[out] xp XML tree read from file
|
||||
* @param[out] de If set, return db-element status (eg empty flag)
|
||||
* @param[out] msdiff If set, return modules-state differences
|
||||
* @retval -1 Error
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
|
||||
* @retval 1 OK
|
||||
* @note retval 0 is NYI becaues of functions calling this function
|
||||
* @note retval 0 is NYI because of functions calling this function cannot handle it yet
|
||||
*/
|
||||
#undef XMLDB_READFILE_FAIL
|
||||
#undef XMLDB_READFILE_FAIL /* See comment on retval = 0 above */
|
||||
int
|
||||
xmldb_readfile(clicon_handle h,
|
||||
const char *db,
|
||||
yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xp,
|
||||
db_elmnt *de,
|
||||
modstate_diff_t *msdiff)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x0 = NULL;
|
||||
char *dbfile = NULL;
|
||||
int fd = -1;
|
||||
char *format;
|
||||
int ret;
|
||||
int retval = -1;
|
||||
cxobj *x0 = NULL;
|
||||
char *dbfile = NULL;
|
||||
int fd = -1;
|
||||
char *format;
|
||||
int ret;
|
||||
|
||||
if (xmldb_db2file(h, db, &dbfile) < 0)
|
||||
goto done;
|
||||
|
|
@ -497,8 +528,9 @@ xmldb_readfile(clicon_handle h,
|
|||
goto fail;
|
||||
#endif
|
||||
/* Always assert a top-level called "config".
|
||||
To ensure that, deal with two cases:
|
||||
1. File is empty <top/> -> rename top-level to "config" */
|
||||
* To ensure that, deal with two cases:
|
||||
* 1. File is empty <top/> -> rename top-level to "config"
|
||||
*/
|
||||
if (xml_child_nr(x0) == 0){
|
||||
if (xml_name_set(x0, "config") < 0)
|
||||
goto done;
|
||||
|
|
@ -509,7 +541,10 @@ xmldb_readfile(clicon_handle h,
|
|||
if (singleconfigroot(x0, &x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* From Clixon 3.10,datastore files may contain module-state defining
|
||||
if (xml_child_nr(x0) == 0 && de)
|
||||
de->de_empty = 1;
|
||||
|
||||
/* Datastore files may contain module-state defining
|
||||
* which modules are used in the file.
|
||||
*/
|
||||
if (text_read_modstate(h, yspec, x0, msdiff) < 0)
|
||||
|
|
@ -569,16 +604,36 @@ xmldb_get_nocache(clicon_handle h,
|
|||
size_t xlen;
|
||||
int i;
|
||||
int ret;
|
||||
db_elmnt de0 = {0,};
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
}
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
|
||||
|
||||
/* Check if empty */
|
||||
if (xml_child_nr(xt) == 0)
|
||||
empty = 1;
|
||||
/* Add global defaults.
|
||||
* Must do it before xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, xt) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(xt, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
/* Given the xpath, return a vector of matches in xvec */
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
|
|
@ -664,6 +719,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
cxobj *x1t = NULL;
|
||||
db_elmnt de0 = {0,};
|
||||
int ret;
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
|
|
@ -672,7 +728,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
de = clicon_db_elmnt_get(h, db);
|
||||
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
||||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -680,10 +736,30 @@ xmldb_get_cache(clicon_handle h,
|
|||
* No, argument against: we may want to have a semantically wrong file and wish to edit?
|
||||
*/
|
||||
de0.de_xml = x0t;
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
|
||||
} /* x0t == NULL */
|
||||
else
|
||||
x0t = de->de_xml;
|
||||
|
||||
/* Check if empty, must be before add global defaults */
|
||||
if (xml_child_nr(x0t) == 0)
|
||||
empty = 1;
|
||||
|
||||
/* Add global defaults.
|
||||
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, x0t) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(x0t, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Here x0t looks like: <config>...</config> */
|
||||
/* Given the xpath, return a vector of matches in xvec
|
||||
* Can we do everything in one go?
|
||||
|
|
@ -693,8 +769,6 @@ xmldb_get_cache(clicon_handle h,
|
|||
* a) for every node that is found, copy to new tree
|
||||
* b) if config dont dont state data
|
||||
*/
|
||||
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
||||
|
|
@ -703,6 +777,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
goto done;
|
||||
xml_spec_set(x1t, xml_spec(x0t));
|
||||
|
||||
|
||||
if (xlen < 1000){
|
||||
/* This is optimized for the case when the tree is large and xlen is small
|
||||
* If the tree is large and xlen too, then the other is better.
|
||||
|
|
@ -786,6 +861,7 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
db_elmnt *de = NULL;
|
||||
db_elmnt de0 = {0,};
|
||||
int ret;
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
|
|
@ -794,7 +870,7 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
de = clicon_db_elmnt_get(h, db);
|
||||
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
||||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -806,6 +882,25 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
} /* x0t == NULL */
|
||||
else
|
||||
x0t = de->de_xml;
|
||||
/* Check if empty, must be before add global defaults */
|
||||
if (xml_child_nr(x0t) == 0)
|
||||
empty = 1;
|
||||
|
||||
/* Add global defaults.
|
||||
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, x0t) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(x0t, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
|
@ -874,7 +969,7 @@ xmldb_get(clicon_handle h,
|
|||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache
|
||||
* @param[out] xret Single return XML tree. Free with xml_free()
|
||||
* @param[out] msdiff If set, return modules-state differences (upgrade code)
|
||||
* @param[out] msdiff If set, return modules-state differences (upgrade code)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
|
|
@ -944,12 +1039,16 @@ xmldb_get0_clear(clicon_handle h,
|
|||
|
||||
if (x == NULL)
|
||||
goto ok;
|
||||
/* clear XML tree of defaults */
|
||||
/* Mark non-presence containers as XML_FLAG_DEFAULT */
|
||||
if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
|
||||
goto done;
|
||||
/* Clear XML tree of defaults */
|
||||
if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
|
||||
goto done;
|
||||
|
||||
/* clear mark and change */
|
||||
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||
(void*)(0xff));
|
||||
(void*)(0xffff));
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -972,3 +1071,4 @@ xmldb_get0_free(clicon_handle h,
|
|||
*xp = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec, cxobj **xp, modstate_diff_t *msd);
|
||||
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec,
|
||||
cxobj **xp, db_elmnt *de, modstate_diff_t *msd);
|
||||
|
||||
#endif /* _CLIXON_DATASTORE_READ_H */
|
||||
|
|
|
|||
|
|
@ -481,9 +481,11 @@ text_modify(clicon_handle h,
|
|||
switch(op){
|
||||
case OP_CREATE:
|
||||
if (x0){
|
||||
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
if (xml_nopresence_default(x0) == 0){
|
||||
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
case OP_REPLACE: /* fall thru */
|
||||
case OP_MERGE:
|
||||
|
|
@ -928,7 +930,7 @@ xmldb_put(clicon_handle h,
|
|||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if (x0 == NULL){
|
||||
firsttime++; /* to avoid leakage on error, see fail from text_modify */
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, NULL)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, de, NULL)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -987,10 +989,10 @@ xmldb_put(clicon_handle h,
|
|||
db_elmnt de0 = {0,};
|
||||
if (de != NULL)
|
||||
de0 = *de;
|
||||
if (de0.de_xml == NULL){
|
||||
if (de0.de_xml == NULL)
|
||||
de0.de_xml = x0;
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
}
|
||||
de0.de_empty = (xml_child_nr(de0.de_xml) == 0);
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
}
|
||||
if (xmldb_db2file(h, db, &dbfile) < 0)
|
||||
goto done;
|
||||
|
|
@ -1057,6 +1059,9 @@ xmldb_dump(clicon_handle h,
|
|||
char *format;
|
||||
int pretty;
|
||||
|
||||
/* clear XML tree of defaults */
|
||||
if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
|
||||
goto done;
|
||||
/* Add modstate first */
|
||||
if ((x = clicon_modst_cache_get(h, 1)) != NULL){
|
||||
if ((xmodst = xml_dup(x)) == NULL)
|
||||
|
|
@ -1079,3 +1084,4 @@ xmldb_dump(clicon_handle h,
|
|||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1100,7 +1100,9 @@ nacm_access(clicon_handle h,
|
|||
/* Do initial nacm processing common to all access validation in
|
||||
* RFC8341 3.4 */
|
||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
||||
operation is permitted. */
|
||||
* operation is permitted.
|
||||
* note option CLICON_NACM_DISABLED_ON_EMPTY
|
||||
*/
|
||||
if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
|
||||
goto permit;
|
||||
enabled = xml_body(x);
|
||||
|
|
@ -1155,6 +1157,7 @@ nacm_access_pre(clicon_handle h,
|
|||
cxobj *xnacm = NULL;
|
||||
cvec *nsc = NULL;
|
||||
|
||||
/* Check clixon option: disabled, external tree or internal */
|
||||
mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (mode == NULL)
|
||||
goto permit;
|
||||
|
|
|
|||
|
|
@ -1883,6 +1883,7 @@ xml_copy_one(cxobj *x0,
|
|||
default:
|
||||
break;
|
||||
}
|
||||
xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT)); /* Maybe more flags */
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -979,9 +979,223 @@ xml_namespace_change(cxobj *x,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Add default values (if not set)
|
||||
* @param[in] xt XML tree with some node marked
|
||||
* Typically called in a recursive apply function:
|
||||
int
|
||||
xml_default_create1(yang_stmt *y,
|
||||
cxobj *xt,
|
||||
int top,
|
||||
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 (!top){
|
||||
if ((prefix = yang_find_myprefix(y)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
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] yt 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;
|
||||
|
||||
if (xml_default_create1(y, xt, top, &xc) < 0)
|
||||
goto done;
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
|
||||
goto done;
|
||||
if ((str = cv2str_dup(yang_cv_get(y))) == 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[out] createp Need to create XML container
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_nopresence_try(yang_stmt *yt,
|
||||
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:
|
||||
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
|
||||
/* 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, 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)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_default1(yang_stmt *yt,
|
||||
cxobj *xt)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yc;
|
||||
cxobj *xc;
|
||||
int top=0; /* Top symbol (set default namespace) */
|
||||
int create = 0;
|
||||
|
||||
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) {
|
||||
switch (yang_keyword_get(yc)){
|
||||
case Y_LEAF:
|
||||
if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
|
||||
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){
|
||||
/* 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, &create) < 0)
|
||||
goto done;
|
||||
if (create){
|
||||
/* Retval shows there is a default value need to create the
|
||||
* container */
|
||||
if (xml_default_create1(yc, xt, top, &xc) < 0)
|
||||
goto done;
|
||||
xml_sort(xt);
|
||||
/* Then call it recursively */
|
||||
if (xml_default1(yc, xc) < 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
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
|
|
@ -990,79 +1204,22 @@ xml_default(cxobj *xt)
|
|||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ys;
|
||||
yang_stmt *y;
|
||||
// int i; // XXX
|
||||
cxobj *xc;
|
||||
cxobj *xb;
|
||||
char *str;
|
||||
int added=0;
|
||||
char *namespace;
|
||||
char *prefix;
|
||||
int ret;
|
||||
|
||||
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
/* Check leaf defaults */
|
||||
if (yang_keyword_get(ys) == Y_CONTAINER || yang_keyword_get(ys) == Y_LIST ||
|
||||
yang_keyword_get(ys) == Y_INPUT){
|
||||
y = NULL;
|
||||
while ((y = yn_each(ys, y)) != NULL) {
|
||||
if (yang_keyword_get(y) != Y_LEAF)
|
||||
continue;
|
||||
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
|
||||
if (!xml_find(xt, yang_argument_get(y))){
|
||||
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){
|
||||
if (xml_prefix_set(xc, prefix) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* namespace does not exist in target, use source prefix */
|
||||
if ((prefix = yang_find_myprefix(y)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
|
||||
goto done;
|
||||
if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
if (xml_value_set(xb, str) < 0)
|
||||
goto done;
|
||||
free(str);
|
||||
added++;
|
||||
if (xml_insert(xt, xc, INS_LAST, NULL, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xml_default1(ys, xt) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Recursively fill in default values in a tree
|
||||
* Alt to use xml_apply
|
||||
/*! Recursively fill in default values in an XML tree
|
||||
* @param[in] xt XML tree
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
xml_default_recurse(cxobj *xn)
|
||||
|
|
@ -1082,6 +1239,85 @@ xml_default_recurse(cxobj *xn)
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Ensure default values are set on top-level
|
||||
*
|
||||
* Not recursive, except in one case with one or several non-presence containers
|
||||
* @param[in] xt XML tree
|
||||
* Typically called in a recursive apply function:
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
xml_default_yspec(yang_stmt *yspec,
|
||||
cxobj *xt)
|
||||
{
|
||||
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) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! This node is a default set value or (recursively) a non-presence container
|
||||
* @retval 1 xt is a nopresence/default node (ie "virtual")
|
||||
* @retval 0 xt is not such a node
|
||||
*/
|
||||
int
|
||||
xml_nopresence_default(cxobj *xt)
|
||||
{
|
||||
cxobj *xc;
|
||||
yang_stmt *yt;
|
||||
|
||||
if ((yt = xml_spec(xt)) == NULL)
|
||||
return 0;
|
||||
switch (yang_keyword_get(yt)){
|
||||
case Y_CONTAINER:
|
||||
if (yang_find(yt, Y_PRESENCE, NULL))
|
||||
return 0;
|
||||
break;
|
||||
case Y_LEAF:
|
||||
return xml_flag(xt, XML_FLAG_DEFAULT)?1:0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if (xml_nopresence_default(xc) == 0)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*! Remove xml container if it is non-presence and only contains default leafs
|
||||
* Called from xml_apply. Reason for marking is to delete it afterwords.
|
||||
* @param[in] x
|
||||
* @param[in] arg (flag value)
|
||||
* @code
|
||||
* if (xml_apply(xt, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
|
||||
* err;
|
||||
* if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
|
||||
* goto done;
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_nopresence_default_mark(cxobj *x,
|
||||
void *arg)
|
||||
{
|
||||
if (xml_nopresence_default(x))
|
||||
xml_flag_set(x, (intptr_t)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
|
||||
* @param[in] xt XML top of tree
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1029,6 +1029,7 @@ xml_insert2(cxobj *xp,
|
|||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
|
||||
* @note It is assumed that all siblings of xi are YANG bound
|
||||
*/
|
||||
int
|
||||
xml_insert(cxobj *xp,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue