clixon/lib/src/clixon_xml_map.c
2019-12-31 13:29:25 +01:00

3612 lines
101 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
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 *****
*
* XML code
*
* file
* +---------+ db2xml_key-> save_db_to_xml->
* +-------------> | database| <------------+------------------+
* | +---------+ <-xml2db | <-load_xml_to_db |
* | | |
* | | |
* v v v
* +---------+ <-xml2cvec_key +-----------+ +---------+
* | cvec | <---------------------> | xml cxobj |<--------->| xmlfile |
* +---------+ cvec2xml-> +-----------+ +---------+
* cvec2xml_1(yang)-> xml2json->|
* xml2txt-> |
* xml2cli-> v
* +---------+
* | file |
* +---------+
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_sort.h"
#include "clixon_yang_internal.h" /* internal */
#include "clixon_yang_type.h"
#include "clixon_xml_map.h"
/*! Is attribute and is either of form xmlns="", or xmlns:x="" */
int
isxmlns(cxobj *x)
{
char *prefix = NULL;
if (xml_type(x) != CX_ATTR)
return 0;
if (strcmp(xml_name(x), "xmlns")==0)
return 1;
if ((prefix = xml_prefix(x)) != NULL
&& strcmp(xml_prefix(x), "xmlns")==0)
return 1;
return 0;
}
/*! x is element and has eactly one child which in turn has none
* @see child_type in clixon_json.c
*/
static int
tleaf(cxobj *x)
{
cxobj *xc;
if (xml_type(x) != CX_ELMNT)
return 0;
if (xml_child_nr_notype(x, CX_ATTR) != 1)
return 0;
/* From here exactly one noattr child, get it */
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL)
if (xml_type(xc) != CX_ATTR)
break;
if (xc == NULL)
return -1; /* n/a */
return (xml_child_nr_notype(xc, CX_ATTR) == 0);
}
/*! Translate XML -> TEXT
* @param[in] level print 4 spaces per level in front of each line
* XXX rewrite using YANG and remove encrypted password KLUDGE
*/
int
xml2txt(FILE *f,
cxobj *x,
int level)
{
cxobj *xc = NULL;
int children=0;
int retval = -1;
xc = NULL; /* count children (elements and bodies, not attributes) */
while ((xc = xml_child_each(x, xc, -1)) != NULL)
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY)
children++;
if (!children){ /* If no children print line */
switch (xml_type(x)){
case CX_BODY:
fprintf(f, "%s;\n", xml_value(x));
break;
case CX_ELMNT:
fprintf(f, "%*s;\n", 4*level, xml_name(x));
break;
default:
break;
}
goto ok;
}
fprintf(f, "%*s", 4*level, "");
fprintf(f, "%s ", xml_name(x));
if (!tleaf(x))
fprintf(f, "{\n");
xc = NULL;
while ((xc = xml_child_each(x, xc, -1)) != NULL){
if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY)
if (xml2txt(f, xc, level+1) < 0)
break;
}
if (!tleaf(x))
fprintf(f, "%*s}\n", 4*level, "");
ok:
retval = 0;
// done:
return retval;
}
/*! Translate from XML to CLI commands
* Howto: join strings and pass them down.
* Identify unique/index keywords for correct set syntax.
* Args:
* @param[in] f Where to print cli commands
* @param[in] x XML Parse-tree (to translate)
* @param[in] prepend0 Print this text in front of all commands.
* @param[in] gt option to steer cli syntax
*/
int
xml2cli(FILE *f,
cxobj *x,
char *prepend0,
enum genmodel_type gt)
{
int retval = -1;
cxobj *xe = NULL;
cbuf *cbpre = NULL;
yang_stmt *ys;
int match;
char *body;
if (xml_type(x)==CX_ATTR)
goto ok;
if ((ys = xml_spec(x)) == NULL)
goto ok;
if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){
if (prepend0)
fprintf(f, "%s", prepend0);
if (gt == GT_ALL || gt == GT_VARS)
fprintf(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){
if (index(body, ' '))
fprintf(f, "\"%s\"", body);
else
fprintf(f, "%s", body);
}
fprintf(f, "\n");
goto ok;
}
/* Create prepend variable string */
if ((cbpre = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (prepend0)
cprintf(cbpre, "%s", prepend0);
cprintf(cbpre, "%s ", xml_name(x));
if (yang_keyword_get(ys) == Y_LIST){
/* If list then first loop through keys */
xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){
if ((match = yang_key_match(ys, xml_name(xe))) < 0)
goto done;
if (!match)
continue;
if (gt == GT_ALL)
cprintf(cbpre, "%s ", xml_name(xe));
cprintf(cbpre, "%s ", xml_body(xe));
}
}
/* Then loop through all other (non-keys) */
xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){
if (yang_keyword_get(ys) == Y_LIST){
if ((match = yang_key_match(ys, xml_name(xe))) < 0)
goto done;
if (match){
fprintf(f, "%s\n", cbuf_get(cbpre));
continue; /* Not key itself */
}
}
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
goto done;
}
ok:
retval = 0;
done:
if (cbpre)
cbuf_free(cbpre);
return retval;
}
/*! Validate xml node of type leafref, ensure the value is one of that path's reference
* @param[in] xt XML leaf node of type leafref
* @param[in] ytype Yang type statement belonging to the XML node
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* From rfc7950 Sec 9.9.2
* The "path" XPath expression is evaluated in the following context,
* in addition to the definition in Section 6.4.1:
* o If the "path" statement is defined within a typedef, the context
* node is the leaf or leaf-list node in the data tree that
* references the typedef.
* o Otherwise, the context node is the node in the data tree for which
* the "path" statement is defined.
*/
static int
validate_leafref(cxobj *xt,
yang_stmt *ytype,
cxobj **xret)
{
int retval = -1;
yang_stmt *ypath;
cxobj **xvec = NULL;
cxobj *x;
int i;
size_t xlen = 0;
char *leafrefbody;
char *leafbody;
cvec *nsc = NULL;
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0)
goto done;
goto fail;
}
/* XXX see comment above regarding typeref or not */
if (xml_nsctx_yang(ytype, &nsc) < 0)
goto done;
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, yang_argument_get(ypath)) < 0)
goto done;
for (i = 0; i < xlen; i++) {
x = xvec[i];
if ((leafbody = xml_body(x)) == NULL)
continue;
if (strcmp(leafbody, leafrefbody) == 0)
break;
}
if (i==xlen){
if (netconf_bad_element_xml(xret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0)
goto done;
goto fail;
}
ok:
retval = 1;
done:
if (nsc)
xml_nsctx_free(nsc);
if (xvec)
free(xvec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate xml node of type identityref, ensure value is a defined identity
* Check if a given node has value derived from base identity. This is
* a run-time check necessary when validating eg netconf.
* Valid values for an identityref are any identities derived from all
* the identityref's base identities.
* Example:
* b0 --> b1 --> b2 (b1 & b2 are derived)
* identityref b2
* base b0;
* This function does: derived_from(b2, b0);
* @param[in] xt XML leaf node of type identityref
* @param[in] ys Yang spec of leaf
* @param[in] ytype Yang type field of type identityref
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* @see ys_populate_identity where the derived types are set
* @see yang_augment_node
* @see RFC7950 Sec 9.10.2:
*/
static int
validate_identityref(cxobj *xt,
yang_stmt *ys,
yang_stmt *ytype,
cxobj **xret)
{
int retval = -1;
char *node = NULL;
char *idref = NULL;
yang_stmt *ybaseref; /* This is the type's base reference */
yang_stmt *ybaseid;
char *prefix = NULL;
char *id = NULL;
cbuf *cberr = NULL;
cbuf *cb = NULL;
cvec *idrefvec; /* Derived identityref list: (module:id)**/
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Get idref value. Then see if this value is derived from ytype.
*/
if ((node = xml_body(xt)) == NULL){ /* It may not be empty */
if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0)
goto done;
goto fail;
}
if (nodeid_split(node, &prefix, &id) < 0)
goto done;
/* This is the type's base reference */
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0)
goto done;
goto fail;
}
/* This is the actual base identity */
if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){
if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0)
goto done;
goto fail;
}
#if 0 /* Assume proper namespace, otherwise we assume module prefixes,
* see IDENTITYREF_KLUDGE
*/
{
char *namespace;
yang_stmt *ymod;
yang_stmt *yspec;
/* Create an idref as <bbmodule>:<id> which is the format of the derived
* identityref list associated with the base identities.
*/
/* Get namespace (of idref) from xml */
if (xml2ns(xt, prefix, &namespace) < 0)
goto done;
yspec = ys_spec(ys);
/* Get module of that namespace */
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){
clicon_err(OE_YANG, ENOENT, "No module found");
goto done;
}
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
}
#else
{
yang_stmt *ymod;
/* idref from prefix:id to module:id */
if (prefix == NULL)
ymod = ys_module(ys);
else{ /* from prefix to name */
#if 1 /* IDENTITYREF_KLUDGE */
ymod = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix);
#endif
}
if (ymod == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s",
node, yang_argument_get(ybaseid));
if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
}
#endif
idref = cbuf_get(cb);
/* Here check if node is in the derived node list of the base identity
* The derived node list is a cvec computed XXX
*/
idrefvec = yang_cvec_get(ybaseid);
if (cvec_find(idrefvec, idref) == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s",
node, yang_argument_get(ybaseid));
if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
if (cb)
cbuf_free(cb);
if (id)
free(id);
if (prefix)
free(prefix);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given an XML node, return root node
* A root node is an ancestor xr of x with one or both of the following properties
* - its XML parent is NULL parent,
* - its associated yang specification's parent is a yang module.
* @param[in] x XML node
* @param[out] xr XML root
*/
int
xml_yang_root(cxobj *x,
cxobj **xr)
{
int retval = -1;
cxobj *xp;
yang_stmt *y;
yang_stmt *yp;
while ((xp = xml_parent(x)) != NULL){
if ((y = xml_spec(x)) != NULL &&
(yp = yang_parent_get(y)) != NULL)
/* Actually, maybe only the Y_MODULE clause is relevant */
if (yp==NULL ||
yang_keyword_get(yp) == Y_MODULE ||
yang_keyword_get(yp) == Y_SUBMODULE)
break; /* x is the root */
x = xp;
}
*xr = x;
retval = 0;
return retval;
}
/*! Validate an RPC node
* @param[in] h Clicon handle
* @param[in] xrpc XML node to be validated
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* rfc7950
* 7.14.2
* If a leaf in the input tree has a "mandatory" statement with the
* value "true", the leaf MUST be present in an RPC invocation.
*
* If a leaf in the input tree has a default value, the server MUST use
* this value in the same cases as those described in Section 7.6.1. In
* these cases, the server MUST operationally behave as if the leaf was
* present in the RPC invocation with the default value as its value.
*
* If a leaf-list in the input tree has one or more default values, the
* server MUST use these values in the same cases as those described in
* Section 7.7.2. In these cases, the server MUST operationally behave
* as if the leaf-list was present in the RPC invocation with the
* default values as its values.
*
* Since the input tree is not part of any datastore, all "config"
* statements for nodes in the input tree are ignored.
*
* If any node has a "when" statement that would evaluate to "false",
* then this node MUST NOT be present in the input tree.
*
* 7.14.4
* Input parameters are encoded as child XML elements to the rpc node's
* XML element, in the same order as they are defined within the "input"
* statement.
*
* If the RPC operation invocation succeeded and no output parameters
* are returned, the <rpc-reply> contains a single <ok/> element defined
* in [RFC6241]. If output parameters are returned, they are encoded as
* child elements to the <rpc-reply> element defined in [RFC6241], in
* the same order as they are defined within the "output" statement.
* @see xml_yang_validate_all
* @note Should need a variant accepting cxobj **xret
*/
int
xml_yang_validate_rpc(clicon_handle h,
cxobj *xrpc,
cxobj **xret)
{
int retval = -1;
yang_stmt *yn=NULL; /* rpc name */
cxobj *xn; /* rpc name */
if (strcmp(xml_name(xrpc), "rpc")){
clicon_err(OE_XML, EINVAL, "Expected RPC");
goto done;
}
xn = NULL;
/* xn is name of rpc, ie <rcp><xn/></rpc> */
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
if ((yn = xml_spec(xn)) == NULL){
if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0)
goto done;
goto fail;
}
if ((retval = xml_yang_validate_all(h, xn, xret)) < 1)
goto done; /* error or validation fail */
if ((retval = xml_yang_validate_add(h, xn, xret)) < 1)
goto done; /* error or validation fail */
if (xml_apply0(xn, CX_ELMNT, xml_default, h) < 0)
goto done;
}
// ok: /* pass validation */
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Check if an xml node is a part of a choice and have >1 siblings
* @param[in] xt XML node to be validated
* @param[in] yt xt:s yang statement
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* Check if xt is part of valid choice
*/
static int
check_choice(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
yang_stmt *y;
yang_stmt *ytp; /* yt:s parent */
yang_stmt *ytcase = NULL; /* yt:s parent case if any */
yang_stmt *ytchoice = NULL;
yang_stmt *yp;
cxobj *x;
cxobj *xp;
if ((ytp = yang_parent_get(yt)) == NULL)
goto ok;
/* Return OK if xt is not choice */
switch (yang_keyword_get(ytp)){
case Y_CASE:
ytcase = ytp;
ytchoice = yang_parent_get(ytp);
break;
case Y_CHOICE:
ytchoice = ytp;
break;
default:
goto ok; /* Not choice */
break;
}
if ((xp = xml_parent(xt)) == NULL)
goto ok;
x = NULL; /* Find a child with same yang spec */
while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
if (x == xt)
continue;
y = xml_spec(x);
if (y == yt) /* eg same list */
continue;
yp = yang_parent_get(y);
switch (yang_keyword_get(yp)){
case Y_CASE:
if (yang_parent_get(yp) != ytchoice) /* Not same choice (not releveant) */
continue;
if (yp == ytcase) /* same choice but different case */
continue;
break;
case Y_CHOICE:
if (yp != ytcase) /* Not same choice (not relevant) */
continue;
break;
default:
continue; /* not choice */
break;
}
if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0)
goto done;
goto fail;
} /* while */
ok:
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Check if an xml node lacks mandatory children
* @param[in] xt XML node to be validated
* @param[in] yt xt:s yang statement
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
*/
static int
check_mandatory(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
int i;
cxobj *x;
yang_stmt *y;
yang_stmt *yc;
yang_stmt *yp;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
for (i=0; i<yt->ys_len; i++){
yc = yt->ys_stmt[i];
/* Check if a list does not have mandatory key leafs */
if (yt->ys_keyword == Y_LIST &&
yc->ys_keyword == Y_KEY &&
yang_config(yt)){
cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){
if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0)
goto done;
goto fail;
}
}
}
if (!yang_mandatory(yc))
continue;
switch (yc->ys_keyword){
case Y_CONTAINER:
case Y_ANYDATA:
case Y_ANYXML:
case Y_LEAF:
if (yang_config(yc)==0)
break;
/* Find a child with the mandatory yang */
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL
&& y==yc)
break; /* got it */
}
if (x == NULL){
if (netconf_missing_element_xml(xret, "application", yc->ys_argument, "Mandatory variable") < 0)
goto done;
goto fail;
}
break;
case Y_CHOICE: /* More complex because of choice/case structure */
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL &&
(yp = yang_choice(y)) != NULL &&
yp == yc){
break; /* leave loop with x set */
}
}
if (x == NULL){
/* @see RFC7950: 15.6 Error Message for Data That Violates
* a Mandatory "choice" Statement */
if (netconf_data_missing_xml(xret, yc->ys_argument, NULL) < 0)
goto done;
goto fail;
}
break;
default:
break;
} /* switch */
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*!
* @param[out] xret Error XML tree. Free with xml_free after use
*/
static int
check_list_key(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
int i;
yang_stmt *yc;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
for (i=0; i<yt->ys_len; i++){
yc = yt->ys_stmt[i];
/* Check if a list does not have mandatory key leafs */
if (yt->ys_keyword == Y_LIST &&
yc->ys_keyword == Y_KEY &&
yang_config(yt)){
cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){
if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0)
goto done;
goto fail;
}
}
}
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! New element last in list, check if already exists if sp return -1
* @param[in] vec Vector of existing entries (new is last)
* @param[in] i1 The new entry is placed at vec[i1]
* @param[in] vlen Lenght of entry
* @retval 0 OK, entry is unique
* @retval -1 Duplicate detected
* @note This is currently linear complexity. It could be improved by inserting new element sorted and binary search.
*/
static int
check_insert_duplicate(char **vec,
int i1,
int vlen)
{
int i;
int v;
char *b;
for (i=0; i<i1; i++){
for (v=0; v<vlen; v++){
b = vec[i*vlen+v];
if (b == NULL || strcmp(b, vec[i1*vlen+v]))
break;
}
if (v==vlen) /* duplicate */
break;
}
return i==i1?0:-1;
}
/*! Given a list with unique constraint, detect duplicates
* @param[in] x The first element in the list (on return the last)
* @param[in] xt The parent of x
* @param[in] y Its yang spec (Y_LIST)
* @param[in] yu A yang unique spec (Y_UNIQUE)
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @note It would be possible to cache the vector built below
*/
static int
check_unique_list(cxobj *x,
cxobj *xt,
yang_stmt *y,
yang_stmt *yu,
cxobj **xret)
{
int retval = -1;
cvec *cvk; /* unique vector */
cg_var *cvi; /* unique node name */
cxobj *xi;
char **vec = NULL; /* 2xmatrix */
int vlen;
int i;
int v;
char *bi;
cvk = yang_cvec_get(yu);
vlen = cvec_len(cvk); /* nr of unique elements to check */
if ((vec = calloc(vlen*xml_child_nr(xt), sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
i = 0; /* x element index */
do {
cvi = NULL;
v = 0; /* index in each tuple */
while ((cvi = cvec_each(cvk, cvi)) != NULL){
/* RFC7950: Sec 7.8.3.1: entries that do not have value for all
* referenced leafs are not taken into account */
if ((xi = xml_find(x, cv_string_get(cvi))) ==NULL)
break;
if ((bi = xml_body(xi)) == NULL)
break;
vec[i*vlen + v++] = bi;
}
if (cvi==NULL){
/* Last element (i) is newly inserted, see if it is already there */
if (check_insert_duplicate(vec, i, vlen) < 0){
if (netconf_data_not_unique_xml(xret, x, cvk) < 0)
goto done;
goto fail;
}
}
x = xml_child_each(xt, x, CX_ELMNT);
i++;
} while (x && y == xml_spec(x)); /* stop if list ends, others may follow */
/* It would be possible to cache vec here as an optimization */
retval = 1;
done:
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given a list, check if any min/max-elemants constraints apply
* @param[in] x One x (the last) of a specific lis
* @param[in] y Yang spec of x
* @param[in] nr Number of elements (like x) in thlist
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @see RFC7950 7.7.5
*/
static int
check_min_max(cxobj *x,
yang_stmt *y,
int nr,
cxobj **xret)
{
int retval = -1;
yang_stmt *ymin; /* yang min */
yang_stmt *ymax; /* yang max */
cg_var *cv;
if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ymin);
if (nr < cv_uint32_get(cv)){
if (netconf_minmax_elements_xml(xret, x, 0) < 0)
goto done;
goto fail;
}
}
if ((ymax = yang_find(y, Y_MAX_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ymax);
if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */
nr > cv_uint32_get(cv)){
if (netconf_minmax_elements_xml(xret, x, 1) < 0)
goto done;
goto fail;
}
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Detect unique constraint for duplicates from parent node and minmax
* @param[in] xt XML parent (may have lists w unique constraints as child)
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
* Assume xt:s children are sorted and yang populated.
* The function does two different things of the children of an XML node:
* (1) Check min/max element constraints
* (2) Check unique constraints
*
* The routine uses a node traversing mechanism as the following example, where
* two lists [x1,..] and [x2,..] are embedded:
* xt: {a, b, [x1, x1, x1], d, e, f, [x2, x2, x2], g}
* The function does this using a single iteration and uses the fact that the
* xml symbols share yang symbols: ie [x1..] has yang y1 and d has yd.
*
* Unique constraints:
* Lists are identified, then check_unique_list is called on each list.
* Example, x has an associated yang list node with list of unique constraints
* y-list->y-unique - "a"
* xt->x -> ab
* x -> bc
* x -> ab
*
* Min-max constraints:
* Find upper and lower bound of existing lists and report violations
* Somewhat tricky to find violation of min-elements of empty
* lists, but this is done by a "gap-detection" mechanism, which detects
* gaps in the xml nodes given the ancestor Yang structure.
* But no gap analysis is done if the yang spec of the top-level xml is unknown
* Example:
* Yang structure:y1, y2, y3,
* XML structure: [x1, x1], [x3, x3] where [x2] list is missing
* @note min-element constraints on empty lists are not detected on top-level.
* Or more specifically, if no yang spec if associated with the top-level
* XML node. This may not be a large problem since it would mean empty configs
* are not allowed.
*/
static int
check_list_unique_minmax(cxobj *xt,
cxobj **xret)
{
int retval = -1;
cxobj *x = NULL;
yang_stmt *y;
yang_stmt *yt;
yang_stmt *yp = NULL; /* previous in list */
yang_stmt *ye = NULL; /* yang each list to catch emtpy */
yang_stmt *ych; /* y:s parent node (if choice that ye can compare to) */
cxobj *xp = NULL; /* previous in list */
yang_stmt *yu; /* yang unique */
int ret;
int nr=0; /* Nr of list elements for min/max check */
enum rfc_6020 keyw;
/* RFC 7950 7.7.5: regarding min-max elements check
* The behavior of the constraint depends on the type of the
* leaf-list's or list's closest ancestor node in the schema tree
* that is not a non-presence container (see Section 7.5.1):
* o If no such ancestor exists in the schema tree, the constraint
* is enforced.
* o Otherwise, if this ancestor is a case node, the constraint is
* enforced if any other node from the case exists.
* o Otherwise, it is enforced if the ancestor node exists.
*/
yt = xml_spec(xt); /* If yt == NULL, then no gap-analysis is done */
/* Traverse all elemenents */
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) == NULL)
continue;
if ((ych=yang_choice(y)) == NULL)
ych = y;
keyw = yang_keyword_get(y);
if (keyw != Y_LIST && keyw != Y_LEAF_LIST)
continue;
if (yp != NULL){ /* There exists a previous (leaf)list */
if (y == yp){ /* If same yang as previous x, then skip (eg same list) */
nr++;
continue;
}
else {
/* Check if the list length violates min/max */
if ((ret = check_min_max(xp, yp, nr, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
}
yp = y; /* Restart min/max count */
xp = x; /* Need a reference to the XML as well */
nr = 1;
/* Gap analysis: Check if there is any empty list between y and yp
* Note, does not detect empty choice list (too complicated)
*/
if (yt != NULL && ych != ye){
/* Skip analysis if Yang spec is unknown OR
* if we are still iterating the same Y_CASE w multiple lists
*/
ye = yn_each(yt, ye);
if (ye && ych != ye)
do {
if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){
/* Check if the list length violates min/max */
if ((ret = check_min_max(xt, ye, 0, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ye = yn_each(yt, ye);
} while(ye != NULL && /* to avoid livelock (shouldnt happen) */
ye != ych);
}
if (keyw != Y_LIST)
continue;
/* Here only lists. test unique constraints */
yu = NULL;
while ((yu = yn_each(y, yu)) != NULL) {
if (yang_keyword_get(yu) != Y_UNIQUE)
continue;
/* Here is a list w unique constraints identified by:
* its first element x, its yang spec y, its parent xt, and
* a unique yang spec yu,
*/
if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
}
/* yp if set, is a list that has been traversed
* This check is made in the loop as well - this is for the last list
*/
if (yp){
/* Check if the list length violates min/max */
if ((ret = check_min_max(xp, yp, nr, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* Check if there is any empty list between after last non-empty list
* Note, does not detect empty lists within choice/case (too complicated)
*/
if ((ye = yn_each(yt, ye)) != NULL)
do {
if (yang_keyword_get(ye) == Y_LIST || yang_keyword_get(ye) == Y_LEAF_LIST){
/* Check if the list length violates min/max */
if ((ret = check_min_max(xt, ye, 0, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
} while((ye = yn_each(yt, ye)) != NULL);
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification for added entry
* 1. Check if mandatory leafs present as subs.
* 2. Check leaf values, eg int ranges and string regexps.
* @param[in] xt XML node to be validated
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *xret = NULL;
* if ((ret = xml_yang_validate_add(h, x, &xret)) < 0)
* err;
* if (ret == 0)
* fail;
* @endcode
* @see xml_yang_validate_all
* @see xml_yang_validate_rpc
* @note Should need a variant accepting cxobj **xret
*/
int
xml_yang_validate_add(clicon_handle h,
cxobj *xt,
cxobj **xret)
{
int retval = -1;
cg_var *cv = NULL;
char *reason = NULL;
yang_stmt *yt; /* yang spec of xt going in */
char *body;
int ret;
cxobj *x;
enum cv_type cvtype;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
if ((ret = check_choice(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((ret = check_mandatory(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Check leaf values */
switch (yt->ys_keyword){
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
/* validate value against ranges, etc */
if ((cv = cv_dup(yang_cv_get(yt))) == NULL){
clicon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
/* In the union case, value is parsed as generic REST type,
* needs to be reparsed when concrete type is selected
*/
if ((body = xml_body(xt)) == NULL){
/* We do not allow ints to be empty. Otherwise NULL strings
* are considered as "" */
cvtype = cv_type_get(cv);
if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){
if (netconf_bad_element_xml(xret, "application", yt->ys_argument, "Invalid NULL value") < 0)
goto done;
goto fail;
}
}
else{
if (cv_parse1(body, cv, &reason) != 1){
if (netconf_bad_element_xml(xret, "application", yt->ys_argument, reason) < 0)
goto done;
goto fail;
}
}
if ((ys_cv_validate(h, cv, yt, &reason)) != 1){
if (netconf_bad_element_xml(xret, "application", yt->ys_argument, reason) < 0)
goto done;
goto fail;
}
break;
default:
break;
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_add(h, x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
if (cv)
cv_free(cv);
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}
/*! Some checks done only at edit_config, eg keys in lists
* @param[out] xret Error XML tree. Free with xml_free after use
*/
int
xml_yang_validate_list_key_only(clicon_handle h,
cxobj *xt,
cxobj **xret)
{
int retval = -1;
yang_stmt *yt; /* yang spec of xt going in */
int ret;
cxobj *x;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
if ((ret = check_list_key(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_list_key_only(h, x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification for all (not only added) entries
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
* @param[in] xt XML node to be validated
* @param[out] xret Error XML tree (if retval=0). Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *xret = NULL;
* if ((ret = xml_yang_validate_all(h, x, &xret)) < 0)
* err;
* if (ret == 0)
* fail;
* xml_free(xret);
* @endcode
* @see xml_yang_validate_add
* @see xml_yang_validate_rpc
* @note Should need a variant accepting cxobj **xret
*/
int
xml_yang_validate_all(clicon_handle h,
cxobj *xt,
cxobj **xret)
{
int retval = -1;
yang_stmt *ys; /* yang node */
yang_stmt *yc; /* yang child */
yang_stmt *ye; /* yang must error-message */
char *xpath;
int nr;
int ret;
cxobj *x;
char *namespace = NULL;
cbuf *cb = NULL;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
ys=xml_spec(xt);
if (ys==NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (xml2ns(xt, xml_prefix(xt), &namespace) < 0)
goto done;
if (namespace){
cprintf(cb, "namespace is: %s", namespace);
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
}
else
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), NULL) < 0)
goto done;
goto fail;
}
if (yang_config(ys) != 0){
/* Node-specific validation */
switch (yang_keyword_get(ys)){
case Y_ANYXML:
case Y_ANYDATA:
goto ok;
break;
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
/* Special case if leaf is leafref, then first check against
current xml tree
*/
/* Get base type yc */
if (yang_type_get(ys, NULL, &yc, NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (strcmp(yang_argument_get(yc), "leafref") == 0){
if ((ret = validate_leafref(xt, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
else if (strcmp(yang_argument_get(yc), "identityref") == 0){
if ((ret = validate_identityref(xt, ys, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
break;
default:
break;
}
/* must sub-node RFC 7950 Sec 7.5.3. Can be several.
* XXX. use yang path instead? */
yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL) {
if (yc->ys_keyword != Y_MUST)
continue;
xpath = yc->ys_argument; /* "must" has xpath argument */
if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0)
goto done;
if (!nr){
ye = yang_find(yc, Y_ERROR_MESSAGE, NULL);
if (netconf_operation_failed_xml(xret, "application",
ye?ye->ys_argument:"must xpath validation failed") < 0)
goto done;
goto fail;
}
}
/* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){
xpath = yc->ys_argument; /* "when" has xpath argument */
if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0)
goto done;
if (!nr){
if (netconf_operation_failed_xml(xret, "application",
"when xpath validation failed") < 0)
goto done;
goto fail;
}
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* Check unique and min-max after choice test for example*/
if (yang_config(ys) != 0){
/* Checks if next level contains any unique list constraints */
if ((ret = check_list_unique_minmax(xt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;
goto done;
}
/*! Translate a single xml node to a cligen variable vector. Note not recursive
* @param[out] xret Error XML tree (if ret == 0). Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
*/
int
xml_yang_validate_all_top(clicon_handle h,
cxobj *xt,
cxobj **xret)
{
int ret;
cxobj *x;
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, xret)) < 1)
return ret;
}
if ((ret = check_list_unique_minmax(xt, xret)) < 1)
return ret;
return 1;
}
/*! Translate a single xml node to a cligen variable vector. Note not recursive
* @param[in] xt XML tree containing one top node
* @param[in] ys Yang spec containing type specification of top-node of xt
* @param[out] cvv CLIgen variable vector. Should be freed by cvec_free()
* @retval 0 Everything OK, cvv allocated and set
* @retval -1 Something wrong, clicon_err() called to set error. No cvv returned
* @note cvv Should be freed by cvec_free() after use.
* 'Not recursive' means that only one level of XML bodies is translated to cvec:s.
* If range is wriong (eg 1000 for uint8) a warning is logged, the value is
* skipped, and continues.
* yang is needed to know which type an xml element has.
* Example:
<a>
<b>23</b>
<c>88</c>
<d>
<e>99</e>
</d>
</a>
--> b:23, c:88
* @see cvec2xml
*/
int
xml2cvec(cxobj *xt,
yang_stmt *yt,
cvec **cvv0)
{
int retval = -1;
cvec *cvv = NULL;
cxobj *xc; /* xml iteration variable */
yang_stmt *ys; /* yang spec */
cg_var *cv;
cg_var *ycv;
char *body;
char *reason = NULL;
int ret;
char *name;
xc = NULL;
/* Tried to allocate whole cvv here, but some cg_vars may be invalid */
if ((cvv = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto err;
}
xc = NULL;
/* Go through all children of the xml tree */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){
name = xml_name(xc);
if ((ys = yang_find_datanode(yt, name)) == NULL){
clicon_debug(0, "%s: yang sanity problem: %s in xml but not present in yang under %s",
__FUNCTION__, name, yt->ys_argument);
if ((body = xml_body(xc)) != NULL){
if ((cv = cv_new(CGV_STRING)) == NULL){
clicon_err(OE_PLUGIN, errno, "cv_new");
goto err;
}
cv_name_set(cv, name);
if ((ret = cv_parse1(body, cv, &reason)) < 0){
clicon_err(OE_PLUGIN, errno, "cv_parse %s",name);
goto err;
}
/* If value is out-of-range, log and skip value, and continue */
if (ret == 0){
clicon_log(LOG_WARNING, "cv_parse %s: %s", name, reason);
if (reason)
free(reason);
}
else
cvec_append_var(cvv, cv); /* Add to variable vector */
cv_free(cv);
}
}
else if ((ycv = yang_cv_get(ys)) != NULL){
if ((body = xml_body(xc)) != NULL){
if ((cv = cv_new(CGV_STRING)) == NULL){
clicon_err(OE_PLUGIN, errno, "cv_new");
goto err;
}
if (cv_cp(cv, ycv) < 0){
clicon_err(OE_PLUGIN, errno, "cv_cp");
goto err;
}
if ((ret = cv_parse1(body, cv, &reason)) < 0){
clicon_err(OE_PLUGIN, errno, "cv_parse: %s", name);
goto err;
}
if (ret == 0){
clicon_log(LOG_WARNING, "cv_parse %s: %s", name, reason);
if (reason)
free(reason);
}
else
cvec_append_var(cvv, cv); /* Add to variable vector */
cv_free(cv);
}
}
}
if (debug > 1){
clicon_debug(2, "%s cvv:\n", __FUNCTION__);
cvec_print(stderr, cvv);
}
*cvv0 = cvv;
return 0;
err:
if (cvv)
cvec_free(cvv);
return retval;
}
/*! Translate a cligen variable vector to an XML tree with depth one
* @param[in] cvv CLIgen variable vector. Should be freed by cvec_free()
* @param[in] toptag The XML tree in xt will have this XML tag
* @param[in] xt Parent, or NULL
* @param[out] xt Pointer to XML tree containing one top node. Should be freed with xml_free
* @retval 0 Everything OK, cvv allocated and set
* @retval -1 Something wrong, clicon_err() called to set error. No xt returned
* @see xml2cvec
* @see cvec2xml This does more but has an internal xml2cvec translation
*/
int
cvec2xml_1(cvec *cvv,
char *toptag,
cxobj *xp,
cxobj **xt0)
{
int retval = -1;
cxobj *xt = NULL;
cxobj *xn;
cxobj *xb;
cg_var *cv;
char *val;
int len=0;
int i;
cv = NULL;
while ((cv = cvec_each(cvv, cv)) != NULL)
len++;
if ((xt = xml_new(toptag, xp, NULL)) == NULL)
goto err;
if (xml_childvec_set(xt, len) < 0)
goto err;
cv = NULL;
i = 0;
while ((cv = cvec_each(cvv, cv)) != NULL) {
if (cv_type_get(cv)==CGV_ERR || cv_name_get(cv) == NULL)
continue;
if ((xn = xml_new(cv_name_get(cv), NULL, NULL)) == NULL) /* this leaks */
goto err;
xml_parent_set(xn, xt);
xml_child_i_set(xt, i++, xn);
if ((xb = xml_new("body", xn, NULL)) == NULL) /* this leaks */
goto err;
xml_type_set(xb, CX_BODY);
val = cv2str_dup(cv);
xml_value_set(xb, val); /* this leaks */
if (val)
free(val);
}
*xt0 = xt;
return 0;
err:
if (xt)
xml_free(xt);
return retval;
}
/*! Recursive help function to compute differences between two xml trees
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @param[out] x0vec Pointervector to XML nodes existing in only first tree
* @param[out] x0veclen Length of first vector
* @param[out] x1vec Pointervector to XML nodes existing in only second tree
* @param[out] x1veclen Length of x1vec vector
* @param[out] changed_x0 Pointervector to XML nodes changed orig value
* @param[out] changed_x1 Pointervector to XML nodes changed wanted value
* @param[out] changedlen Length of changed vector
* Algorithm to compare two sorted lists A, B:
* A 0 1 2 3 5 6
* B 0 2 4 5 6
* Let (a, b) be first elements of (A, B) respectively(*)
* a = b : EITHER leafs: a!=b : add a in changed_x0, b in changed_x1,
* OR: Set (A,B) to children of (a,b) and call algorithm recursively
* , get next (a,b)
* a < b : add a in x0, get next a
* a > b : add b in x1, get next b
* (*) "comparing" a&b here is made by xml_cmp() which judges equality from a structural
* perspective, ie both have the same yang spec, if they are lists, they have the
* the same keys. NOT that the values are equal!
* @see xml_diff API function, this one is internal and recursive
*/
static int
xml_diff1(yang_stmt *ys,
cxobj *x0,
cxobj *x1,
cxobj ***x0vec,
size_t *x0veclen,
cxobj ***x1vec,
size_t *x1veclen,
cxobj ***changed_x0,
cxobj ***changed_x1,
size_t *changedlen)
{
int retval = -1;
cxobj *x0c = NULL; /* x0 child */
cxobj *x1c = NULL; /* x1 child */
yang_stmt *yc;
char *b1;
char *b2;
int eq;
/* Traverse x0 and x1 in lock-step */
x0c = x1c = NULL;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
x1c = xml_child_each(x1, x1c, CX_ELMNT);
for (;;){
if (x0c == NULL && x1c == NULL)
goto ok;
else if (x0c == NULL){
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
goto done;
x1c = xml_child_each(x1, x1c, CX_ELMNT);
continue;
}
else if (x1c == NULL){
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
goto done;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
/* Both x0c and x1c exists, check if they are equal. */
eq = xml_cmp(x0c, x1c, 0);
if (eq < 0){
if (cxvec_append(x0c, x0vec, x0veclen) < 0)
goto done;
x0c = xml_child_each(x0, x0c, CX_ELMNT);
continue;
}
else if (eq > 0){
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
goto done;
x1c = xml_child_each(x1, x1c, CX_ELMNT);
continue;
}
else{ /* equal */
if ((yc = xml_spec(x0c)) == NULL){
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x0c));
goto done;
}
if (yang_choice(yc)){
/* if x0c and x1c are choice/case, then they are changed */
if (cxvec_append(x0c, changed_x0, changedlen) < 0)
goto done;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
goto done;
}
else if (yc->ys_keyword == Y_LEAF){
/* if x0c and x1c are leafs w bodies, then they are changed */
if ((b1 = xml_body(x0c)) == NULL) /* empty type */
break;
if ((b2 = xml_body(x1c)) == NULL) /* empty type */
break;
if (strcmp(b1, b2)){
if (cxvec_append(x0c, changed_x0, changedlen) < 0)
goto done;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
goto done;
}
}
else if (xml_diff1(yc, x0c, x1c,
x0vec, x0veclen,
x1vec, x1veclen,
changed_x0, changed_x1, changedlen)< 0)
goto done;
}
x0c = xml_child_each(x0, x0c, CX_ELMNT);
x1c = xml_child_each(x1, x1c, CX_ELMNT);
}
ok:
retval = 0;
done:
return retval;
}
/*! Compute differences between two xml trees
* @param[in] yspec Yang specification
* @param[in] x0 First XML tree
* @param[in] x1 Second XML tree
* @param[out] first Pointervector to XML nodes existing in only first tree
* @param[out] firstlen Length of first vector
* @param[out] second Pointervector to XML nodes existing in only second tree
* @param[out] secondlen Length of second vector
* @param[out] changed_x0 Pointervector to XML nodes changed orig value
* @param[out] changed_x1 Pointervector to XML nodes changed wanted value
* @param[out] changedlen Length of changed vector
* All xml vectors should be freed after use.
*/
int
xml_diff(yang_stmt *yspec,
cxobj *x0,
cxobj *x1,
cxobj ***first,
size_t *firstlen,
cxobj ***second,
size_t *secondlen,
cxobj ***changed_x0,
cxobj ***changed_x1,
size_t *changedlen)
{
int retval = -1;
*firstlen = 0;
*secondlen = 0;
*changedlen = 0;
if (x0 == NULL && x1 == NULL)
return 0;
if (x1 == NULL){
if (cxvec_append(x0, first, firstlen) < 0)
goto done;
goto ok;
}
if (x0 == NULL){
if (cxvec_append(x0, second, secondlen) < 0)
goto done;
goto ok;
}
if (xml_diff1((yang_stmt*)yspec, x0, x1,
first, firstlen,
second, secondlen,
changed_x0, changed_x1, changedlen) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Construct an xml key format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* xpath: /modname:a/b/%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] cb api_path_fmt,
* @retval 0 OK
* @retval -1 Error
* @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression"
*/
static int
yang2api_path_fmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_stmt *yp; /* parent */
yang_stmt *ymod;
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
if ((yp = yang_parent_get(ys)) == NULL){
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
goto done;
}
if (yp != NULL && /* XXX rm */
yang_keyword_get(yp) != Y_MODULE &&
yang_keyword_get(yp) != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
goto done;
if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
#if 0
/* In some cases, such as cli_show_auto, a trailing '/' should
* NOT be present if ys is a key in a list.
* But in other cases (I think most), the / should be there,
* so a patch is added in cli_show_auto instead.
*/
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST &&
yang_key_match(yp, ys->ys_argument) == 1)
;
else
#endif
cprintf(cb, "/");
}
/* If parent namespace/module is different from child -> add child prefix */
if (ys_real_module(yp) != (ymod = ys_real_module(ys)))
cprintf(cb, "%s:", yang_argument_get(ymod));
}
else /* top symbol - mark with name prefix */
cprintf(cb, "/%s:", yang_argument_get(yp));
if (inclkey){
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
else{
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST){
if (yang_key_match(yp, yang_argument_get(ys)) == 0)
cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
}
else
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
switch (yang_keyword_get(ys)){
case Y_LIST:
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
for (i=0; i<cvec_len(cvk); i++){
if (i)
cprintf(cb, ",");
cprintf(cb, "%%s");
}
break;
case Y_LEAF_LIST:
cprintf(cb, "=%%s");
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Construct an api_path_format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* api_path: /a/b=%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] api_path_fmt XML api path. Needs to be freed after use.
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
yang2api_path_fmt(yang_stmt *ys,
int inclkey,
char **api_path_fmt)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
goto done;
if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
* xmlkeyfmt: /interfaces/interface=%s/ipv4/address=%s
* cvv: 0 : set interfaces interface e ipv4 address 1.2.3.4
* 1 : name = "e"
* 2 : ip = "1.2.3.4"
* api_path: /interfaces/interface=e/ipv4/address=1.2.3.4
* @param[in] api_path_fmt XML key format, eg /aaa/%s/name
* @param[in] cvv cligen variable vector, one for every wildchar in
* api_path_fmt
* @param[out] api_path api_path, eg /aaa/17. Free after use
* @note first and last elements of cvv are not used,..
* @see api_path_fmt2xpath
* @example
* api_path_fmt: /interfaces/interface=%s/name
* cvv: -
* api_path: /interfaces/interface/name
* @example
* api_path_fmt: /interfaces/interface=%s/name
* cvv: e0
* api_path: /interfaces/interface=e0/name
* @example
* api_path_fmt: /subif-entry=%s,%s/subid
* cvv: foo
* api_path: /subif-entry=foo/subid
*
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
api_path_fmt2api_path(char *api_path_fmt,
cvec *cvv,
char **api_path)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
char *strenc=NULL;
cg_var *cv;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if (j == cvec_len(cvv)) /* last element */
;
else{
cv = cvec_i(cvv, j++);
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (uri_percent_encode(&strenc, "%s", str) < 0)
goto done;
cprintf(cb, "%s", strenc);
free(strenc); strenc = NULL;
free(str); str = NULL;
}
}
else
if (c == '%')
esc++;
else{
if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%' && j == cvec_len(cvv))
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*api_path = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML path
* Used to input xmldb_get()
* @param[in] api_path_fmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] xpath XPATH
* Add .* in last %s position.
* @example
* api_path_fmt: /interface/%s/address/%s
* cvv: name=eth0
* xpath: /interface/[name='eth0']/address
* @example
* api_path_fmt: /ip/me/%s (if key)
* cvv: -
* xpath: /ipv4/me/a
* @example
* api_path_fmt: /subif-entry=%s,%s/subid
* cvv: foo
* xpath: /subif-entry[if-name=foo]/subid"
* @example
* api_path_fmt: /a:b/c
* xpath : /b/c prefix:a
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
api_path_fmt2xpath(char *api_path_fmt,
cvec *cvv,
char **xpath)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
cg_var *cv;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if (j == cvec_len(cvv)) /* last element */
;
else{
cv = cvec_i(cvv, j++);
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
free(str);
}
}
else /* regular char */
if (c == '%')
esc++;
else{
if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! 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
* @param[out] upmark Set if a child (recursively) has marked set.
* The function removes all branches that does not pass the test
* Purge all nodes that dont have MARK flag set recursively.
* Save all nodes that is MARK:ed or have at least one (grand*)child that is MARKed
* @code
* xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL);
* @endcode
* @note This function seems a little too complex semantics
* @see xml_tree_prune_flagged for a simpler variant
*/
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;
mark = 0;
yt = xml_spec(xt); /* xan be null */
x = NULL;
xprev = x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
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_purge(x) < 0)
goto done;
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;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
/* If it is key remove it here */
if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0)
goto done;
if (iskey && xml_purge(x) < 0)
goto done;
x = xprev;
}
xprev = x;
}
}
retval = 0;
done:
if (upmark)
*upmark = mark;
return retval;
}
/*! Prune everything that passes test
* @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
* The function removes all branches that does not pass test
* @code
* xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1);
* @endcode
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
x = NULL;
xprev = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (xml_flag(x, flag) == test?flag:0){ /* Pass test means purge */
if (xml_purge(x) < 0)
goto done;
x = xprev;
continue;
}
if (xml_tree_prune_flagged(x, flag, test) < 0)
goto done;
xprev = x;
}
retval = 0;
done:
return retval;
}
/*! Add prefix:namespace pair to xml node, set cache, prefix, etc
*/
static int
add_namespace(cxobj *x1, /* target */
cxobj *x1p,
char *prefix1,
char *namespace)
{
int retval = -1;
cxobj *xa = NULL;
/* Add binding to x1p. We add to parent due to heurestics, so we dont
* end up in adding it to large number of siblings
*/
if (nscache_set(x1, prefix1, namespace) < 0)
goto done;
/* Create xmlns attribute to x1p/x1 XXX same code v */
if (prefix1){
if ((xa = xml_new(prefix1, x1, NULL)) == NULL)
goto done;
if (xml_prefix_set(xa, "xmlns") < 0)
goto done;
}
else{
if ((xa = xml_new("xmlns", x1, NULL)) == NULL)
goto done;
}
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, namespace) < 0)
goto done;
xml_sort(x1, NULL); /* Ensure attr is first / XXX xml_insert? */
/* 5. Add prefix to x1, if any */
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
* @param[in] arg Ignored
* Typically called in a recursive apply function:
* @code
* xml_apply(xt, CX_ELMNT, xml_default, NULL);
* @endcode
*/
int
xml_default(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys;
yang_stmt *y;
int i;
cxobj *xc;
cxobj *xb;
char *str;
int added=0;
char *namespace;
char *prefix;
int ret;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
/* Check leaf defaults */
if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST ||
ys->ys_keyword == Y_INPUT){
for (i=0; i<ys->ys_len; i++){
y = ys->ys_stmt[i];
if (y->ys_keyword != Y_LEAF)
continue;
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
if (!xml_find(xt, y->ys_argument)){
if ((xc = xml_new(y->ys_argument, NULL, y)) == NULL)
goto done;
/* assign right prefix */
if ((namespace = yang_find_mynamespace(y)) != NULL){
prefix = NULL;
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
goto done;
if (ret){
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
else{ /* namespace does not exist in target, use source prefix */
if ((prefix = yang_find_myprefix(y)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if (add_namespace(xc, xt, prefix, namespace) < 0)
goto done;
}
}
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, NULL)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
added++;
if (xml_insert(xt, xc, INS_LAST, NULL, NULL) < 0)
goto done;
}
}
}
}
retval = 0;
done:
return retval;
}
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/
int
xml_sanity(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys;
char *name;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
name = xml_name(xt);
if (strstr(ys->ys_argument, name)==NULL){
clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'",
name, ys->ys_argument);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Mark all nodes that are not configure data and set return
* @param[in] xt XML tree
* @param[out] arg If set, set to 1 as int* if not config data
*/
int
xml_non_config_data(cxobj *xt,
void *arg) /* Set to 1 if state node */
{
int retval = -1;
yang_stmt *ys;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
if (!yang_config(ys)){ /* config == false means state data: mark for remove */
xml_flag_set(xt, XML_FLAG_MARK);
if (arg)
(*(int*)arg) = 1;
}
retval = 0;
done:
return retval;
}
/*! Add yang specification backpointer to rpc
*
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @retval 0 OK
* @retval -1 Error
* @note This may be unnecessary if yspec is set on creation
* @note For subs to anyxml nodes will not have spec set
* @note No validation is done,... XXX
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode
* @see xml_spec_populate
*/
int
xml_spec_populate_rpc(clicon_handle h,
cxobj *xrpc,
yang_stmt *yspec)
{
int retval = -1;
yang_stmt *yrpc = NULL; /* yang node */
yang_stmt *ymod=NULL; /* yang module */
yang_stmt *yi = NULL; /* input */
cxobj *x;
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
clicon_err(OE_UNIX, EINVAL, "RPC expected");
goto done;
}
x = NULL;
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
if (ymod != NULL)
yrpc = yang_find(ymod, Y_RPC, xml_name(x));
/* Non-strict semantics: loop through all modules to find the node
*/
if (yrpc){
xml_spec_set(x, yrpc);
if ((yi = yang_find(yrpc, Y_INPUT, NULL)) != NULL){
/* xml_spec_populate need to have parent with yang spec for
* recursive population to work. Therefore, assign input yang
* to rpc level although not 100% intuitive */
xml_spec_set(x, yi);
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
}
}
}
retval = 0;
done:
return retval;
}
/*! Add yang specification backpointer to XML node
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @note This may be unnecessary if yspec is set on creation
* @note For subs to anyxml nodes will not have spec set
* @note No validation is done,... XXX
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode
*/
#undef DEBUG
int
xml_spec_populate(cxobj *x,
void *arg)
{
int retval = -1;
yang_stmt *yspec = NULL; /* yang spec */
yang_stmt *y = NULL; /* yang node */
yang_stmt *yparent; /* yang parent */
yang_stmt *ymod; /* yang module */
cxobj *xp = NULL; /* xml parent */
char *name;
char *ns = NULL; /* XML namespace of x */
char *nsy = NULL; /* Yang namespace of x */
yspec = (yang_stmt*)arg;
xp = xml_parent(x);
name = xml_name(x);
#ifdef DEBUG
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
#endif
if (xml2ns(x, xml_prefix(x), &ns) < 0)
goto done;
if (xp && (yparent = xml_spec(xp)) != NULL){
y = yang_find_datanode(yparent, name);
}
else if (yspec){
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
/* ymod is "real" module, name may belong to included submodule */
if (ymod != NULL){
#ifdef DEBUG
clicon_debug(1, "%s %s mod:%s", __FUNCTION__, name, yang_argument_get(ymod));
#endif
y = yang_find_schemanode(ymod, name);
}
#ifdef DEBUG
else
clicon_debug(1, "%s %s mod:NULL", __FUNCTION__, name);
#endif
}
if (y) {
nsy = yang_find_mynamespace(y);
if (ns == NULL || nsy == NULL){
clicon_err(OE_XML, EFAULT, "Namespace NULL");
goto done;
}
#ifdef DEBUG
clicon_debug(1, "%s y:%s", __FUNCTION__, yang_argument_get(y));
#endif
/* Assign spec only if namespaces match */
if (strcmp(ns, nsy) == 0)
xml_spec_set(x, y);
}
#ifdef DEBUG
else
clicon_debug(1, "%s y:NULL", __FUNCTION__);
#endif
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path(cvv) to xml xpath(cbuf) and namespace context
*
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3) as cvec
* @param[in] offset Offset of cvec, where api-path starts
* @param[in] yspec Yang spec
* @param[in,out] xpath The xpath as cbuf (must be created and may have content)
* @param[out] nsc Namespace context of xpath (free w xml_nsctx_free)
* @param[out] xerr Netconf error message
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error xml set
* @retval -1 Fatal error, clicon_err called
*
* @code
* cbuf *xpath = cbuf_new();
* cvec *cvv = NULL;
* cvec *nsc = NULL;
* if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0)
* err;
* if ((ret = api_path2xpath_cvv(yspec, cvv, 0, cxpath, &nsc, NULL)) < 0)
* err;
* if (ret == 1)
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath);
* cvec_free(nsc);
* @endcode
* It works like this:
* Assume origin incoming path is
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
* ["www.foo.com" "restconf" "a" "b=c"]
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
* @note Not proper namespace translation from api-path 2 xpath!!!
* @see api_path2xml For api-path to xml tree
* @see api_path2xpath Using strings as parameters
*/
int
api_path2xpath_cvv(cvec *api_path,
int offset,
yang_stmt *yspec,
cbuf *xpath,
cvec **nscp,
cxobj **xerr)
{
int retval = -1;
int i;
cg_var *cv;
char *nodeid;
char *prefix = NULL; /* api-path (module) prefix */
char *xprefix = NULL; /* xml xpath prefix */
char *name = NULL;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
yang_stmt *ymod = NULL;
char *val;
cg_var *cvi;
char **valvec = NULL;
int vi;
int nvalvec;
cbuf *cberr = NULL;
char *namespace = NULL;
cvec *nsc = NULL;
/* Initialize namespace context */
if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
goto done;
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
for (i=offset; i<cvec_len(api_path); i++){
cv = cvec_i(api_path, i);
nodeid = cv_name_get(cv);
/* api-path: prefix points to module */
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
clicon_debug(1, "%s [%d] cvname: %s:%s",
__FUNCTION__, i, prefix?prefix:"", name);
/* top-node must have prefix */
if (i == offset && prefix == NULL){
cprintf(cberr, "'%s': Expected prefix:name", nodeid);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
ymod = NULL;
if (prefix){ /* if prefix -> get module + change namespace */
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
cprintf(cberr, "No such yang module: %s", prefix);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod); /* change namespace */
}
if (i == offset && ymod) /* root */
y = yang_find_datanode(ymod, name);
else
y = yang_find_datanode(y, name);
if (y == NULL){
if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail;
}
/* Get XML/xpath prefix given namespace.
* note different from api-path prefix
*/
if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
xprefix = yang_find_myprefix(y);
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
/* not found, add it to nsc */
if (xml_nsctx_add(nsc, xprefix, namespace) < 0)
goto done;
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
switch (yang_keyword_get(y)){
case Y_LIST:
/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values
*/
if (valvec){ /* loop, valvec may have been used before */
free(valvec);
valvec = NULL;
}
if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
goto done;
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
vi = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
cprintf(xpath, "[");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
}
break;
case Y_LEAF_LIST: /* XXX: LOOP? */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
if (val)
cprintf(xpath, "[.='%s']", val);
else
cprintf(xpath, "[.='']");
break;
default:
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
break;
}
if (val)
free(val);
}
else{
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (name){
free(name);
name = NULL;
}
} /* for */
retval = 1; /* OK */
if (nscp){
*nscp = nsc;
nsc = NULL;
}
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
if (valvec)
free(valvec);
if (prefix)
free(prefix);
if (nsc)
cvec_free(nsc);
if (name)
free(name);
return retval;
fail:
retval = 0; /* Validation failed */
goto done;
}
/*! Translate from restconf api-path to xml xpath and namespace
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3)
* @param[in] yspec Yang spec
* @param[out] xpath xpath (use free() to deallocate)
* @param[out] nsc Namespace context of xpath (free w xml_nsctx_free)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf called
* @retval -1 Fatal error, clicon_err called
* @code
* char *xpath = NULL;
* cvec *nsc = NULL;
* if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc)) < 0)
* err;
* if (ret == 1)
* ... access xpath as cbuf_get(xpath)
* free(xpath)
* cvec_free(nsc);
* @endcode
*
* @see api_path2xml_cvv which uses other parameter formats
*/
int
api_path2xpath(char *api_path,
yang_stmt *yspec,
char **xpathp,
cvec **nsc)
{
int retval = -1;
cvec *cvv = NULL; /* api-path vector */
cbuf *xpath = NULL; /* xpath as cbuf (sub-function uses that) */
cxobj *xerr = NULL; /* ignored */
/* Split api-path into cligen variable vector */
if (str2cvec(api_path, '/', '=', &cvv) < 0)
goto done;
if ((xpath = cbuf_new()) == NULL)
goto done;
if ((retval = api_path2xpath_cvv(cvv, 0, yspec, xpath, nsc, &xerr)) < 0){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if (retval == 0){
/* XXX: xerr ignored */
clicon_err(OE_XML, EINVAL, "xml does not adhere to yang");
goto fail;
}
/* prepare output xpath parameter */
if (xpathp)
if ((*xpathp = strdup(cbuf_get(xpath))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 1;
done:
if (xerr)
xml_free(xerr);
if (cvv)
cvec_free(cvv);
if (xpath)
cbuf_free(xpath);
return retval;
fail:
retval = 0; /* Validation failed */
goto done;
}
/*! Create xml tree from api-path as vector
* @param[in] vec APIpath as char* vector
* @param[in] nvec Length of vec
* @param[in] x0 Xpath tree so far
* @param[in] y0 Yang spec for x0
* @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xpathp Resulting xml tree
* @param[out] ypathp Yang spec matching xpathp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called
*
* @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @see api_path2xpath For api-path to xml xpath translation
* @see api_path2xml
*/
static int
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_stmt *y0,
yang_class nodeclass,
int strict,
cxobj **xpathp,
yang_stmt **ypathp,
cxobj **xerr)
{
int retval = -1;
char *nodeid;
char *name = NULL;
char *prefix = NULL;
char *restval = NULL;
char *restval_enc;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
char **valvec = NULL;
int nvalvec = 0;
int vi;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ymod;
yang_stmt *ykey;
char *namespace = NULL;
cbuf *cberr = NULL;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
if (xpathp)
*xpathp = x0;
if (ypathp)
*ypathp = y0;
goto ok;
} /* E.g "x=1,2" -> nodeid:x restval=1,2 */
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* restval is RFC 3896 encoded */
if ((restval_enc = index(nodeid, '=')) != NULL){
*restval_enc = '\0';
restval_enc++;
if (uri_percent_decode(restval_enc, &restval) < 0)
goto done;
}
/* Split into prefix and localname */
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
if (y0->ys_keyword == Y_SPEC){ /* top-node */
if (prefix == NULL){
cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
cprintf(cberr, "No such yang module prefix");
if (netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod);
y0 = ymod;
}
y = (nodeclass==YC_SCHEMANODE)?
yang_find_schemanode(y0, name):
yang_find_datanode(y0, name);
if (y == NULL){
if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail;
}
if (prefix && namespace == NULL){
if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod);
}
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (0 && restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=restval'");
goto done;
}
if ((x = xml_new(y->ys_argument, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
if ((xb = xml_new("body", x, NULL)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (restval && xml_value_set(xb, restval) < 0)
goto done;
break;
case Y_LIST:
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
if (valvec){ /* loop, valvec may have been used before */
free(valvec);
valvec = NULL;
}
if (restval==NULL){
if (strict){
cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
}
else{
/* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values
*/
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if ((nvalvec != cvec_len(cvk)) && strict){
cprintf(cberr, "List key %s length mismatch", name);
if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
}
cvi = NULL;
/* create list object */
if ((x = xml_new(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
vi = 0;
/* Create keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL){
keyname = cv_string_get(cvi);
if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"",
yang_argument_get(y), keyname);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((xn = xml_new(keyname, x, ykey)) == NULL)
goto done;
xml_type_set(xn, CX_ELMNT);
if ((xb = xml_new("body", xn, NULL)) == NULL)
goto done;
xml_type_set(xb, CX_BODY);
if (vi++ < nvalvec){
if (xml_value_set(xb, valvec[vi-1]) < 0)
goto done;
}
}
break;
default: /* eg Y_CONTAINER, Y_LEAF */
if ((x = xml_find_type(x0, NULL, name, CX_ELMNT)) == NULL){ /* eg key of list */
if ((x = xml_new(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
}
break;
}
if (x && namespace){
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
}
if ((retval = api_path2xml_vec(vec+1, nvec-1,
x, y,
nodeclass, strict,
xpathp, ypathp, xerr)) < 1)
goto done;
ok:
retval = 1; /* OK */
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
if (prefix)
free(prefix);
if (name)
free(name);
if (restval)
free(restval);
if (valvec)
free(valvec);
return retval;
fail:
retval = 0; /* invalid api-path or XML */
goto done;
}
/*! Create xml tree from api-path
* @param[in] api_path (Absolute) API-path as defined in RFC 8040
* @param[in] yspec Yang spec
* @param[in,out] xtop Incoming XML tree
* @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xbotp Resulting xml tree (end of xpath)
* @param[out] ybotp Yang spec matching xbotp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called
* @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @example
* api_path: /subif-entry=foo/subid
* xtop[in] <config/>
* xtop[out]:<config/> <subif-entry>
* <if-name>foo<if-name><subid/>>
* </subif-entry></config>
* xbotp: <subid/>
* ybotp: Y_LEAF subid
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @see api_path2xpath For api-path to xml xpath translation
*/
int
api_path2xml(char *api_path,
yang_stmt *yspec,
cxobj *xtop,
yang_class nodeclass,
int strict,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{
int retval = -1;
char **vec = NULL;
int nvec;
cxobj *xroot;
cbuf *cberr = NULL;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (*api_path!='/'){
cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
cprintf(cberr, "Malformed api-path: %s: too short)", api_path);
if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict,
xbotp, ybotp, xerr)) < 1)
goto done;
xml_yang_root(*xbotp, &xroot);
if (xmlns_assign(xroot) < 0)
goto done;
// ok:
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given an XML node, build an xpath to root, internal function
* @retval 0 OK
* @retval -1 Error. eg XML malformed
*/
static int
xml2xpath1(cxobj *x,
cbuf *cb)
{
int retval = -1;
cxobj *xp;
yang_stmt *y = NULL;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
cxobj *xkey;
cxobj *xb;
char *b;
enum rfc_6020 keyword;
if ((xp = xml_parent(x)) != NULL &&
xml_spec(xp) != NULL)
xml2xpath1(xp, cb);
/* XXX: sometimes there should be a /, sometimes not */
cprintf(cb, "/%s", xml_name(x));
if ((y = xml_spec(x)) != NULL){
keyword = yang_keyword_get(y);
switch (keyword){
case Y_LEAF_LIST:
if ((b = xml_body(x)) != NULL)
cprintf(cb, "[.=\"%s\"]", b);
else
cprintf(cb, "[.=\"\"]");
break;
case Y_LIST:
cvk = yang_cvec_get(y);
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((xkey = xml_find(x, keyname)) == NULL)
goto done; /* No key in xml */
if ((xb = xml_find(x, keyname)) == NULL)
goto done;
b = xml_body(xb);
cprintf(cb, "[%s=\"%s\"]", keyname, b?b:"");
}
break;
default:
break;
}
}
retval = 0;
done:
return retval;
}
/*! Given an XML node, build an xpath to root
* Builds only unqualified xpaths, ie no predicates []
* @param[in] x XML object
* @param[out] xpath Malloced xpath string. Need to free() after use
* @retval 0 OK
* @retval -1 Error. (eg XML malformed)
*/
int
xml2xpath(cxobj *x,
char **xpathp)
{
int retval = -1;
cbuf *cb;
char *xpath = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xml2xpath1(x, cb) < 0)
goto done;
/* XXX: see xpath in test statement,.. */
xpath = cbuf_get(cb);
if (xpathp){
if ((*xpathp = strdup(xpath)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
xpath = NULL;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Construct an api_path from an XML node (single level not recursive)
* @param[in] x XML node (need to be yang populated)
* @param[out] cb api_path, must be initialized
* @retval 0 OK
* @retval -1 Error
* @see yang2api_path_fmt
* @see xml2xpath
*/
int
xml2api_path_1(cxobj *x,
cbuf *cb)
{
int retval = -1;
yang_stmt *y = NULL;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
enum rfc_6020 keyword;
int i;
char *keyname;
cxobj *xkey;
cxobj *xb;
char *b;
char *enc;
yang_stmt *ymod;
cxobj *xp;
if ((y = xml_spec(x)) == NULL){
cprintf(cb, "/%s", xml_name(x));
goto ok;
}
ymod = ys_module(y);
xp = xml_parent(x);
if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */
cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x));
else
cprintf(cb, "/%s", xml_name(x));
keyword = yang_keyword_get(y);
switch (keyword){
case Y_LEAF_LIST:
b = xml_body(x);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "=%s", enc?enc:"");
if (enc)
free(enc);
break;
case Y_LIST:
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
cvi = NULL;
i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((xkey = xml_find(x, keyname)) == NULL)
goto done; /* No key in xml */
if ((xb = xml_find(x, keyname)) == NULL)
goto done;
if (i++)
cprintf(cb, ",");
b = xml_body(xb);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "%s", enc?enc:"");
if (enc)
free(enc);
}
break;
default:
break;
}
#if 0
{ /* Just for testing */
cxobj *xc;
if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
if (xml2api_path_1(xc, cb) < 0)
goto done;
}
#endif
ok:
retval = 0;
done:
return retval;
}
/*! Check if the module tree x is in is assigned right XML namespace, assign if not
* @param[in] x XML node
*(0. You should probably find the XML root and apply this function to that.)
* 1. Check which namespace x should have (via yang). This is correct namespace.
* 2. Check which namespace x has via its XML tree
* 3. If equal, OK
* 4. Assign the correct namespace to the XML node
* Assign default namespace to x. Create an "xmlns"
* @code
* xml_yang_root(x, &xroot);
* xmlns_assign(xroot);
* @endcode
*/
int
xmlns_assign(cxobj *x)
{
int retval = -1;
yang_stmt *y;
char *ns_correct; /* correct uri */
char *ns_xml; /* may be null or incorrect */
if ((y = xml_spec(x)) == NULL){
clicon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x));
goto done;
}
/* 1. Check which namespace x should have (via yang). This is correct namespace. */
if ((ns_correct = yang_find_mynamespace(y)) == NULL){
clicon_err(OE_YANG, ENOENT, "yang node %s does not have namespace", y->ys_argument);
goto done;
}
/* 2. Check which namespace x has via its XML tree */
if (xml2ns(x, NULL, &ns_xml) < 0)
goto done;
/* 3. If equal, OK, 4. Else, find root of XML tree */
if (ns_xml && strcmp(ns_xml, ns_correct)==0)
goto ok;
/* 4. Assign the correct namespace */
if (xmlns_set(x, NULL, ns_correct) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace
* @param[in] x0 Source XML tree
* @param[in] x1 Target XML tree
* 1. Find N=namespace(x0)
* 2. Detect if N is declared in x1 parent
* 3. If yes, assign prefix to x1
* 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root)
* 5. Add prefix to x1, if any
* 6. Ensure x1 cache is updated
* @note switch use of x0 and x1 compared to datastore text_modify
* @see xml2ns
* XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0)
goto done;
*/
int
check_namespaces(cxobj *x0, /* source */
cxobj *x1, /* target */
cxobj *x1p)
{
int retval = -1;
char *namespace = NULL;
char *prefix0 = NULL;;
char *prefix1 = NULL;
char *prefixb = NULL; /* identityref body prefix */
cvec *nsc0 = NULL;
cvec *nsc = NULL;
int isroot;
char *pexist = NULL;
yang_stmt *y;
/* XXX: need to identify root better than hiereustics and strcmp,... */
isroot = xml_parent(x1p)==NULL &&
(strcmp(xml_name(x1p), "config") == 0 || strcmp(xml_name(x1p), "top") == 0)&&
xml_prefix(x1p)==NULL;
/* 1. Find N=namespace(x0) */
prefix0 = xml_prefix(x0);
if (xml2ns(x0, prefix0, &namespace) < 0)
goto done;
if (namespace == NULL){
clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s",
prefix0?prefix0:"NULL");
goto done;
}
/* 2a. Detect if namespace is declared in x1 target parent */
if (xml2prefix(x1p, namespace, &pexist) == 1){
/* Yes, and it has prefix pexist */
if (pexist){
if ((prefix1 = strdup(pexist)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else
prefix1 = NULL;
/* 3. If yes, assign prefix to x1 */
if (prefix1 && xml_prefix_set(x1, prefix1) < 0)
goto done;
/* And copy namespace context from parent to child */
if ((nsc0 = nscache_get_all(x1p)) != NULL){
if ((nsc = cvec_dup(nsc0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_dup");
goto done;
}
nscache_replace(x1, nsc);
}
/* Just in case */
if (nscache_set(x1, prefix1, namespace) < 0)
goto done;
}
else{ /* No, namespace does not exist in x1 _parent_
* Check if it is exists in x1 itself */
if (nscache_get_prefix(x1, namespace, &pexist) == 1){
/* Yes it exists, but is it equal? */
if ((pexist == NULL && prefix0 == NULL) ||
(pexist && prefix0 &&
strcmp(pexist, prefix0)==0)){ /* Equal, reuse */
;
}
else{ /* namespace exist, but not equal, use existing */
/* Add prefix to x1, if any */
if (pexist && xml_prefix_set(x1, pexist) < 0)
goto done;
}
goto ok; /* skip */
}
else
{ /* namespace does not exist in target x1, use source prefix
* use the prefix defined in the module
*/
if (isroot){
if (prefix0 && (prefix1 = strdup(prefix0)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
else{
y = xml_spec(x0);
if ((prefix1 = strdup(yang_find_myprefix(y))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
}
if (add_namespace(x1, x1p, prefix1, namespace) < 0)
goto done;
}
ok:
/* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */
retval = 0;
done:
if (prefixb)
free(prefixb);
if (prefix1)
free(prefix1);
return retval;
}
/*! Merge a base tree x0 with x1 with yang spec y
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[out] reason If retval=0 a malloced string
* @retval 0 OK. If reason is set, Yang error
* @retval -1 Error
* Assume x0 and x1 are same on entry and that y is the spec
*/
static int
xml_merge1(cxobj *x0, /* the target */
yang_stmt *y0,
cxobj *x0p,
cxobj *x1, /* the source */
char **reason)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *x1name;
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
cbuf *cbr = NULL; /* Reason buffer */
assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0);
x1name = xml_name(x1);
if (y0->ys_keyword == Y_LEAF_LIST || y0->ys_keyword == Y_LEAF){
x1bstr = xml_body(x1);
if (x0==NULL){
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
if (x1bstr){ /* empty type does not have body */
if ((x0b = xml_new("body", x0, NULL)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) == NULL){
if ((x0b = xml_new("body", x0, NULL)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
if (check_namespaces(x1, x0, x0p) < 0)
goto done;
} /* if LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST */
if (x0==NULL){
if ((x0 = xml_new(x1name, NULL, (yang_stmt*)y0)) == NULL)
goto done;
}
if (check_namespaces(x1, x0, x0p) < 0)
goto done;
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_datanode(y0, x1cname)) == NULL){
if (reason){
if ((cbr = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname);
if ((*reason = strdup(cbuf_get(cbr))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
break;
}
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (xml_merge1(x0c, yc, x0, x1c, reason) < 0)
goto done;
if (*reason != NULL)
goto ok;
} /* while */
if (xml_parent(x0) == NULL &&
xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0)
goto done;
} /* else Y_CONTAINER */
ok:
retval = 0;
done:
if (cbr)
cbuf_free(cbr);
return retval;
}
/*! Merge XML trees x1 into x0 according to yang spec yspec
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 xml tree which modifies base
* @param[in] yspec Yang spec
* @param[out] reason If retval=0, reason is set. Malloced. Needs to be freed by caller
* @retval 0 OK. If reason is set, Yang error
* @retval -1 Error
* @note both x0 and x1 need to be top-level trees
* @see text_modify_top as more generic variant (in datastore text)
* @note returns -1 if YANG do not match, you may want to have a softer error
*/
int
xml_merge(cxobj *x0,
cxobj *x1,
yang_stmt *yspec,
char **reason)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc;
yang_stmt *ymod;
cbuf *cbr = NULL; /* Reason buffer */
if (x0 == NULL || x1 == NULL){
clicon_err(OE_UNIX, EINVAL, "parameters x0 or x1 is NULL");
goto done;
goto done;
}
/* Loop through children of the modification tree */
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
if ((ys_module_by_xml(yspec, x1c, &ymod)) < 0)
goto done;
if (ymod == NULL){
if (reason &&
(*reason = strdup("Namespace not found or yang spec not loaded")) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
goto ok;
}
/* Get yang spec of the child */
if ((yc = yang_find_datanode(ymod, x1cname)) == NULL){
if (reason){
if ((cbr = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?)", xml_name(x1), x1cname);
if ((*reason = strdup(cbuf_get(cbr))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
break;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
/* There is a case where x0c and x1c are choice nodes, if so,
* it is treated as a match, and x0c will remain
* If it is overwritten, then x0c should be removed here.
*/
if (xml_merge1(x0c, yc, x0, x1c, reason) < 0)
goto done;
if (*reason != NULL)
break;
}
ok:
retval = 0; /* OK */
done:
if (cbr)
cbuf_free(cbr);
return retval;
}
/*! Get integer value from xml node from yang enumeration
* @param[in] node XML node in a tree
* @param[out] val Integer value returned
* @retval 0 OK, value parsed
* @retval -1 Error, value not obtained or parsed, no reason given
* @note assume yang spec set
* @note The function only returns assigned values, but according to RFC:
If a value is not specified, then one will be automatically assigned.
If the "enum" substatement is the first one defined, the assigned
value is zero (0); otherwise, the assigned value is one greater than
the current highest enum value (i.e., the highest enum value,
* Thanks: Matthew Smith
*/
int
yang_enum_int_value(cxobj *node,
int32_t *val)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *ys;
yang_stmt *ytype;
yang_stmt *yrestype; /* resolved type */
yang_stmt *yenum;
yang_stmt *yval;
char *reason = NULL;
if (node == NULL)
goto done;
if ((ys = (yang_stmt *) xml_spec(node)) == NULL)
goto done;
if ((yspec = ys_spec(ys)) == NULL)
goto done;
if ((ytype = yang_find(ys, Y_TYPE, NULL)) == NULL)
goto done;
if (yang_type_resolve(ys, ys, ytype, &yrestype,
NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (yrestype==NULL || strcmp(yrestype->ys_argument, "enumeration"))
goto done;
if ((yenum = yang_find(yrestype, Y_ENUM, xml_body(node))) == NULL)
goto done;
/* Should assign value if yval not found */
if ((yval = yang_find(yenum, Y_VALUE, NULL)) == NULL)
goto done;
/* reason is string containing why int could not be parsed */
if (parse_int32(yval->ys_argument, val, &reason) < 0)
goto done;
retval = 0;
done:
return retval;
}