diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in
index 4c55d23f..773a7d48 100644
--- a/apps/backend/Makefile.in
+++ b/apps/backend/Makefile.in
@@ -85,6 +85,7 @@ APPL = clixon_backend
APPSRC = backend_main.c
APPSRC += backend_socket.c
APPSRC += backend_client.c
+APPSRC += backend_get.c
APPSRC += backend_plugin_restconf.c # Pseudo plugin for restconf daemon
APPSRC += backend_startup.c
APPOBJ = $(APPSRC:.c=.o)
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index f6d16f9d..e28647cd 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -69,8 +69,9 @@
#include "clixon_backend_handle.h"
#include "clixon_backend_plugin.h"
#include "clixon_backend_commit.h"
-#include "backend_client.h"
#include "backend_handle.h"
+#include "backend_get.h"
+#include "backend_client.h"
/*! Find client by session-id
* @param[in] ce_list List of clients
@@ -156,108 +157,6 @@ backend_client_rm(clicon_handle h,
return backend_client_delete(h, ce); /* actually purge it */
}
-/*!
- * Maybe should be in the restconf client instead of backend?
- * @param[in] h Clicon handle
- * @param[in] yspec Yang spec
- * @param[in] xpath Xpath selection, not used but may be to filter early
- * @param[out] xrs XML restconf-state node
- * @see netconf_hello_server
- * @see rfc8040 Sections 9.1
- */
-static int
-client_get_capabilities(clicon_handle h,
- yang_stmt *yspec,
- char *xpath,
- cxobj **xret)
-{
- int retval = -1;
- cxobj *xrstate = NULL; /* xml restconf-state node */
- cbuf *cb = NULL;
-
- if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){
- clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
- goto done;
- }
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- cprintf(cb, "");
- cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit");
- cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0");
- cprintf(cb, "");
- if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0)
- goto done;
- retval = 0;
- done:
- if (cb)
- cbuf_free(cb);
- return retval;
-}
-
-/*! Get streams state according to RFC 8040 or RFC5277 common function
- * @param[in] h Clicon handle
- * @param[in] yspec Yang spec
- * @param[in] xpath Xpath selection, not used but may be to filter early
- * @param[in] module Name of yang module
- * @param[in] top Top symbol, ie netconf or restconf-state
- * @param[in,out] xret Existing XML tree, merge x into this
- * @retval -1 Error (fatal)
- * @retval 0 Statedata callback failed
- * @retval 1 OK
- */
-static int
-client_get_streams(clicon_handle h,
- yang_stmt *yspec,
- char *xpath,
- yang_stmt *ymod,
- char *top,
- cxobj **xret)
-{
- int retval = -1;
- yang_stmt *yns = NULL; /* yang namespace */
- cxobj *x = NULL;
- cbuf *cb = NULL;
- int ret;
-
- if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
- clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod));
- goto done;
- }
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns));
- /* Second argument is a hack to have the same function for the
- * RFC5277 and 8040 stream cases
- */
- if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
- goto done;
- cprintf(cb,"%s>", top);
-
- if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
- if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
- goto done;
- goto fail;
- }
- if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
- retval = 1;
- done:
- if (cb)
- cbuf_free(cb);
- if (x)
- xml_free(x);
- return retval;
- fail:
- retval = 0;
- goto done;
-}
-
/*! Get clixon per datastore stats
* @param[in] h Clicon handle
* @param[in] dbname Datastore name
@@ -292,445 +191,6 @@ clixon_stats_get_db(clicon_handle h,
return retval;
}
-/*! Get system state-data, including streams and plugins
- * @param[in] h Clicon handle
- * @param[in] xpath XPath selection, may be used to filter early
- * @param[in] nsc XML Namespace context for xpath
- * @param[in] content config/state or both
- * @param[in,out] xret Existing XML tree, merge x into this
- * @retval -1 Error (fatal)
- * @retval 0 Statedata callback failed (clicon_err called)
- * @retval 1 OK
- */
-static int
-client_statedata(clicon_handle h,
- char *xpath,
- cvec *nsc,
- netconf_content content,
- cxobj **xret)
-{
- int retval = -1;
- yang_stmt *yspec;
- yang_stmt *ymod;
- int ret;
- char *namespace;
- cbuf *cb = NULL;
-
- clicon_debug(1, "%s", __FUNCTION__);
- if ((yspec = clicon_dbspec_yang(h)) == NULL){
- clicon_err(OE_YANG, ENOENT, "No yang spec");
- goto done;
- }
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- /* Add default state to config if present */
- if (xml_default_recurse(*xret, 1) < 0)
- goto done;
- /* Add default global state */
- if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0)
- goto done;
-
- if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
- if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
- clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
- goto done;
- }
- if ((namespace = yang_find_mynamespace(ymod)) == NULL){
- clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
- goto done;
- }
- cprintf(cb, "", namespace);
- if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
- goto done;
- if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
- }
- if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
- if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){
- clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found");
- goto done;
- }
- if ((namespace = yang_find_mynamespace(ymod)) == NULL){
- clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
- goto done;
- }
- cbuf_reset(cb);
- /* XXX This code does not filter state data with xpath */
- cprintf(cb, "", namespace);
- if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
- goto done;
- if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
- if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0)
- goto done;
- }
- if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
- if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
- }
- /* Use plugin state callbacks */
- if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0)
- goto done;
- if (ret == 0)
- goto fail;
- retval = 1; /* OK */
- done:
- clicon_debug(1, "%s %d", __FUNCTION__, retval);
- if (cb)
- cbuf_free(cb);
- return retval;
- fail:
- retval = 0;
- goto done;
-}
-
-/*! Retrieve all or part of a specified configuration.
- *
- * Function reused from both from_client_get() and from_client_get_config
- * @param[in] yspec
- * @param[in] db
- * @param[in] xpath
- * @param[in] username
- * @param[in] content
- * @param[in] depth
- * @param[out] cbret Return xml tree, eg ..., = (offset + limit))
- remaining = total - (offset + limit);
- }
- /* XXX need only first for remaining, but all for NACM */
- if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
- goto done;
- /* Add remain attribute to first returning element */
- if (xlen){
- cxobj *x;
- cxobj *xa;
- x = xvec[0];
- if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
- goto done;
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- cprintf(cb, "%u", remaining);
- if (xml_value_set(xa, cbuf_get(cb)) < 0)
- goto done;
- if (xml_prefix_set(xa, "cp") < 0)
- goto done;
- if (xmlns_set(x, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0)
- goto done;
- }
-#endif
- /* Pre-NACM access step */
- xnacm = clicon_nacm_cache(h);
- if (xnacm != NULL){ /* Do NACM validation */
-#ifndef CLIXON_PAGINATION
- if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
- goto done;
-#endif
- /* NACM datanode/module read validation */
- if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
- goto done;
- }
- cprintf(cbret, "", NETCONF_BASE_NAMESPACE);
- if (xret==NULL)
- cprintf(cbret, "");
- else{
- if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0)
- goto done;
- if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0)
- goto done;
- }
- cprintf(cbret, "");
- ok:
- retval = 0;
- done:
- if (cb)
- cbuf_free(cb);
- if (xerr)
- xml_free(xerr);
- if (xvec)
- free(xvec);
- if (xret)
- xml_free(xret);
- return retval;
-}
-
-#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION)
-/*! Help function for parsing restconf query parameter and setting netconf attribute
- *
- * If not "unbounded", parse and set a numeric value
- * @param[in] h Clixon handle
- * @param[in] name Name of attribute
- * @param[in] defaultstr Default string which is accepted and sets value to 0
- * @param[in,out] cbret Output buffer for internal RPC message
- * @param[out] value Value
- * @retval -1 Error
- * @retval 0 Invalid, cbret set
- * @retval 1 OK
- */
-static int
-element2value(clicon_handle h,
- cxobj *xe,
- char *name,
- char *defaultstr,
- cbuf *cbret,
- uint32_t *value)
-{
- int retval = -1;
- char *valstr;
- int ret;
- char *reason = NULL;
- cxobj *x;
-
- *value = 0;
- if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL &&
- (valstr = xml_body(x)) != NULL &&
- strcmp(valstr, defaultstr) != 0){
- if ((ret = parse_uint32(valstr, value, &reason)) < 0){
- clicon_err(OE_XML, errno, "parse_uint32");
- goto done;
- }
- if (ret == 0){
- if (netconf_bad_element(cbret, "application",
- name, "Unrecognized value") < 0)
- goto done;
- goto fail;
- }
- }
- retval = 1;
- done:
- if (reason)
- free(reason);
- return retval;
- fail:
- retval = 0;
- goto done;
-}
-#endif
-
-/*! Retrieve all or part of a specified configuration.
- *
- * @param[in] h Clicon handle
- * @param[in] xe Request:
- * @param[out] cbret Return xml tree, eg ...,
- * The set of namespace declarations are those in scope on the
- * element.
- */
- else
- if (xml_nsctx_node(xfilter, &nsc) < 0)
- goto done;
- if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
- goto done;
- if (nsc)
- xml_nsctx_free(nsc);
- nsc = nsc1;
- }
- /* Clixon extensions: depth */
- if ((attr = xml_find_value(xe, "depth")) != NULL){
- char *reason = NULL;
- if ((ret = parse_int32(attr, &depth, &reason)) < 0){
- clicon_err(OE_XML, errno, "parse_int32");
- goto done;
- }
- if (ret == 0){
- if (netconf_bad_attribute(cbret, "application",
- "depth", "Unrecognized value of depth attribute") < 0)
- goto done;
- goto ok;
- }
- }
-#ifdef CLIXON_PAGINATION
- /* limit */
- if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0)
- goto done;
- /* offset */
- if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0)
- goto done;
- /* direction */
- if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){
- direction = xml_body(x);
- if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){
- if (netconf_bad_attribute(cbret, "application",
- "direction", "Unrecognized value of direction attribute") < 0)
- goto done;
- goto ok;
- }
- }
- /* sort */
- if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL)
- sort = xml_body(x);
- if (sort) ; /* XXX */
- /* where */
- if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL)
- where = xml_body(x);
- /* Build a "predicate" cbuf
- * This solution uses xpath predicates to translate "limit" and "offset" to
- * relational operators <>.
- */
- if ((cbpath = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- /* This uses xpath. Maybe limit should use parameters */
- cprintf(cbpath, "%s", xpath);
- if (where)
- cprintf(cbpath, "[%s]", where);
- if (offset){
- cprintf(cbpath, "[%u <= position()", offset);
- if (limit)
- cprintf(cbpath, " and position() < %u", limit+offset);
- cprintf(cbpath, "]");
- }
- else if (limit)
- cprintf(cbpath, "[position() < %u]", limit);
-#endif /* CLIXON_PAGINATION */
- if ((ret = client_get_config_only(h, nsc, yspec, db, xpath,
-#ifdef CLIXON_PAGINATION
- limit, offset,
- cbuf_get(cbpath),
-#endif
- username, -1, cbret)) < 0)
- goto done;
- ok:
- retval = 0;
- done:
- if (xpath)
- free(xpath);
- if (nsc)
- xml_nsctx_free(nsc);
- if (cbx)
- cbuf_free(cbx);
- return retval;
-}
-
/*! Loads all or part of a specified configuration to target configuration
*
* @param[in] h Clicon handle
@@ -863,7 +323,6 @@ from_client_edit_config(clicon_handle h,
if (xml_sort_recurse(xc) < 0)
goto done;
if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){
- clicon_debug(1, "%s ERROR PUT", __FUNCTION__);
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
goto done;
goto ok;
@@ -940,13 +399,14 @@ from_client_copy_config(clicon_handle h,
void *arg,
void *regarg)
{
- int retval = -1;
+ int retval = -1;
struct client_entry *ce = (struct client_entry *)arg;
- char *source;
- char *target;
- uint32_t iddb;
- uint32_t myid = ce->ce_id;
- cbuf *cbx = NULL; /* Assist cbuf */
+ char *source;
+ char *target;
+ uint32_t iddb;
+ uint32_t myid = ce->ce_id;
+ cbuf *cbx = NULL; /* Assist cbuf */
+ cbuf *cbmsg = NULL;
if ((source = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
@@ -983,7 +443,12 @@ from_client_copy_config(clicon_handle h,
goto ok;
}
if (xmldb_copy(h, source, target) < 0){
- if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Copy %s datastore to %s: %s", source, target, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
@@ -992,6 +457,8 @@ from_client_copy_config(clicon_handle h,
ok:
retval = 0;
done:
+ if (cbmsg)
+ cbuf_free(cbmsg);
if (cbx)
cbuf_free(cbx);
return retval;
@@ -1019,6 +486,7 @@ from_client_delete_config(clicon_handle h,
uint32_t iddb;
uint32_t myid = ce->ce_id;
cbuf *cbx = NULL; /* Assist cbuf */
+ cbuf *cbmsg = NULL;
/* XXX should use prefix cf edit_config */
if ((target = netconf_db_find(xe, "target")) == NULL ||
@@ -1046,12 +514,22 @@ from_client_delete_config(clicon_handle h,
goto ok;
}
if (xmldb_delete(h, target) < 0){
- if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Delete %s datastore: %s", target, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
if (xmldb_create(h, target) < 0){
- if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Create %s datastore: %s", target, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "protocol", cbuf_get(cbmsg))< 0)
goto done;
goto ok;
}
@@ -1060,6 +538,8 @@ from_client_delete_config(clicon_handle h,
ok:
retval = 0;
done:
+ if (cbmsg)
+ cbuf_free(cbmsg);
if (cbx)
cbuf_free(cbx);
return retval;
@@ -1208,280 +688,6 @@ from_client_unlock(clicon_handle h,
return retval;
}
-/*! Retrieve running configuration and device state information.
- *
- * @param[in] h Clicon handle
- * @param[in] xe Request:
- * @param[out] cbret Return xml tree, eg ...,
- * The set of namespace declarations are those in scope on the
- * element.
- */
- else
- if (xml_nsctx_node(xfilter, &nsc) < 0)
- goto done;
- if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
- goto done;
- if (nsc)
- xml_nsctx_free(nsc);
- nsc = nsc1;
- }
- /* Clixon extensions: content */
- if ((attr = xml_find_value(xe, "content")) != NULL)
- content = netconf_content_str2int(attr);
- /* Clixon extensions: depth */
- if ((attr = xml_find_value(xe, "depth")) != NULL){
- if ((ret = parse_int32(attr, &depth, &reason)) < 0){
- clicon_err(OE_XML, errno, "parse_int32");
- goto done;
- }
- if (ret == 0){
- if (netconf_bad_attribute(cbret, "application",
- "depth", "Unrecognized value of depth attribute") < 0)
- goto done;
- goto ok;
- }
- }
-#ifdef CLIXON_PAGINATION
- /* limit */
- if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0)
- goto done;
- /* offset */
- if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0)
- goto done;
- /* direction */
- if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){
- direction = xml_body(x);
- if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){
- if (netconf_bad_attribute(cbret, "application",
- "direction", "Unrecognized value of direction attribute") < 0)
- goto done;
- goto ok;
- }
- }
- /* sort */
- if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL)
- sort = xml_body(x);
- if (sort) ; /* XXX */
- /* where */
- if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL)
- where = xml_body(x);
- /* Build a "predicate" cbuf
- * This solution uses xpath predicates to translate "limit" and "offset" to
- * relational operators <>.
- */
- if ((cbpath = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- /* This uses xpath. Maybe limit should use parameters */
- cprintf(cbpath, "%s", xpath);
- if (where)
- cprintf(cbpath, "[%s]", where);
- if (offset){
- cprintf(cbpath, "[%u <= position()", offset);
- if (limit)
- cprintf(cbpath, " and position() < %u", limit+offset);
- cprintf(cbpath, "]");
- }
- else if (limit)
- cprintf(cbpath, "[position() < %u]", limit);
-#endif /* CLIXON_PAGINATION */
- if (content == CONTENT_CONFIG){ /* config only, no state */
- if (client_get_config_only(h, nsc, yspec, "running", xpath,
-#ifdef CLIXON_PAGINATION
- limit, offset,
- cbuf_get(cbpath),
-#endif
- username, depth, cbret) < 0)
- goto done;
- goto ok;
- }
- /* If not only-state, then read running config
- * Note xret can be pruned by nacm below and change name and
- * merged with state data, so zero-copy cant be used
- * Also, must use external namespace context here due to stmt
- */
- if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
- if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) {
- if (netconf_operation_failed(cbret, "application", "read registry")< 0)
- goto done;
- goto ok;
- }
- }
- else{
- if (xmldb_get0(h, "running", YB_MODULE, nsc, xpath, 1, &xret, NULL, NULL) < 0) {
- if (netconf_operation_failed(cbret, "application", "read registry")< 0)
- goto done;
- goto ok;
- }
- }
- /* If not only config,
- * get state data from plugins as defined by plugin_statedata(), if any
- */
- clicon_err_reset();
- if ((ret = client_statedata(h, xpath?xpath:"/", nsc, content, &xret)) < 0)
- goto done;
- if (ret == 0){ /* Error from callback (error in xret) */
- if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
- /* Check XML by validating it. return internal error with error cause
- * Primarily intended for user-supplied state-data.
- * The whole config tree must be present in case the state data references config data
- */
- if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
- goto done;
- if (ret > 0 &&
- (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
- goto done;
- if (ret == 0){
- if (clicon_debug_get())
- clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
- if (clixon_netconf_internal_error(xerr,
- ". Internal error, state callback returned invalid XML",
- NULL) < 0)
- goto done;
- if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- } /* CLICON_VALIDATE_STATE_XML */
-
- if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
- /* Keep state data only, remove everything that is not config. Note that state data
- * may be a sub-part in a config tree, we need to traverse to find all
- */
- if (xml_non_config_data(xret, NULL) < 0)
- goto done;
- if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
- goto done;
- if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
- goto done;
- }
- /* Code complex to filter out anything that is outside of xpath
- * Actually this is a safety catch, should really be done in plugins
- * and modules_state functions.
- */
- if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
- goto done;
- /* If vectors are specified then mark the nodes found and
- * then filter out everything else,
- * otherwise return complete tree.
- */
- if (xvec != NULL){
- for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */
- if (xret==NULL)
- cprintf(cbret, "");
- else{
- if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0)
- goto done;
- /* Top level is data, so add 1 to depth if significant */
- if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0)
- goto done;
- }
- cprintf(cbret, "");
- ok:
- retval = 0;
- done:
- clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
-
- if (cbpath)
- cbuf_free(cbpath);
- if (reason)
- free(reason);
- if (xerr)
- xml_free(xerr);
- if (xpath)
- free(xpath);
- if (xvec)
- free(xvec);
- if (nsc)
- xml_nsctx_free(nsc);
- if (xret)
- xml_free(xret);
- return retval;
-}
-
/*! Request graceful termination of a NETCONF session.
* @param[in] h Clicon handle
* @param[in] xe Request:
@@ -1564,321 +770,6 @@ from_client_kill_session(clicon_handle h,
return retval;
}
-#ifdef LIST_PAGINATION
-
-/*! Retrieve collection configuration and device state information
- *
- * @param[in] h Clicon handle
- * @param[in] xe Request:
- * @param[out] cbret Return xml tree, eg ..., stuctured path tree */
- if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0)
- goto done;
- if (ret == 0){
- if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- /* get last element of path, eg /a/b/c, get c */
- if ((cp = PREVQ(clixon_path *, path_tree)) == NULL){
- if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
- goto done;
- goto ok;
- }
- /* get yang of last element */
- if ((y = cp->cp_yang) == NULL){
- if (netconf_bad_element(cbret, "application", "list-target", "No yang associated with path") < 0)
- goto done;
- goto ok;
- }
- if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
- if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
- goto done;
- goto ok;
- }
- /* Build a "predicate" cbuf
- * This solution uses xpath predicates to translate "limit" and "offset" to
- * relational operators <>.
- */
- if ((cbpath = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- /* This uses xpath. Maybe limit should use parameters */
- cprintf(cbpath, "%s", xpath);
- if (where)
- cprintf(cbpath, "[%s]", where);
- if (offset){
- cprintf(cbpath, "[%u <= position()", offset);
- if (limit)
- cprintf(cbpath, " and position() < %u", limit+offset);
- cprintf(cbpath, "]");
- }
- else if (limit)
- cprintf(cbpath, "[position() < %u]", limit);
-
- /* Split into CT or CF */
- if (yang_config_ancestor(y) == 1){ /* CT */
- if (content == CONTENT_CONFIG || content == CONTENT_ALL){
- if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) {
- if (netconf_operation_failed(cbret, "application", "read registry")< 0)
- goto done;
- goto ok;
- }
- if (ret == 0){
- if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- /* First get number of hits (ALL entries: note not optimized) */
- if (xpath_count(xret, nsc, xpath, &total) < 0)
- goto done;
- if (total < (offset + limit))
- remaining = 0;
- else
- remaining = total - (offset + limit);
- }
- /* There may be CF data in a CT collection */
- if (content == CONTENT_ALL){
- if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0)
- goto done;
- }
- }
- else { /* CF */
- /* There can be no CT data in a CF collection */
- if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){
- if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0)
- goto done;
- if (ret == 0){ /* Error from callback (error in xret) */
- if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- }
- }
- if (0 && clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
- /* XXX: subset in collection may not have mandatory variables */
- /* Check XML by validating it. return internal error with error cause
- * Primarily intended for user-supplied state-data.
- * The whole config tree must be present in case the state data references config data
- */
- if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
- goto done;
- if (ret > 0 &&
- (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
- goto done;
- if (ret == 0){
- if (clicon_debug_get())
- clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
- if (clixon_netconf_internal_error(xerr,
- ". Internal error, state callback returned invalid XML",
- NULL) < 0)
- goto done;
- if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
- goto done;
- goto ok;
- }
- } /* CLICON_VALIDATE_STATE_XML */
-
- if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
- /* Keep state data only, remove everything that is not config. Note that state data
- * may be a sub-part in a config tree, we need to traverse to find all
- */
- if (xml_non_config_data(xret, NULL) < 0)
- goto done;
- if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
- goto done;
- if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
- goto done;
- }
- /* Code complex to filter out anything that is outside of xpath
- * Actually this is a safety catch, should really be done in plugins
- * and modules_state functions.
- */
- if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0)
- goto done;
-
- /* Pre-NACM access step */
- xnacm = clicon_nacm_cache(h);
- if (xnacm != NULL){ /* Do NACM validation */
- /* NACM datanode/module read validation */
- if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
- goto done;
- }
- cprintf(cbret, "",
- NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */
- if ((ns = yang_find_mynamespace(y)) != NULL) {
- if ((cb = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, errno, "cbuf_new");
- goto done;
- }
- for (i=0; i0?depth+1:depth) < 0)
- goto done;
- }
- }
- cprintf(cbret, "");
- ok:
- retval = 0;
- done:
- clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
- if (datastore)
- free(datastore);
- if (path_tree)
- clixon_path_free(path_tree);
- if (xtop)
- xml_free(xtop);
- if (cbpath)
- cbuf_free(cbpath);
- if (cb)
- cbuf_free(cb);
- if (reason)
- free(reason);
- if (xerr)
- xml_free(xerr);
- if (xvec)
- free(xvec);
- if (nsc)
- xml_nsctx_free(nsc);
- if (xret)
- xml_free(xret);
- return retval;
-}
-#endif /* LIST_PAGINATION */
/*! Create a notification subscription
* @param[in] h Clicon handle
diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c
new file mode 100644
index 00000000..57d36e2a
--- /dev/null
+++ b/apps/backend/backend_get.c
@@ -0,0 +1,1108 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2017-2019 Olof Hagsand
+ Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clixon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clixon_backend_handle.h"
+#include "clixon_backend_plugin.h"
+#include "clixon_backend_commit.h"
+#include "backend_handle.h"
+#include "backend_get.h"
+
+/*!
+ * Maybe should be in the restconf client instead of backend?
+ * @param[in] h Clicon handle
+ * @param[in] yspec Yang spec
+ * @param[in] xpath Xpath selection, not used but may be to filter early
+ * @param[out] xrs XML restconf-state node
+ * @see netconf_hello_server
+ * @see rfc8040 Sections 9.1
+ */
+static int
+client_get_capabilities(clicon_handle h,
+ yang_stmt *yspec,
+ char *xpath,
+ cxobj **xret)
+{
+ int retval = -1;
+ cxobj *xrstate = NULL; /* xml restconf-state node */
+ cbuf *cb = NULL;
+
+ if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
+ goto done;
+ }
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cb, "");
+ cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit");
+ cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0");
+ cprintf(cb, "");
+ if (clixon_xml_parse_string(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
+/*! Get streams state according to RFC 8040 or RFC5277 common function
+ * @param[in] h Clicon handle
+ * @param[in] yspec Yang spec
+ * @param[in] xpath Xpath selection, not used but may be to filter early
+ * @param[in] module Name of yang module
+ * @param[in] top Top symbol, ie netconf or restconf-state
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 Statedata callback failed
+ * @retval 1 OK
+ */
+static int
+client_get_streams(clicon_handle h,
+ yang_stmt *yspec,
+ char *xpath,
+ yang_stmt *ymod,
+ char *top,
+ cxobj **xret)
+{
+ int retval = -1;
+ yang_stmt *yns = NULL; /* yang namespace */
+ cxobj *x = NULL;
+ cbuf *cb = NULL;
+ int ret;
+
+ if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod));
+ goto done;
+ }
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns));
+ /* Second argument is a hack to have the same function for the
+ * RFC5277 and 8040 stream cases
+ */
+ if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
+ goto done;
+ cprintf(cb,"%s>", top);
+
+ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
+ if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
+ goto done;
+ goto fail;
+ }
+ if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ retval = 1;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (x)
+ xml_free(x);
+ return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+
+/*! Get system state-data, including streams and plugins
+ * @param[in] h Clicon handle
+ * @param[in] xpath XPath selection, may be used to filter early
+ * @param[in] nsc XML Namespace context for xpath
+ * @param[in,out] xret Existing XML tree, merge x into this
+ * @retval -1 Error (fatal)
+ * @retval 0 Statedata callback failed (clicon_err called)
+ * @retval 1 OK
+ */
+static int
+client_statedata(clicon_handle h,
+ char *xpath,
+ cvec *nsc,
+ cxobj **xret)
+{
+ int retval = -1;
+ yang_stmt *yspec;
+ yang_stmt *ymod;
+ int ret;
+ char *namespace;
+ cbuf *cb = NULL;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ /* Add default state to config if present */
+ if (xml_default_recurse(*xret, 1) < 0)
+ goto done;
+ /* Add default global state */
+ if (xml_global_defaults(h, *xret, nsc, xpath, yspec, 1) < 0)
+ goto done;
+
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
+ if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
+ goto done;
+ }
+ if ((namespace = yang_find_mynamespace(ymod)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
+ goto done;
+ }
+ cprintf(cb, "", namespace);
+ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
+ goto done;
+ if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
+ if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found");
+ goto done;
+ }
+ if ((namespace = yang_find_mynamespace(ymod)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
+ goto done;
+ }
+ cbuf_reset(cb);
+ /* XXX This code does not filter state data with xpath */
+ cprintf(cb, "", namespace);
+ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
+ goto done;
+ if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0)
+ goto done;
+ }
+ if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
+ if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ }
+ /* Use plugin state callbacks */
+ if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, xret)) < 0)
+ goto done;
+ if (ret == 0)
+ goto fail;
+ retval = 1; /* OK */
+ done:
+ clicon_debug(1, "%s %d", __FUNCTION__, retval);
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+
+#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION)
+/*! Help function for parsing restconf query parameter and setting netconf attribute
+ *
+ * If not "unbounded", parse and set a numeric value
+ * @param[in] h Clixon handle
+ * @param[in] name Name of attribute
+ * @param[in] defaultstr Default string which is accepted and sets value to 0
+ * @param[in,out] cbret Output buffer for internal RPC message
+ * @param[out] value Value
+ * @retval -1 Error
+ * @retval 0 Invalid, cbret set
+ * @retval 1 OK
+ */
+static int
+element2value(clicon_handle h,
+ cxobj *xe,
+ char *name,
+ char *defaultstr,
+ cbuf *cbret,
+ uint32_t *value)
+{
+ int retval = -1;
+ char *valstr;
+ int ret;
+ char *reason = NULL;
+ cxobj *x;
+
+ *value = 0;
+ if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL &&
+ (valstr = xml_body(x)) != NULL &&
+ strcmp(valstr, defaultstr) != 0){
+ if ((ret = parse_uint32(valstr, value, &reason)) < 0){
+ clicon_err(OE_XML, errno, "parse_uint32");
+ goto done;
+ }
+ if (ret == 0){
+ if (netconf_bad_element(cbret, "application",
+ name, "Unrecognized value") < 0)
+ goto done;
+ goto fail;
+ }
+ }
+ retval = 1;
+ done:
+ if (reason)
+ free(reason);
+ return retval;
+ fail:
+ retval = 0;
+ goto done;
+}
+#endif
+
+/*! Common get/get-config code for retrieving configuration and state information.
+ *
+ * @param[in] h Clicon handle
+ * @param[in] xe Request:
+ * @param[in] content Get config/state/both
+ * @param[in] db Database name
+ * @param[out] cbret Return xml tree, eg ...,
+ * The set of namespace declarations are those in scope on the
+ * element.
+ */
+ else
+ if (xml_nsctx_node(xfilter, &nsc) < 0)
+ goto done;
+ if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
+ goto done;
+ if (nsc)
+ xml_nsctx_free(nsc);
+ nsc = nsc1;
+ }
+ /* Clixon extensions: depth */
+ if ((attr = xml_find_value(xe, "depth")) != NULL){
+ if ((ret = parse_int32(attr, &depth, &reason)) < 0){
+ clicon_err(OE_XML, errno, "parse_int32");
+ goto done;
+ }
+ if (ret == 0){
+ if (netconf_bad_attribute(cbret, "application",
+ "depth", "Unrecognized value of depth attribute") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+#ifdef CLIXON_PAGINATION
+ /* Check if list pagination */
+ if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL &&
+ (valstr = xml_body(x)) != NULL &&
+ strcmp(valstr,"true")==0)
+ list_pagination = 1;
+ /* Sanity check for list pagination: path must be a list/leaf-list, if it is,
+ * check config/state
+ */
+ if (list_pagination){
+ /* Check if list/leaf-list */
+ if (yang_path_arg(yspec, xpath, &ylist) < 0)
+ goto done;
+ if (ylist == NULL){
+ if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not found") < 0)
+ goto done;
+ goto ok;
+ }
+ if (yang_keyword_get(ylist) != Y_LIST &&
+ yang_keyword_get(ylist) != Y_LEAF_LIST){
+ if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0)
+ goto done;
+ goto ok;
+ }
+ if ((list_config = yang_config_ancestor(ylist)) != 0){ /* config list */
+ if (content == CONTENT_NONCONFIG){
+ if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a config list but content request is nonconfig") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ else { /* state list */
+ if (content == CONTENT_CONFIG){
+ if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a state list but content request is config") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ /* limit */
+ if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0)
+ goto done;
+ /* offset */
+ if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0)
+ goto done;
+ /* direction */
+ if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){
+ direction = xml_body(x);
+ if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){
+ if (netconf_bad_attribute(cbret, "application",
+ "direction", "Unrecognized value of direction attribute") < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ /* sort */
+ if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL)
+ sort = xml_body(x);
+ if (sort) ; /* XXX */
+ /* where */
+ if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL)
+ where = xml_body(x);
+ /* Build a "predicate" cbuf
+ * This solution uses xpath predicates to translate "limit" and "offset" to
+ * relational operators <>.
+ */
+ if ((cbpath = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ /* This uses xpath. Maybe limit should use parameters */
+ if (xpath)
+ cprintf(cbpath, "%s", xpath);
+ else
+ cprintf(cbpath, "/");
+ if (where)
+ cprintf(cbpath, "[%s]", where);
+ if (offset){
+ cprintf(cbpath, "[%u <= position()", offset);
+ if (limit)
+ cprintf(cbpath, " and position() < %u", limit+offset);
+ cprintf(cbpath, "]");
+ }
+ else if (limit)
+ cprintf(cbpath, "[position() < %u]", limit);
+ /* Get total/remaining
+ * XXX: Maybe together with get config / state data
+ */
+ if (list_config){
+ if ((xcache = xmldb_cache_get(h, db)) != NULL){
+ if (xpath_count(xcache, nsc, xpath, &total) < 0)
+ goto done;
+ if (total >= (offset + limit))
+ remaining = total - (offset + limit);
+ }
+ }
+ else{
+ /* XXX remaining of state list??*/
+ }
+ /* Append predicate to original xpath and replace it */
+ if (xpath)
+ free(xpath);
+ if ((xpath = strdup(cbuf_get(cbpath))) == NULL){
+ clicon_err(OE_UNIX, errno, "strdup");
+ goto done;
+ }
+
+ } /* list_pagination */
+#endif /* CLIXON_PAGINATION */
+ /* Read config
+ * XXX This seems unnecessary complex
+ */
+ switch (content){
+ case CONTENT_CONFIG: /* config data only */
+ /* specific xpath */
+ if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) {
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0)
+ goto done;
+ goto ok;
+ }
+ break;
+ case CONTENT_ALL: /* both config and state */
+ case CONTENT_NONCONFIG: /* state data only */
+ if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
+ /* Whole config tree, for validate debug */
+ if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) {
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ else if (content == CONTENT_ALL){
+ /* specific xpath */
+ if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) {
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ /* CONTENT_NONCONFIG */
+ else if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */
+ goto done;
+ break;
+ }/* switch content */
+ /* If not only config,
+ * get state data from plugins as defined by plugin_statedata(), if any
+ */
+ /* Read state XXX can we do this first and create an operational db? */
+ switch (content){
+ case CONTENT_CONFIG: /* config data only */
+ break;
+ case CONTENT_ALL: /* both config and state */
+ case CONTENT_NONCONFIG: /* state data only */
+ if ((ret = client_statedata(h, xpath?xpath:"/", nsc, &xret)) < 0)
+ goto done;
+ if (ret == 0){ /* Error from callback (error in xret) */
+ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ break;
+ }
+
+ if (content != CONTENT_CONFIG &&
+ clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
+ /* Check XML by validating it. return internal error with error cause
+ * Primarily intended for user-supplied state-data.
+ * The whole config tree must be present in case the state data references config data
+ */
+ if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
+ goto done;
+ if (ret > 0 &&
+ (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
+ goto done;
+ if (ret == 0){
+ if (clicon_debug_get())
+ clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
+ if (clixon_netconf_internal_error(xerr,
+ ". Internal error, state callback returned invalid XML",
+ NULL) < 0)
+ goto done;
+ if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ } /* CLICON_VALIDATE_STATE_XML */
+
+ if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
+ /* Keep state data only, remove everything that is config. Note that state data
+ * may be a sub-part in a config tree, we need to traverse to find all
+ */
+ if (xml_non_config_data(xret, NULL) < 0)
+ goto done;
+ if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
+ goto done;
+ if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
+ goto done;
+ }
+ /* Code complex to filter out anything that is outside of xpath
+ * Actually this is a safety catch, should really be done in plugins
+ * and modules_state functions.
+ * But it is problematic, because defaults, at least of config data, is in place
+ * and we need to re-add it.
+ */
+ if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
+ goto done;
+
+ /* If vectors are specified then mark the nodes found and
+ * then filter out everything else,
+ * otherwise return complete tree.
+ */
+ if (xvec != NULL){
+ for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */
+ if (xret==NULL)
+ cprintf(cbret, "");
+ else{
+ if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0)
+ goto done;
+ /* Top level is data, so add 1 to depth if significant */
+ if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0)
+ goto done;
+ }
+ cprintf(cbret, "");
+ ok:
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+#ifdef CLIXON_PAGINATION
+ if (cbpath)
+ cbuf_free(cbpath);
+#endif
+ if (cbmsg)
+ cbuf_free(cbmsg);
+ if (reason)
+ free(reason);
+ if (xerr)
+ xml_free(xerr);
+ if (xpath)
+ free(xpath);
+ if (xvec)
+ free(xvec);
+ if (xvecnacm)
+ free(xvecnacm);
+ if (nsc)
+ xml_nsctx_free(nsc);
+ if (xret)
+ xml_free(xret);
+ return retval;
+}
+
+/*! Retrieve all or part of a specified configuration.
+ *
+ * @param[in] h Clicon handle
+ * @param[in] xe Request:
+ * @param[out] cbret Return xml tree, eg ...,
+ * @param[out] cbret Return xml tree, eg ...,
+ * @param[out] cbret Return xml tree, eg ..., stuctured path tree */
+ if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0)
+ goto done;
+ if (ret == 0){
+ if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ /* get last element of path, eg /a/b/c, get c */
+ if ((cp = PREVQ(clixon_path *, path_tree)) == NULL){
+ if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
+ goto done;
+ goto ok;
+ }
+ /* get yang of last element */
+ if ((y = cp->cp_yang) == NULL){
+ if (netconf_bad_element(cbret, "application", "list-target", "No yang associated with path") < 0)
+ goto done;
+ goto ok;
+ }
+ if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
+ if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
+ goto done;
+ goto ok;
+ }
+ /* Build a "predicate" cbuf
+ * This solution uses xpath predicates to translate "limit" and "offset" to
+ * relational operators <>.
+ */
+ if ((cbpath = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ /* This uses xpath. Maybe limit should use parameters */
+ cprintf(cbpath, "%s", xpath);
+ if (where)
+ cprintf(cbpath, "[%s]", where);
+ if (offset){
+ cprintf(cbpath, "[%u <= position()", offset);
+ if (limit)
+ cprintf(cbpath, " and position() < %u", limit+offset);
+ cprintf(cbpath, "]");
+ }
+ else if (limit)
+ cprintf(cbpath, "[position() < %u]", limit);
+
+ /* Split into CT or CF */
+ if (yang_config_ancestor(y) == 1){ /* CT */
+ if (content == CONTENT_CONFIG || content == CONTENT_ALL){
+ if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) {
+ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
+ goto done;
+ goto ok;
+ }
+ if (ret == 0){
+ if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ /* First get number of hits (ALL entries: note not optimized) */
+ if (xpath_count(xret, nsc, xpath, &total) < 0)
+ goto done;
+ if (total < (offset + limit))
+ remaining = 0;
+ else
+ remaining = total - (offset + limit);
+ }
+ /* There may be CF data in a CT collection */
+ if (content == CONTENT_ALL){
+ if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0)
+ goto done;
+ }
+ }
+ else { /* CF */
+ /* There can be no CT data in a CF collection */
+ if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){
+ if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0)
+ goto done;
+ if (ret == 0){ /* Error from callback (error in xret) */
+ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ }
+ if (0 && clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
+ /* XXX: subset in collection may not have mandatory variables */
+ /* Check XML by validating it. return internal error with error cause
+ * Primarily intended for user-supplied state-data.
+ * The whole config tree must be present in case the state data references config data
+ */
+ if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0)
+ goto done;
+ if (ret > 0 &&
+ (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
+ goto done;
+ if (ret == 0){
+ if (clicon_debug_get())
+ clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
+ if (clixon_netconf_internal_error(xerr,
+ ". Internal error, state callback returned invalid XML",
+ NULL) < 0)
+ goto done;
+ if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
+ goto done;
+ goto ok;
+ }
+ } /* CLICON_VALIDATE_STATE_XML */
+
+ if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */
+ /* Keep state data only, remove everything that is not config. Note that state data
+ * may be a sub-part in a config tree, we need to traverse to find all
+ */
+ if (xml_non_config_data(xret, NULL) < 0)
+ goto done;
+ if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
+ goto done;
+ if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
+ goto done;
+ }
+ /* Code complex to filter out anything that is outside of xpath
+ * Actually this is a safety catch, should really be done in plugins
+ * and modules_state functions.
+ */
+ if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0)
+ goto done;
+
+ /* Pre-NACM access step */
+ xnacm = clicon_nacm_cache(h);
+ if (xnacm != NULL){ /* Do NACM validation */
+ /* NACM datanode/module read validation */
+ if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
+ goto done;
+ }
+ cprintf(cbret, "",
+ NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */
+ if ((ns = yang_find_mynamespace(y)) != NULL) {
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ for (i=0; i0?depth+1:depth) < 0)
+ goto done;
+ }
+ }
+ cprintf(cbret, "");
+ ok:
+ retval = 0;
+ done:
+ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
+ if (datastore)
+ free(datastore);
+ if (path_tree)
+ clixon_path_free(path_tree);
+ if (xtop)
+ xml_free(xtop);
+ if (cbpath)
+ cbuf_free(cbpath);
+ if (cb)
+ cbuf_free(cb);
+ if (reason)
+ free(reason);
+ if (xerr)
+ xml_free(xerr);
+ if (xvec)
+ free(xvec);
+ if (nsc)
+ xml_nsctx_free(nsc);
+ if (xret)
+ xml_free(xret);
+ return retval;
+}
+#endif /* LIST_PAGINATION */
diff --git a/apps/backend/backend_get.h b/apps/backend/backend_get.h
new file mode 100644
index 00000000..ea2ad755
--- /dev/null
+++ b/apps/backend/backend_get.h
@@ -0,0 +1,47 @@
+/*
+ *
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2017-2019 Olof Hagsand
+ Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC (Netgate)
+
+ This file is part of CLIXON.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Alternatively, the contents of this file may be used under the terms of
+ the GNU General Public License Version 3 or later (the "GPL"),
+ in which case the provisions of the GPL are applicable instead
+ of those above. If you wish to allow use of your version of this file only
+ under the terms of the GPL, and not to allow others to
+ use your version of this file under the terms of Apache License version 2,
+ indicate your decision by deleting the provisions above and replace them with
+ the notice and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this file under
+ the terms of any one of the Apache License version 2 or the GPL.
+
+ ***** END LICENSE BLOCK *****
+ */
+
+#ifndef _BACKEND_GET_H_
+#define _BACKEND_GET_H_
+
+/*
+ * Prototypes
+ */
+int from_client_get_config(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg);
+int from_client_get(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg);
+int from_client_get_pageable_list(clicon_handle h, cxobj *xe, cbuf *cbret, void *arg, void *regarg); /* XXX */
+
+#endif /* _BACKEND_GET_H_ */
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index b4b251ae..d4745aab 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -688,6 +688,7 @@ xml_diff(yang_stmt *yspec,
}
/*! Prune everything that does not pass test or have at least a child* does not
+ *
* @param[in] xt XML tree with some node marked
* @param[in] flag Which flag to test for
* @param[in] test 1: test that flag is set, 0: test that flag is not set
@@ -701,7 +702,6 @@ xml_diff(yang_stmt *yspec,
* @note This function seems a little too complex semantics
* @see xml_tree_prune_flagged for a simpler variant
*/
-#if 1
int
xml_tree_prune_flagged_sub(cxobj *xt,
int flag,
@@ -773,97 +773,6 @@ xml_tree_prune_flagged_sub(cxobj *xt,
*upmark = mark;
return retval;
}
-#else
-/* This is optimized in the sense that xml_purge is replaced with xml_child_rm but it leaks memory,
- * in poarticualr attributes and namespace caches
- */
-int
-xml_tree_prune_flagged_sub(cxobj *xt,
- int flag,
- int test,
- int *upmark)
-{
- int retval = -1;
- int submark;
- int mark;
- cxobj *x;
- cxobj *xprev;
- int iskey;
- int anykey=0;
- yang_stmt *yt;
- int i;
-
- mark = 0;
- yt = xml_spec(xt); /* xan be null */
- x = NULL;
- xprev = x = NULL;
- i = 0;
- while ((x = xml_child_each(xt, x, -1)) != NULL) {
- i++;
- if (xml_type(x) != CX_ELMNT){
- xprev = x;
- continue;
- }
- if (xml_flag(x, flag) == test?flag:0){
- /* Pass test */
- mark++;
- xprev = x;
- continue; /* mark and stop here */
- }
- /* If it is key dont remove it yet (see second round) */
- if (yt){
- if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
- goto done;
- if (iskey){
- anykey++;
- xprev = x; /* skip if this is key */
- continue;
- }
- }
- if (xml_tree_prune_flagged_sub(x, flag, test, &submark) < 0)
- goto done;
- /* if xt is list and submark anywhere, then key subs are also marked
- */
- if (submark)
- mark++;
- else{ /* Safe with xml_child_each if last */
- if (xml_child_rm(xt, i-1) < 0)
- goto done;
- i--;
- x = xprev;
- }
- xprev = x;
- }
- /* Second round: if any keys were found, and no marks detected, purge now */
- if (anykey && !mark){
- x = NULL;
- xprev = x = NULL;
- i = 0;
- while ((x = xml_child_each(xt, x, -1)) != NULL) {
- i++;
- if (xml_type(x) != CX_ELMNT){
- xprev = x;
- continue;
- }
- /* If it is key remove it here */
- if (yt){
- if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
- goto done;
- if (xml_child_rm(xt, i-1) < 0)
- goto done;
- i--;
- x = xprev;
- }
- xprev = x;
- }
- }
- retval = 0;
- done:
- if (upmark)
- *upmark = mark;
- return retval;
-}
-#endif
/*! Prune everything that passes test
* @param[in] xt XML tree with some node marked
diff --git a/lib/src/clixon_xpath_yang.c b/lib/src/clixon_xpath_yang.c
index f2638fb8..3355c445 100644
--- a/lib/src/clixon_xpath_yang.c
+++ b/lib/src/clixon_xpath_yang.c
@@ -150,6 +150,7 @@ xp_yang_eval_step(xp_yang_ctx *xy0,
char *prefix;
yang_stmt *ys;
xp_yang_ctx *xy = NULL;
+ yang_stmt *ys1 = NULL;
/* Create new xy */
if ((xy = xy_dup(xy0)) == NULL)
@@ -164,9 +165,12 @@ xp_yang_eval_step(xp_yang_ctx *xy0,
switch (nodetest->xs_type){
case XP_NODE:
if ((prefix = nodetest->xs_s0) != NULL){
- if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */
- yang_stmt *ys1 = NULL;
- /* XXX: Kludge with prefixes */
+ /* XXX: Kludge with prefixes */
+ if (yang_keyword_get(ys) == Y_SPEC){ /* This means top */
+ if ((ys1 = yang_find_module_by_prefix_yspec(ys, prefix)) != NULL)
+ ys = ys1;
+ }
+ else if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */
if ((ys1 = yang_find_module_by_prefix(ys, prefix)) == NULL)
ys1 = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix);
if (ys1 != NULL)
@@ -323,9 +327,15 @@ xp_yang_eval(xp_yang_ctx *xy,
}
}
break;
+ case XP_PRIME_STR:
+ if ((*xyr = xy_dup(xy)) == NULL)
+ goto done;
+ goto ok;
+ break;
case XP_ABSPATH:
/* Set context node to top node, and nodeset to that node only */
- xy->xy_node = ys_module(xy->xy_node);
+ if (yang_keyword_get(xy->xy_node) != Y_SPEC)
+ xy->xy_node = ys_module(xy->xy_node);
break;
case XP_PRED:
if (xp_yang_eval_predicate(xy, xptree, xyr) < 0)
@@ -419,15 +429,24 @@ xp_yang_eval(xp_yang_ctx *xy,
* Leafrefs have a path arguments that are used both for finding referred XML node instances as well
* as finding a referred YANG node for typechecks.
* Such a path-arg is defined as:
- * The syntax for a path argument is a subset of the XPath abbreviated
+ * The syntax for a path argument is a subset of the XPath abbreviated
* syntax. Predicates are used only for constraining the values for the
* key nodes for list entries. Each predicate consists of exactly one
* equality test per key, and multiple adjacent predicates MAY be
* present if a list has multiple keys.
- * @param[in] ys YANG referring leaf node
- * @param[in] path_arg Leafref path-arg
+ * @param[in] ys YANG referring node
+ * @param[in] path_arg path-arg
* @param[out] yref YANG referred node
* @note this function uses XPATH parser, which is (much too) general
+ * @code
+ * yang_stmt *ys; // source / referring node
+ * yang_stmt *yref = NULL; // target / referred node
+ * char *path_arg="../config/name";
+ *
+ * if (yang_path_arg(ys, path_arg, &yref) < 0)
+ * err;
+ * @endcode
+
* @see rfc7950 Sec 9.9.2
* @see rfc7950 Sec 14 (leafref path)
*/
diff --git a/test/test_augment_state.sh b/test/test_augment_state.sh
index 29e52c4a..eb8576a1 100755
--- a/test/test_augment_state.sh
+++ b/test/test_augment_state.sh
@@ -189,6 +189,7 @@ cat < $fstate
EOF
+# Note Expect gbds(default) + gbos(optional), the latter given by file above
EXPSTATE=$(cat <gbdsgbosgadsgaos
EOF
diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh
index 990e4878..50325d2d 100755
--- a/test/test_leafref_state.sh
+++ b/test/test_leafref_state.sh
@@ -10,7 +10,10 @@
# with different paths.
# Using the -sS state capability of the main example, that is why CLICON_BACKEND_DIR is
# /usr/local/lib/$APPNAME/backend so that the main backend plugins is included.
-# These tests require VALIDATE_STATE_XML to be set
+# Note: Three runs:
+# 1. with state data validation and with require-instance (Invalid)
+# 2. with state data validation and without require-instance (OK)
+# 3. without state data validation and with require-instance (Wrong state data no detected)
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@@ -20,6 +23,7 @@ APPNAME=example
cfg=$dir/conf_yang.xml
fstate=$dir/state.xml
fyang=$dir/leafref.yang
+fyangno=$dir/leafrefno.yang # No require-instance
cat < $cfg
@@ -66,6 +70,32 @@ module leafref{
}
EOF
+# No require-instance in leafref
+cat < $fyangno
+module leafref{
+ yang-version 1.1;
+ namespace "urn:example:example";
+ prefix ex;
+ list sender-config{
+ description "Main config of senders";
+ key name;
+ leaf name{
+ type string;
+ }
+ }
+ list sender-state{
+ description "State referencing configured senders";
+ config false;
+ key ref;
+ leaf ref{
+ type leafref {
+ path "/ex:sender-config/ex:name";
+ }
+ }
+ }
+}
+EOF
+
# This is state data written to file that backend reads from (on request)
cat < $fstate
@@ -73,6 +103,7 @@ cat < $fstate
EOF
+# First run: With validation of state callbacks
new "test params: -f $cfg -- -sS $fstate"
if [ $BE -ne 0 ]; then
@@ -167,7 +198,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
-# Leafref wrong
+# Leafref wrong internal: state references x but config contains only y
new "netconf get / config+state should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failedxerrorLeafref validation failed: No leaf x matching path /ex:sender-config/ex:name in leafref.yang:[0-9]*. Internal error, state callback returned invalid XML]]>]]>$"
@@ -188,6 +219,117 @@ if [ $BE -ne 0 ]; then
stop_backend -f $cfg
fi
+# Second run: Validation and no require-instance
+new "Second run: -f $cfg -o CLICON_YANG_MAIN_FILE=$fyangno -- -sS $fstate"
+
+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 -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate"
+ start_backend -s init -f $cfg -o CLICON_YANG_MAIN_FILE=$fyangno -- -sS $fstate
+fi
+
+new "wait backend"
+wait_backend
+
+# Add y
+XML=$(cat <
+ y
+
+EOF
+)
+
+# Reference (non-existing) x
+cat < $fstate
+
+ [x]
+
+EOF
+
+new "leafref config sender x"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$"
+
+new "netconf commit"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
+
+# Leafref wrong internal: state references x but config contains only y
+new "netconf get / config+state wrong state xml but no validation"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "y[x]]]>]]>$"
+
+new "netconf get / state-only wrong state xml but no validation"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^[x]]]>]]>$"
+
+if [ $BE -ne 0 ]; then
+ new "Kill backend"
+ # Check if premature kill
+ pid=$(pgrep -u root -f clixon_backend)
+ if [ -z "$pid" ]; then
+ err "backend already dead"
+ fi
+ # kill backend
+ stop_backend -f $cfg
+fi
+
+# Third run: No validation of state callbacks
+new "Third run: -f $cfg -o CLICON_VALIDATE_STATE_XML=true -- -sS $fstate"
+
+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 -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate"
+ start_backend -s init -f $cfg -o CLICON_VALIDATE_STATE_XML=false -- -sS $fstate
+fi
+
+new "wait backend"
+wait_backend
+
+# Add y
+XML=$(cat <
+ y
+
+EOF
+)
+
+# Reference (non-existing) x
+cat < $fstate
+
+ [x]
+
+EOF
+
+new "leafref config sender x"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$XML]]>]]>" "^]]>]]>$"
+
+new "netconf commit"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
+
+# Leafref wrong internal: state references x but config contains only y
+new "netconf get / config+state wrong state xml but no validation"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "y[x]]]>]]>$"
+
+new "netconf get / state-only wrong state xml but no validation"
+expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^[x]]]>]]>$"
+
+if [ $BE -ne 0 ]; then
+ new "Kill backend"
+ # Check if premature kill
+ pid=$(pgrep -u root -f clixon_backend)
+ if [ -z "$pid" ]; then
+ err "backend already dead"
+ fi
+ # kill backend
+ stop_backend -f $cfg
+fi
+
+
rm -rf $dir
new "endtest"
diff --git a/test/test_pagination.sh b/test/test_pagination.sh
index 81bcf064..66ac0ecf 100755
--- a/test/test_pagination.sh
+++ b/test/test_pagination.sh
@@ -20,6 +20,9 @@ fstate=$dir/mystate.xml
# Define default restconfig config: RESTCONFIG
RESTCONFIG=$(restconf_config none false)
+# Validate internal state xml
+: ${validatexml:=false}
+
cat < $cfg
$cfg
@@ -38,7 +41,7 @@ cat < $cfg
$APPNAME
/usr/local/lib/$APPNAME/cli
/usr/local/lib/$APPNAME/clispec
- false
+ $validatexml
$RESTCONFIG
EOF
@@ -269,10 +272,10 @@ function testlimit()
# "clixon get"
new "clixon limit=$limit NETCONF get-config"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$limit]]>]]>" "alicepublic17]]>]]>$"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limit]]>]]>" "alicepublic17]]>]]>$"
new "clixon limit=$limit NETCONF get"
-# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$limit]]>]]>" "alicepublic17]]>]]>$"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limit]]>]]>" "alicepublic17]]>]]>$"
# "old: ietf"
new "ietf limit=$limit NETCONF"
@@ -346,6 +349,7 @@ if [ $BE -ne 0 ]; then
fi
unset RESTCONFIG
+unset validatexml
rm -rf $dir
diff --git a/test/test_upgrade_failsafe.sh b/test/test_upgrade_failsafe.sh
index 2a664627..bc6faf91 100755
--- a/test/test_upgrade_failsafe.sh
+++ b/test/test_upgrade_failsafe.sh
@@ -361,7 +361,7 @@ if [ $valgrindtest -ne 2 ]; then
new "8. Load non-compat startup. Syntax fail, enter failsafe, startup invalid"
(cd $dir; rm -f tmp_db candidate_db running_db startup_db) # remove databases
(cd $dir; cp compat-err.xml startup_db)
-runtest true startup 'always work' 'applicationoperation-failederrorread registry'
+runtest true startup 'always work' 'applicationoperation-failederrorGet startup datastore: xml_parse: line 14: syntax error: at or before: <'
fi # valgrindtest
rm -rf $dir
diff --git a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang
index d64e5251..8730da6f 100644
--- a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang
+++ b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang
@@ -81,6 +81,20 @@ module clixon-netconf-list-pagination {
elements.";
}
grouping pageing-parameters {
+ leaf list-pagination {
+ type boolean;
+ default false;
+ description
+ "NETCONF get / get-config needs some way to know that this is a pagination
+ request, in which case the target is a list/leaf-list and the elements below
+ (limit/offset/...) are valid.
+ RESTCONF list pagination has a specific media-type for this purpose.
+ This is an experimental proposal to make this property explicit.
+ Possibly there is a better way (annotation?) to signal that this is in fact a
+ list pagination request.
+ It is also possible to determine this using heurestics (ie a 'limit' property exixts),
+ but it seems not 100% deterministic.";
+ }
leaf limit {
type union {
type uint32;