clixon/apps/backend/backend_get.c
Olof hagsand c9843b34a6 - Rewrite of netconf get/get-config code
- Unified get and get-config code to single function get_common
  - Integrated list-pagination code
  - Moved get code to new files backend_get.[ch]
2021-09-21 11:24:53 +02:00

1108 lines
34 KiB
C

/*
*
***** 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <inttypes.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#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, "<capabilities>");
cprintf(cb, "<capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability>");
cprintf(cb, "<capability>urn:ietf:params:restconf:capability:depth:1.0</capability>");
cprintf(cb, "</capabilities>");
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, "<netconf xmlns=\"%s\"/>", 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, "<restconf-state xmlns=\"%s\"/>", 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: <rpc><xn></rpc>
* @param[in] content Get config/state/both
* @param[in] db Database name
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @retval 0 OK
* @retval -1 Error
* @see from_client_get
* @see from_client_get_config
*/
static int
get_common(clicon_handle h,
cxobj *xe,
netconf_content content,
char *db,
cbuf *cbret
)
{
int retval = -1;
cxobj *xfilter;
char *xpath = NULL;
cxobj *xret = NULL;
cxobj **xvec = NULL;
size_t xlen;
cxobj **xvecnacm = NULL;
size_t xlennacm;
cxobj *xnacm = NULL;
char *username;
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
char *attr;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
yang_stmt *yspec;
int i;
cxobj *xerr = NULL;
int ret;
char *reason = NULL;
cbuf *cbmsg = NULL; /* For error msg */
#ifdef CLIXON_PAGINATION
uint32_t limit = 0;
uint32_t offset = 0;
uint32_t total = 0;
uint32_t remaining = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
cbuf *cbpath = NULL;
cxobj *x;
int list_pagination = 0;
int list_config;
char *valstr;
yang_stmt *ylist;
cxobj *xcache = NULL;
#endif /* CLIXON_PAGINATION */
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
if ((xfilter = xml_find(xe, "filter")) != NULL){
char *xpath0;
cvec *nsc1 = NULL;
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
xpath0 = "/";
/* Create namespace context for xpath from <filter>
* The set of namespace declarations are those in scope on the
* <filter> 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<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (!xml_flag(xret, XML_FLAG_MARK))
if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
/* Add default global values */
if (xml_global_defaults(h, xret, nsc, xpath, yspec, 0) < 0)
goto done;
/* Add default recursive values */
if (xml_default_recurse(xret, 0) < 0)
goto done;
#ifdef CLIXON_PAGINATION
/* Add remaining attribute */
if (list_pagination && remaining && xlen){
cxobj *xa;
cbuf *cba = NULL;
/* Add remaining attribute */
x = xvec[0];
if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
goto done;
if ((cba = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cba, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cba)) < 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;
if (cba)
cbuf_free(cba);
}
#endif /* CLIXON_PAGINATION */
/* Pre-NACM access step */
xnacm = clicon_nacm_cache(h);
if (xnacm != NULL){ /* Do NACM validation */
if (xpath_vec(xret, nsc, "%s", &xvecnacm, &xlennacm, xpath?xpath:"/") < 0)
goto done;
/* NACM datanode/module read validation */
if (nacm_datanode_read(h, xret, xvecnacm, xlennacm, username, xnacm) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\">", NETCONF_BASE_NAMESPACE); /* OK */
if (xret==NULL)
cprintf(cbret, "<data/>");
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, "</rpc-reply>");
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: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
* @see from_client_get
*/
int
from_client_get_config(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
char *db;
if ((db = netconf_db_find(xe, "source")) == NULL){
clicon_err(OE_XML, 0, "db not found");
goto done;
}
retval = get_common(h, xe, CONTENT_CONFIG, db, cbret);
done:
return retval;
}
/*! Retrieve running configuration and device state information.
*
* @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*
* @see from_client_get_config
*/
int
from_client_get(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
netconf_content content = CONTENT_ALL;
char *attr;
/* Clixon extensions: content */
if ((attr = xml_find_value(xe, "content")) != NULL)
content = netconf_content_str2int(attr);
return get_common(h, xe, content, "running", cbret);
}
#ifdef LIST_PAGINATION
/*! Retrieve collection configuration and device state information
*
* @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*
* @see from_client_get
*/
int
from_client_get_pageable_list(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *x;
char *xpath = NULL;
cxobj *xret = NULL;
cxobj **xvec = NULL;
size_t xlen;
cxobj *xnacm = NULL;
char *username;
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
char *attr;
netconf_content content = CONTENT_ALL;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
yang_stmt *yspec;
int i;
cxobj *xerr = NULL;
int ret;
cbuf *cb = NULL;
uint32_t limit = 0;
uint32_t offset = 0;
uint32_t total = 0;
uint32_t remaining = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
char *datastore = NULL;
char *reason = NULL;
cbuf *cbpath = NULL;
cxobj *xtop = NULL;
yang_stmt *y;
char *ns;
clixon_path *path_tree = NULL;
clixon_path *cp;
cxobj *xa; /* attribute */
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
/* 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;
}
}
/* 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);
/* datastore */
if (ret && (x = xml_find_type(xe, NULL, "datastore", CX_ELMNT)) != NULL){
if (nodeid_split(xml_body(x), NULL, &datastore) < 0)
goto done;
}
if (ret == 0)
goto ok;
/* list-target, create (xml and) yang to check if is state (CF) or config (CT)
* is mandatory
*/
if (ret && (x = xml_find_type(xe, NULL, "list-target", CX_ELMNT)) != NULL){
xpath = xml_body(x);
if (xml_nsctx_node(x, &nsc) < 0)
goto done;
}
if (xpath == NULL){
clicon_err(OE_NETCONF, 0, "Missing list-target/xpath, is mandatory");
goto done;
}
if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL)
goto done;
/* Parse xpath -> 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, "<rpc-reply xmlns=\"%s\"><pageable-list xmlns=\"%s\">",
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; i<xlen; i++){
x = xvec[i];
/* Add namespace */
if (xmlns_set(x, NULL, ns) < 0)
goto done;
if (i == 0 && remaining != total){
/* Add remaining annotation to first element
* If no elements were removed, this annotation MUST NOT appear
*/
if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
goto done;
cbuf_reset(cb);
cprintf(cb, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
if (xml_prefix_set(xa, "ycoll") < 0)
goto done;
if (xmlns_set(x, "ycoll", "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination") < 0)
goto done;
}
/* Top level is data, so add 1 to depth if significant */
if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0)
goto done;
}
}
cprintf(cbret, "</pageable-list></rpc-reply>");
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 */