diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2474c40..1c639b1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,9 +55,11 @@ Users may have to change how they access the system
### C/CLI-API changes on existing features
Developers may need to change their code
+* Changed return value of `xml_add_attr` from 0/-1 to xa/NULL
+ * You need to change eg `if (xml_add_attr < 0)` to if (xml_add_attr == NULL)`
* Changed signature of `clicon_netconf_error()` and `netconf_err2cb()`
* You need to add the clixon handle as first parameter:
- * `clicon_netconf_error(...)` --> `clicon_netconf_error(h, ...)`
+ * `clicon_netconf_error(...)` --> `clixon_netconf_error(h, ...)`
* `netconf_err2cb(...)` --> `netconf_err2cb(h, ...)`
* Changed function name for `clicon_debug` functions. You need to rename as follows:
* clicon_debug() -> clixon_debug()
@@ -71,8 +73,11 @@ Developers may need to change their code
* New feature: [Customized NETCONF error message](https://github.com/clicon/clixon/issues/454)
* Added new callback `.ca_errmsg`
* See https://clixon-docs.readthedocs.io/en/latest/errors.html#customized-errors for more info
+* New `clixon-config@2023-11-01.yang` revision
+ * Added `CLICON_NETCONF_CREATOR_ATTR` option
* New `clixon-lib@2023-11-01.yang` revision
* Added ignore-compare extension
+ * Added creator meta configuration
### Corrected Bugs
diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c
index 55291511..39bece86 100644
--- a/apps/backend/backend_get.c
+++ b/apps/backend/backend_get.c
@@ -798,7 +798,7 @@ get_list_pagination(clicon_handle h,
goto done;
}
cprintf(cba, "%u", remaining);
- if (xml_add_attr(x1, "remaining", cbuf_get(cba), "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0)
+ if (xml_add_attr(x1, "remaining", cbuf_get(cba), "cp", "http://clicon.org/clixon-netconf-list-pagination") == NULL)
goto done;
if (cba)
cbuf_free(cba);
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index fd822a04..28a31cfd 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -473,7 +473,7 @@ cli_dbxml(clicon_handle h,
goto done;
}
}
- if (xml_add_attr(xbot, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) < 0)
+ if (xml_add_attr(xbot, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL)
goto done;
/* Add body last in case of leaf */
if (cvec_len(cvv) > 1 &&
diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c
index 994df53d..283ab0a3 100644
--- a/apps/netconf/netconf_rpc.c
+++ b/apps/netconf/netconf_rpc.c
@@ -712,7 +712,7 @@ netconf_rpc_dispatch(clicon_handle h,
* It may even be wrong if something else is done with the incoming message?
*/
if ((username = clicon_username_get(h)) != NULL){
- if (xml_add_attr(xn, "username", username, CLIXON_LIB_PREFIX, CLIXON_LIB_NS) < 0)
+ if (xml_add_attr(xn, "username", username, CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL)
goto done;
}
/* Many of these calls are now calling generic clicon_rpc_netconf_xml
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index c8c5a916..def540b1 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -427,7 +427,7 @@ restconf_insert_attributes(cxobj *xdata,
/* First add xmlns:yang attribute */
if (xmlns_set(xdata, "yang", YANG_XML_NAMESPACE) < 0)
goto done;
- if (xml_add_attr(xdata, "insert", instr, "yang", NULL) < 0)
+ if (xml_add_attr(xdata, "insert", instr, "yang", NULL) == NULL)
goto done;
}
if ((pstr = cvec_find_str(qvec, "point")) != NULL){
@@ -467,7 +467,7 @@ restconf_insert_attributes(cxobj *xdata,
p++;
cprintf(cb, "%s", p);
}
- if (xml_add_attr(xdata, attrname, cbuf_get(cb), "yang", NULL) < 0)
+ if (xml_add_attr(xdata, attrname, cbuf_get(cb), "yang", NULL) == NULL)
goto done;
}
/* Add prefix/namespaces used in attributes */
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index 1585c950..49e3e35b 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -390,11 +390,11 @@ api_data_write(clicon_handle h,
/* Add operation create as attribute. If that fails with Conflict, then
* try "replace" (see comment in function header)
*/
- if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) < 0)
+ if (xml_add_attr(xdata, "operation", xml_operation2str(op), NETCONF_BASE_PREFIX, NULL) == NULL)
goto done;
if (xml_add_attr(xdata, "objectcreate",
plain_patch?"false":"true",
- CLIXON_LIB_PREFIX, CLIXON_LIB_NS) < 0)
+ CLIXON_LIB_PREFIX, CLIXON_LIB_NS) == NULL)
goto done;
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data
diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h
index 67312c6a..71417713 100644
--- a/lib/clixon/clixon_xml.h
+++ b/lib/clixon/clixon_xml.h
@@ -235,6 +235,7 @@ int xml_creator_add(cxobj *xn, char *name);
int xml_creator_rm(cxobj *xn, char *name);
int xml_creator_find(cxobj *xn, char *name);
size_t xml_creator_len(cxobj *xn);
+cvec *xml_creator_get(cxobj *xn);
int xml_creator_copy_one(cxobj *x0, cxobj *x1);
int xml_creator_copy_all(cxobj *x0, cxobj *x1);
int xml_creator_print(FILE *f, cxobj *xn);
@@ -304,7 +305,7 @@ cxobj *xml_root(cxobj *xn);
int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op);
int xml_attr_insert2val(char *instr, enum insert_type *ins);
-int xml_add_attr(cxobj *xn, char *name, char *value, char *prefix, char *ns);
+cxobj *xml_add_attr(cxobj *xn, char *name, char *value, char *prefix, char *ns);
int clicon_log_xml(int level, cxobj *x, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
int clixon_debug_xml(int dbglevel, cxobj *x, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c
index 5caf898f..fb9d5765 100644
--- a/lib/src/clixon_datastore_read.c
+++ b/lib/src/clixon_datastore_read.c
@@ -442,6 +442,56 @@ disable_nacm_on_empty(cxobj *xt,
return retval;
}
+/*! Read creator data from meta-data and add to xml internal annotation
+ *
+ * Iterate creator meta-data using xpaths and create internal creator annotations
+ * @param[in] h Clixon handle
+ * @param[in] xt XML top node
+ * @param[in] xn XML creators node
+ * On the form:
+ *
+ * testA[name='foo']
+ * ...
+ * ...
+ *
+ * @see xml_creator_metadata_write
+ */
+static int
+xml_creator_metadata_read(clicon_handle h,
+ cxobj *xn)
+{
+ int retval = -1;
+ cxobj *xt;
+ cxobj *xc;
+ cxobj *xp;
+ cxobj *x;
+ char *name;
+ char *xpath;
+
+ xt = xml_root(xn);
+ xc = NULL;
+ while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) {
+ if (strcmp(xml_name(xc), "creator"))
+ continue;
+ if ((name = xml_find_body(xc, "name")) == NULL)
+ continue;
+ xp = NULL;
+ while ((xp = xml_child_each(xc, xp, CX_ELMNT)) != NULL) {
+ if (strcmp(xml_name(xp), "path"))
+ continue;
+ if ((xpath = xml_body(xp)) == NULL)
+ continue;
+ if ((x = xpath_first(xt, NULL, "%s", xpath)) == NULL)
+ continue;
+ if (xml_creator_add(x, name) < 0)
+ goto done;
+ }
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
/*! Common read function that reads an XML tree from file
*
* @param[in] th Datastore text handle
@@ -543,7 +593,19 @@ xmldb_readfile(clicon_handle h,
xml_flag_set(x0, XML_FLAG_TOP);
if (xml_child_nr(x0) == 0 && de)
de->de_empty = 1;
-
+ /* Check if creator attributes are supported*/
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
+ if ((x = xpath_first(x0, NULL, "creators")) != NULL){
+ ns = NULL;
+ if (xml2ns(x, NULL, &ns) < 0)
+ goto done;
+ if (strcmp(ns, CLIXON_LIB_NS) == 0){
+ if (xml_creator_metadata_read(h, x) < 0)
+ goto done;
+ xml_purge(x);
+ }
+ }
+ }
/* Check if we support modstate */
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
if ((msdiff = modstate_diff_new()) == NULL)
diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c
index 85b136c6..ef008562 100644
--- a/lib/src/clixon_datastore_write.c
+++ b/lib/src/clixon_datastore_write.c
@@ -89,7 +89,7 @@
* An attribute may have a prefix(or NULL). The routine finds the associated
* xmlns binding to find the namespace: :.
* If such an attribute is not found, failure is returned with cbret set,
- * If such an attribute its found, its string value is returned.
+ * If such an attribute is found, its string value is returned and removed from XML
* @param[in] x XML node (where to look for attribute)
* @param[in] name Attribute name
* @param[in] ns (Expected) Namespace of attribute
@@ -98,6 +98,7 @@
* @retval 1 OK
* @retval 0 Failed (cbret set)
* @retval -1 Error
+ * @note as a side.effect the attribute is removed
*/
static int
attr_ns_value(cxobj *x,
@@ -142,6 +143,70 @@ attr_ns_value(cxobj *x,
goto done;
}
+/*! Add creator data to metadata xml object on the form name:xpath*
+ *
+ * Callback function type for xml_apply
+ * @param[in] x XML node
+ * @param[in] arg General-purpose argument
+ * @retval -1 Error, aborted at first error encounter, return -1 to end user
+ * @retval 0 OK, continue
+ * @retval 1 Abort, dont continue with others, return 1 to end user
+ * @retval 2 Locally abort this subtree, continue with others
+ * On the form:
+ *
+ * testA[name='foo']
+ * ...
+ * ...
+ *
+ * @see xml_creator_metadata_read
+ */
+static int
+xml_creator_metadata_write(cxobj *x,
+ void *arg)
+{
+ int retval = -1;
+ cxobj *xmeta = (cxobj*)arg;
+ cxobj *xmc;
+ cvec *cvv;
+ cg_var *cv;
+ char *val;
+ cvec *nsc = NULL;
+ char *xpath = NULL;
+
+ if ((cvv = xml_creator_get(x)) == NULL){
+ retval = 0;
+ goto done;
+ }
+ /* Note this requires x to have YANG spec */
+ if (xml2xpath(x, nsc, 0, 0, &xpath) < 0)
+ goto done;
+ cv = NULL;
+ while ((cv = cvec_each(cvv, cv)) != NULL){
+ val = cv_name_get(cv);
+ /* Find existing entry where name is val */
+ xmc = NULL;
+ while ((xmc = xml_child_each(xmeta, xmc, CX_ELMNT)) != NULL){
+ if (strcmp(xml_find_body(xmc, "name"), val) == 0)
+ break;
+ }
+ if (xmc != NULL){
+ if (clixon_xml_parse_va(YB_NONE, NULL, &xmc, NULL, "%s ", xpath) < 0)
+ goto done;
+ }
+ else {
+ if (clixon_xml_parse_va(YB_NONE, NULL, &xmeta, NULL,
+ "%s %s ",
+ val, xpath) < 0)
+ goto done;
+ }
+ }
+ retval = 2;
+ done:
+ if (xpath)
+ free(xpath);
+ return retval;
+}
+
/*! When new body is added, some needs type lookup is made and namespace checked
*
* This includes identityrefs, paths
@@ -209,11 +274,11 @@ check_body_namespace(cxobj *x0,
goto done;
/* Create xmlns attribute to x0 XXX same code ^*/
if (prefix){
- if (xml_add_attr(x, prefix, ns0, "xmlns", NULL) < 0)
+ if (xml_add_attr(x, prefix, ns0, "xmlns", NULL) == NULL)
goto done;
}
else
- if (xml_add_attr(x, "xmlns", ns0, NULL, NULL) < 0)
+ if (xml_add_attr(x, "xmlns", ns0, NULL, NULL) == NULL)
goto done;
xml_sort(x); /* Ensure attr is first / XXX xml_insert? */
}
@@ -231,7 +296,7 @@ check_body_namespace(cxobj *x0,
;
}
else{ /* Add it according to the kludge,... */
- if (xml_add_attr(x0, prefix, ns0, "xmlns", NULL) < 0)
+ if (xml_add_attr(x0, prefix, ns0, "xmlns", NULL) == NULL)
goto done;
}
}
@@ -513,7 +578,6 @@ text_modify(clicon_handle h,
goto done;
if (ret == 0)
goto fail;
-
if (createstr != NULL &&
(op == OP_REPLACE || op == OP_MERGE || op == OP_CREATE)){
if (x0 == NULL || xml_defaults_nopresence(x0, 0)){ /* does not exist or is default */
@@ -532,11 +596,13 @@ text_modify(clicon_handle h,
clicon_data_set(h, "objectexisted", "true");
}
}
- /* Special clixon-lib attribute for keeping track of creator of objects */
- if ((ret = attr_ns_value(x1, "creator", CLIXON_LIB_NS, cbret, &creator)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
+ /* Special clixon-lib attribute for keeping track of creator of objects */
+ if ((ret = attr_ns_value(x1, "creator", CLIXON_LIB_NS, cbret, &creator)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
x1name = xml_name(x1);
if (yang_keyword_get(y0) == Y_LEAF_LIST ||
@@ -798,12 +864,13 @@ text_modify(clicon_handle h,
}
/* XXX: Note, if there is an error in adding the object later, the
* original object is not reverted.
- * XXX: Here creator attributes in x0 are destroyed
*/
if (x0){
- /* Recursively copy creator attributes from existing tree */
- if (xml_creator_copy_all(x0, x1) < 0)
- goto done;
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
+ /* Recursively copy creator attributes from existing tree */
+ if (xml_creator_copy_all(x0, x1) < 0)
+ goto done;
+ }
xml_purge(x0);
x0 = NULL;
}
@@ -854,8 +921,10 @@ text_modify(clicon_handle h,
#ifdef XML_PARENT_CANDIDATE
xml_parent_candidate_set(x0, x0p);
#endif
- if (xml_creator_copy_one(x1, x0) < 0)
- goto done;
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
+ if (xml_creator_copy_one(x1, x0) < 0)
+ goto done;
+ }
changed++;
/* Get namespace from x1
* Check if namespace exists in x0 parent
@@ -1229,6 +1298,7 @@ xmldb_put(clicon_handle h,
int firsttime = 0;
int pretty;
cxobj *xerr = NULL;
+ cxobj *xmeta = NULL;
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
@@ -1262,6 +1332,16 @@ xmldb_put(clicon_handle h,
xml_name(x0), DATASTORE_TOP_SYMBOL);
goto done;
}
+
+ /* XXX Ad-hoc:
+ * In yang mounts yang specs may not be available
+ * in initial parsing, but may be at a later stage.
+ * But it should be possible to find a more precise triggering
+ */
+ if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
+ if ((ret = xml_bind_yang(h, x0, YB_MODULE, yspec, NULL)) < 0)
+ goto done;
+ }
/* Here x0 looks like: ... */
#if 0 /* debug */
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
@@ -1301,7 +1381,6 @@ xmldb_put(clicon_handle h,
if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif
-
/* Write back to datastore cache if first time */
if (clicon_datastore_cache(h) != DATASTORE_NOCACHE){
db_elmnt de0 = {0,};
@@ -1336,6 +1415,19 @@ xmldb_put(clicon_handle h,
goto done;
}
pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY");
+ /* Add creator attribute */
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR")){
+ if ((xmeta = xml_new("creators", x0, CX_ELMNT)) == NULL)
+ goto done;
+ if (xmlns_set(xmeta, NULL, CLIXON_LIB_NS) < 0)
+ goto done;
+ if (xml_apply(x0, CX_ELMNT, xml_creator_metadata_write, xmeta) < 0)
+ goto done;
+ if (xml_child_nr_type(xmeta, CX_ELMNT) == 0){
+ xml_purge(xmeta);
+ xmeta = NULL;
+ }
+ }
if (strcmp(format,"json")==0){
if (clixon_json2file(f, x0, pretty, fprintf, 0, 0) < 0)
goto done;
@@ -1346,6 +1438,10 @@ xmldb_put(clicon_handle h,
*/
if (xmodst && xml_purge(xmodst) < 0)
goto done;
+ if (clicon_option_bool(h, "CLICON_NETCONF_CREATOR_ATTR") && xmeta){
+ if (xml_purge(xmeta) < 0)
+ goto done;
+ }
retval = 1;
done:
if (f != NULL)
diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c
index 5a95dab0..54f87c97 100644
--- a/lib/src/clixon_json.c
+++ b/lib/src/clixon_json.c
@@ -348,7 +348,7 @@ json2xml_decode_identityref(cxobj *x,
if (prefix2 == NULL)
prefix2 = yang_find_myprefix(ymod);
/* Add "xmlns:prefix2=namespace" */
- if (xml_add_attr(x, prefix2, ns, "xmlns", NULL) < 0)
+ if (xml_add_attr(x, prefix2, ns, "xmlns", NULL) == NULL)
goto done;
}
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index db1584f3..6b36d029 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -178,7 +178,7 @@ netconf_invalid_value_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -302,7 +302,7 @@ netconf_missing_attribute_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -418,7 +418,7 @@ netconf_bad_attribute_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -514,7 +514,7 @@ netconf_common_xml(cxobj **xret,
if (*xret == NULL){
if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
@@ -789,7 +789,7 @@ netconf_access_denied_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1034,7 +1034,7 @@ netconf_data_missing_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1093,7 +1093,7 @@ netconf_missing_choice_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1167,7 +1167,7 @@ netconf_operation_not_supported_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1285,7 +1285,7 @@ netconf_operation_failed_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1374,7 +1374,7 @@ netconf_malformed_message_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1460,7 +1460,7 @@ netconf_data_not_unique_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
@@ -1531,7 +1531,7 @@ netconf_minmax_elements_xml(cxobj **xret,
}
else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done;
- if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) < 0)
+ if (xml_add_attr(*xret, "xmlns", NETCONF_BASE_NAMESPACE, NULL, NULL) == NULL)
goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done;
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 8618a33b..d5e696bd 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -185,7 +185,8 @@ struct xml{
yang_stmt *x_spec; /* Pointer to specification, eg yang,
by reference, dont free */
cg_var *x_cv; /* Cached value as cligen variable (set by xml_cmp) */
- cvec *x_creators; /* Support clixon-lib creator annotation */
+ cvec *x_creators; /* Support clixon-lib creator annotation.
+ Only if CLICON_NETCONF_CREATOR_ATTR = true */
#ifdef XML_EXPLICIT_INDEX
struct search_index *x_search_index; /* explicit search index vectors */
#endif
@@ -653,7 +654,7 @@ xml_creator_add(cxobj *xn,
cg_var *cv;
if (!is_element(xn))
- return 0;
+ goto ok;
if (xn->x_creators == NULL){
if ((xn->x_creators = cvec_new(0)) == NULL){
clicon_err(OE_XML, errno, "cvec_new");
@@ -662,6 +663,7 @@ xml_creator_add(cxobj *xn,
}
if ((cv = cvec_find(xn->x_creators, name)) == NULL)
cvec_add_string(xn->x_creators, name, NULL);
+ ok:
retval = 0;
done:
return retval;
@@ -723,6 +725,16 @@ xml_creator_len(cxobj *xn)
return 0;
}
+/*! Get all creator attributes
+ */
+cvec*
+xml_creator_get(cxobj *xn)
+{
+ if (!is_element(xn))
+ return 0;
+ return xn->x_creators;
+}
+
/*! Copy creator info from x0 to x1 single object
*
* @param[in] x0 Source XML node
@@ -830,7 +842,7 @@ creator_print_fn(cxobj *x,
return 2; /* Locally abort this subtree, continue with others */
}
-/*! Print XML and creator tags where they exists, for debugging
+/*! Print XML and creator tags where they exists recursively, for debugging
*
* @param[in] xn XML tree
* @retval see xml_apply
@@ -1355,7 +1367,7 @@ clixon_child_xvec_append(cxobj *xn,
* xml_free(x);
* @endcode
* @note Differentiates between body/attribute vs element to reduce mem allocation
- * @see xml_sort_insert
+ * @see xml_insert
*/
cxobj *
xml_new(char *name,
@@ -2021,10 +2033,10 @@ xml_find_type_value(cxobj *xt,
* @see xml_find_value where a body can be found as well
*/
cxobj *
-xml_find_type(cxobj *xt,
- const char *prefix,
- const char *name,
- enum cxobj_type type)
+xml_find_type(cxobj *xt,
+ const char *prefix,
+ const char *name,
+ enum cxobj_type type)
{
cxobj *x = NULL;
int pmatch; /* prefix match */
@@ -2649,18 +2661,17 @@ xml_attr_insert2val(char *instr,
* @param[in] value Attribute value
* @param[in] prefix Attribute prefix, or NULL
* @param[in] namespace Attribute prefix, if NULL do not add namespace mapping
- * @retval 0 OK
- * @retval -1 Error
+ * @retval xa Created attribute object
+ * @retval NULL Error
*/
-int
+cxobj *
xml_add_attr(cxobj *xn,
char *name,
char *value,
char *prefix,
char *namespace)
{
- int retval = -1;
- cxobj *xa;
+ cxobj *xa = NULL;
char *ns = NULL;
if ((xa = xml_new(name, xn, CX_ATTR)) == NULL)
@@ -2675,9 +2686,16 @@ xml_add_attr(cxobj *xn,
if (ns == NULL && xmlns_set(xn, prefix, namespace) < 0)
goto done;
}
- retval = 0;
+ if (xml_sort(xn) < 0)
+ goto done;
+ ret:
+ return xa;
done:
- return retval;
+ if (xa){
+ xml_free(xa);
+ xa = NULL;
+ }
+ goto ret;
}
/*! Specialization of clicon_log with xml tree
diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c
index d943aa96..0c8b97f3 100644
--- a/lib/src/clixon_xml_bind.c
+++ b/lib/src/clixon_xml_bind.c
@@ -227,8 +227,13 @@ populate_self_parent(cxobj *xt,
}
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) != 0){
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cb, "Namespace mismatch: %s in XML does not match %s in yang", ns, nsy);
if (xerr &&
- netconf_bad_element_xml(xerr, "application", name, "Namespace mismatch") < 0)
+ netconf_bad_element_xml(xerr, "application", name, cbuf_get(cb)) < 0)
goto done;
goto fail;
}
diff --git a/lib/src/clixon_xml_default.c b/lib/src/clixon_xml_default.c
index 11fdd969..944b5afc 100644
--- a/lib/src/clixon_xml_default.c
+++ b/lib/src/clixon_xml_default.c
@@ -615,7 +615,7 @@ xml_add_default_tag(cxobj *x,
if (xml_flag(x, flags)) {
/* set default attribute */
- if (xml_add_attr(x, "default", "true", IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, NULL) < 0)
+ if (xml_add_attr(x, "default", "true", IETF_NETCONF_WITH_DEFAULTS_ATTR_PREFIX, NULL) == NULL)
goto done;
}
retval = 0;
diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y
index 30ff1df1..0cbe564e 100644
--- a/lib/src/clixon_xml_parse.y
+++ b/lib/src/clixon_xml_parse.y
@@ -358,12 +358,14 @@ xml_parse_attr(clixon_xml_yacc *xy,
int retval = -1;
cxobj *xa = NULL;
+ /* XXX: here duplicates of same attributes are removed
+ * This is probably not according standard?
+ */
if ((xa = xml_find_type(xy->xy_xelement, prefix, name, CX_ATTR)) == NULL){
if ((xa = xml_new(name, xy->xy_xelement, CX_ATTR)) == NULL)
goto done;
if (xml_prefix_set(xa, prefix) < 0)
goto done;
-
}
if (xml_value_set(xa, attval) < 0)
goto done;
@@ -484,12 +486,14 @@ pi : BQMARK NAME EQMARK {_PARSE_DEBUG("pi -> NAME ?>"); free($2); }
;
-attrs : attrs attr
- |
+attrs : attrs attr { _PARSE_DEBUG("attrs -> attrs attr"); }
+ | { _PARSE_DEBUG("attrs ->"); }
;
-attr : NAME '=' attvalue { if (xml_parse_attr(_XY, NULL, $1, $3) < 0) YYABORT; }
- | NAME ':' NAME '=' attvalue { if (xml_parse_attr(_XY, $1, $3, $5) < 0) YYABORT; }
+attr : NAME '=' attvalue { if (xml_parse_attr(_XY, NULL, $1, $3) < 0) YYABORT;
+ _PARSE_DEBUG("attr -> NAME = attvalue"); }
+ | NAME ':' NAME '=' attvalue { if (xml_parse_attr(_XY, $1, $3, $5) < 0) YYABORT;
+ _PARSE_DEBUG("attr -> NAME : NAME = attvalue"); }
;
attvalue : '\"' STRING '\"' { $$=$2; /* $2 must be consumed */}
diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c
index 6bac6f5c..45e7bfdb 100644
--- a/lib/src/clixon_xml_sort.c
+++ b/lib/src/clixon_xml_sort.c
@@ -1057,7 +1057,7 @@ xml_insert2(cxobj *xp,
/*! Insert xc as child to xp in sorted place. Remove xc from previous parent.
*
* @param[in] xp Parent xml node. If NULL just remove from old parent.
- * @param[in] x Child xml node to insert under xp
+ * @param[in] xi Child xml node to insert under xp
* @param[in] ins Insert operation (if ordered-by user)
* @param[in] key_val Key if x is LIST and ins is before/after, val if LEAF_LIST
* @param[in] nsc_key Network namespace for key
diff --git a/test/test_datastore_creator.sh b/test/test_datastore_creator.sh
new file mode 100755
index 00000000..53f1caa9
--- /dev/null
+++ b/test/test_datastore_creator.sh
@@ -0,0 +1,139 @@
+#!/usr/bin/env bash
+# test for data creator attribute: add same object from sessions s1 and s2
+# Restart and ensure attributes remain
+
+# Magic line must be first in script (see README.md)
+s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
+
+cfg=$dir/conf.xml
+fyang=$dir/clixon-example.yang
+
+: ${clixon_util_xpath:=clixon_util_xpath}
+
+cat < $cfg
+
+ $cfg
+ ${YANG_INSTALLDIR}
+ $dir
+ $fyang
+ $clispec
+ /usr/local/lib/$APPNAME/cli
+ example
+ /usr/local/var/run/$APPNAME.sock
+ /usr/local/var/run/$APPNAME.pidfile
+ $dir
+ true
+
+EOF
+
+cat < $fyang
+module clixon-example{
+ yang-version 1.1;
+ namespace "urn:example:clixon";
+ prefix ex;
+
+ container table {
+ list parameter{
+ key name;
+ leaf name{
+ type string;
+ }
+ leaf value{
+ type string;
+ }
+ }
+ }
+}
+EOF
+
+new "test params: -f $cfg"
+
+if [ $BE -ne 0 ]; then
+ new "kill old backend"
+ sudo clixon_backend -zf $cfg
+ if [ $? -ne 0 ]; then
+ err
+ fi
+ new "start backend -s init -f $cfg"
+ start_backend -s init -f $cfg
+fi
+
+new "wait backend 1"
+wait_backend
+
+conf="-d candidate -b $dir -y $fyang"
+
+new "s1 add x"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none " "" " "
+
+new "datastore get"
+expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] "
+
+new "rpc get-config"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " " "" ""
+
+# duplicate
+new "s1 merge x"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none " "" " "
+
+new "datastore get"
+expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] "
+
+# New service
+new "s2 merge x"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none " "" " "
+
+new "datastore get"
+expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] s2 /table/parameter\[name=\"x\"\] "
+
+# New entry
+new "s1 create y=bar"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none " "" " "
+
+new "datastore get"
+expectpart "$(sudo $clixon_util_xpath -f $dir/candidate_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] /table/parameter\[name=\"y\"\] s2 /table/parameter\[name=\"x\"\] "
+
+# To running
+new "commit to running"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " " "" " "
+
+new "datastore get running"
+expectpart "$(sudo $clixon_util_xpath -f $dir/running_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] /table/parameter\[name=\"y\"\] s2 /table/parameter\[name=\"x\"\] "
+
+new "rpc get-config"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " " "" ""
+
+if [ $BE -ne 0 ]; then
+ new "kill old backend"
+ sudo clixon_backend -zf $cfg
+ if [ $? -ne 0 ]; then
+ err
+ fi
+fi
+
+if [ $BE -ne 0 ]; then
+ new "start backend -s running -f $cfg"
+ start_backend -s running -f $cfg
+fi
+
+new "wait backend 2"
+wait_backend
+
+new "datastore get running"
+expectpart "$(sudo $clixon_util_xpath -f $dir/running_db -p /config/creators)" 0 "s1 /table/parameter\[name=\"x\"\] /table/parameter\[name=\"y\"\] s2 /table/parameter\[name=\"x\"\] "
+
+new "rpc get-config"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " " "" ""
+
+if [ $BE -ne 0 ]; then
+ new "kill old backend"
+ sudo clixon_backend -zf $cfg
+ if [ $? -ne 0 ]; then
+ err
+ fi
+fi
+
+rm -rf $dir
+
+new "endtest"
+endtest
diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in
index 6d9987d8..b5615e34 100644
--- a/yang/clixon/Makefile.in
+++ b/yang/clixon/Makefile.in
@@ -42,7 +42,7 @@ datarootdir = @datarootdir@
YANG_INSTALLDIR = @YANG_INSTALLDIR@
# Note: mirror these to test/config.sh.in
-YANGSPECS = clixon-config@2023-05-01.yang # 6.3
+YANGSPECS = clixon-config@2023-11-01.yang # 6.5
YANGSPECS += clixon-lib@2023-11-01.yang # 6.5
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
diff --git a/yang/clixon/clixon-config@2023-03-01.yang b/yang/clixon/clixon-config@2023-11-01.yang
similarity index 94%
rename from yang/clixon/clixon-config@2023-03-01.yang
rename to yang/clixon/clixon-config@2023-11-01.yang
index 3851572f..cd72a1fc 100644
--- a/yang/clixon/clixon-config@2023-03-01.yang
+++ b/yang/clixon/clixon-config@2023-11-01.yang
@@ -8,7 +8,10 @@ module clixon-config {
}
import clixon-autocli {
prefix autocli;
- }
+ }
+ import clixon-lib {
+ prefix cl;
+ }
organization
"Clicon / Clixon";
@@ -46,6 +49,20 @@ module clixon-config {
***** END LICENSE BLOCK *****";
+ revision 2023-11-01 {
+ description
+ "Added options:
+ CLICON_CREATOR_ATTR
+ Released in Clixon 6.5";
+ }
+ revision 2023-05-01 {
+ description
+ "Added options:
+ CLICON_CONFIG_EXTEND
+ CLICON_PLUGIN_DLOPEN_GLOBAL
+ Moved datastore-format datatype to clixon-lib
+ Released in Clixon 6.3";
+ }
revision 2023-03-01 {
description
"Added options:
@@ -225,7 +242,7 @@ module clixon-config {
Renamed CLICON_XMLDB_CACHE to CLICON_DATASTORE_CACHE (changed type)
Deleted: CLICON_XMLDB_PLUGIN, CLICON_USE_STARTUP_CONFIG";
}
- revision 2019-03-05{
+ revision 2019-03-05{
description
"Changed URN. Changed top-level symbol to clixon-config.
Released in Clixon 3.10";
@@ -274,27 +291,6 @@ module clixon-config {
}
}
}
- typedef datastore_format{
- description
- "Datastore format (only xml and json implemented in actual data.";
- type enumeration{
- enum xml{
- description
- "Save and load xmldb as XML
- More specifically, such a file looks like: ... provided
- DATASTORE_TOP_SYMBOL is 'config'";
- }
- enum json{
- description "Save and load xmldb as JSON";
- }
- enum text{
- description "'Curly' C-like text format";
- }
- enum cli{
- description "CLI format";
- }
- }
- }
typedef datastore_cache{
description
"XML configuration, ie running/candididate/ datastore cache behaviour.";
@@ -461,6 +457,15 @@ module clixon-config {
You can override file setting with -E command-line option.
Note that due to bootstraping this value is only meaningful in the main config file";
}
+ leaf CLICON_CONFIG_EXTEND {
+ type string;
+ description
+ "If specified load an application-specific configuration YANG that overrides
+ this config.
+ Normally, that YANG imports clixon-config.
+ This field is a 'bootstrap' field.
+ ";
+ }
leaf CLICON_YANG_MAIN_FILE {
type string;
description
@@ -520,7 +525,13 @@ module clixon-config {
leaf CLICON_YANG_SCHEMA_MOUNT{
type boolean;
description
- "YANG schema mount, RFC 8528";
+ "YANG schema mount, RFC 8528.
+ When enabled, mount-points as defined by the 'yangmnt:mount-point' extension can
+ be populated by other YANGs than the root.
+ This is controlled by the ca_yang_mount plugin callback by returning a assigning a
+ yanglib module-set section that corresponds to the mounted YANGs.
+ Also, schema mount statistics is added to state data
+ Further, autocli syntax is added by definining a tree resolve wrapper";
default false;
}
leaf CLICON_BACKEND_REGEXP {
@@ -571,6 +582,18 @@ module clixon-config {
RFC6242 for example.
This only applies to the external NETCONF";
}
+ leaf CLICON_NETCONF_CREATOR_ATTR {
+ type boolean;
+ default false;
+ description
+ "If set, clixon will accept the 'creator' attribute as defined by the
+ creator annotation in clixon-lib.
+ It can be used when several clients (such as a 'service') can create the same object.
+ If one such client/service is deleted, the object is deleted only if all services
+ that created the object are deleted.
+ The clixon controller uses this feature, but could in principle be used by other
+ applications.";
+ }
leaf CLICON_RESTCONF_API_ROOT {
type string;
default "/restconf";
@@ -612,7 +635,7 @@ module clixon-config {
edit operation.
Setting this option disables this behaviour, ie the startup configuration is NOT
automatically updated.
- If this option is false, the startup is autoamtically updated following the RFC";
+ If this option is false, the startup is automatically updated following the RFC";
}
leaf CLICON_RESTCONF_USER {
type string;
@@ -675,7 +698,7 @@ module clixon-config {
Both feature clixon-restconf:http-data and restconf/enable-http-data
must be enabled for this match to occur.";
}
- leaf CLICON_HTTP_DATA_ROOT{
+ leaf CLICON_HTTP_DATA_ROOT{
if-feature "clrc:http-data";
type string;
default "/var/www";
@@ -931,7 +954,7 @@ module clixon-config {
Others are experimental (in Clixon 5.5)";
}
leaf CLICON_XMLDB_FORMAT {
- type datastore_format;
+ type cl:datastore_format;
default xml;
description "XMLDB datastore format.";
}
@@ -1005,6 +1028,18 @@ module clixon-config {
as well as the CLIgen callbacks.
See https://clixon-docs.readthedocs.io/en/latest/backend.html#plugin-callback-guidelines";
}
+ leaf CLICON_PLUGIN_DLOPEN_GLOBAL {
+ type boolean;
+ default false;
+ description
+ "Local/global flag for dlopen as described in the man page.
+ This applies to the opening of all clixon plugins (backend/cli/netconf/restconf)
+ when loading the shared .so file with dlopen.
+ If false: Symbols defined in this shared object are not made available to resolve
+ references in subsequently loaded shared objects (default).
+ If true: The symbols defined by this shared object will be made available for symbol res‐
+ olution of subsequently loaded shared objects.";
+ }
leaf CLICON_YANG_AUGMENT_ACCEPT_BROKEN {
type boolean;
default false;
@@ -1094,7 +1129,7 @@ module clixon-config {
restconf GET.
The module state data is on the form:
...
- instead where the modile state is on the form:
+ instead where the module state is on the form:
...
See also CLICON_XMLDB_MODSTATE where the module state info is used to tag datastores
with module information.";
@@ -1131,7 +1166,7 @@ module clixon-config {
default false;
description
"Enable event stream discovery as described in RFC 5277
- sections 3.2. If enabled, available streams will appear
+ section 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_DISCOVERY_RFC8040 {
diff --git a/yang/clixon/clixon-lib@2023-11-01.yang b/yang/clixon/clixon-lib@2023-11-01.yang
index 620bfedc..f34a6f01 100644
--- a/yang/clixon/clixon-lib@2023-11-01.yang
+++ b/yang/clixon/clixon-lib@2023-11-01.yang
@@ -71,6 +71,7 @@ module clixon-lib {
revision 2023-11-01 {
description
"Added ignore-compare extension
+ Added creator meta configuration
Removed obsolete extension autocli-op
Released in 6.5.0";
}
@@ -209,6 +210,21 @@ module clixon-lib {
Limitations: only objects that are actually added or deleted.
A sub-object will not be noted";
}
+ container creators{
+ config false;
+ description "Meta-data for creator attribute.";
+ list creator {
+ key name;
+ leaf name {
+ description "Name of creator / service (instance) name";
+ type string;
+ }
+ leaf-list path {
+ description "Path to object";
+ type string;
+ }
+ }
+ }
rpc debug {
description "Set debug level of backend.";
input {