clixon/lib/src/clixon_xml_map.c
Olof hagsand 3dba0b5370 Restore xmldb_get() to original (but removed one parameter
Moved all zero-copy xmldb_get functions to xmldb_get0.
Clarified CHANGELOG for xmldb_get changes
2019-06-04 11:14:03 +02:00

3026 lines
84 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_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"
/*! x is element and has eactly one child which in turn has none */
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] cbret Error buffer
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
*/
static int
validate_leafref(cxobj *xt,
yang_stmt *ytype,
cbuf *cbret)
{
int retval = -1;
yang_stmt *ypath;
cxobj **xvec = NULL;
cxobj *x;
int i;
size_t xlen = 0;
char *leafrefbody;
char *leafbody;
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
if (netconf_missing_element(cbret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0)
goto done;
goto fail;
}
if (xpath_vec(xt, "%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(cbret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0)
goto done;
goto fail;
}
ok:
retval = 0;
done:
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] cbret Error buffer
* @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,
cbuf *cbret)
{
int retval = -1;
char *node;
yang_stmt *ybaseref; /* This is the type's base reference */
yang_stmt *ybaseid;
char *prefix = NULL;
cbuf *cb = NULL;
/* Get idref value. Then see if this value is derived from ytype.
* Always add default prefix because derived identifiers are stored with
* prefixes in the base identifiers derived-list.
*/
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((node = xml_body(xt)) == NULL)
return 0;
if (strchr(node, ':') == NULL){
prefix = yang_find_myprefix(ys);
cprintf(cb, "%s:%s", prefix, node);
node = cbuf_get(cb);
}
/* This is the type's base reference */
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){
if (netconf_missing_element(cbret, "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(cbret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0)
goto done;
goto fail;
}
/* Here check if node is in the derived node list of the base identity
* The derived node list is a cvec computed XXX
*/
if (cvec_find(yang_cvec_get(ybaseid), node) == NULL){
cbuf_reset(cb);
cprintf(cb, "Identityref validation failed, %s not derived from %s",
node, yang_argument_get(ybaseid));
if (netconf_operation_failed(cbret, "application", cbuf_get(cb)) < 0)
goto done;
goto fail;
}
retval = 1;
done:
if (cb)
cbuf_free(cb);
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] xt XML node to be validated
* @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,
cbuf *cbret)
{
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(cbret, "application", xml_name(xn), NULL) < 0)
goto done;
goto fail;
}
if ((retval = xml_yang_validate_all(h, xn, cbret)) < 1)
goto done; /* error or validation fail */
if ((retval = xml_yang_validate_add(h, xn, cbret)) < 1)
goto done; /* error or validation fail */
if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 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] cbret Error buffer (set w netconf error if retval == 0)
* @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,
cbuf *cbret)
{
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(cbret, "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] cbret Error buffer (set w netconf error if retval == 0)
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
*/
static int
check_mandatory(cxobj *xt,
yang_stmt *yt,
cbuf *cbret)
{
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 = yt->ys_cvec; /* 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(cbret, "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(cbret, "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 (cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-type>application</error-type>"
"<error-tag>data-missing</error-tag>"
"<error-app-tag>missing-choice</error-app-tag>"
#ifdef NYI
// "<error-path></error-path>"
#endif
"<error-info><missing-choice>%s</missing-choice></error-info>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>",
yc->ys_argument) <0)
goto done;
goto fail;
}
break;
default:
break;
} /* switch */
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
static int
check_list_key(cxobj *xt,
yang_stmt *yt,
cbuf *cbret)
{
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 = yt->ys_cvec; /* 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(cbret, "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] cbret Error buffer (set w netconf error if retval == 0)
* @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,
cbuf *cbret)
{
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(cbret, 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] cbret Error buffer (set w netconf error if retval == 0)
* @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,
cbuf *cbret)
{
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(cbret, 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(cbret, 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] cbret Error buffer (set w netconf error if retval == 0)
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret 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,
cbuf *cbret)
{
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, cbret)) < 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, cbret)) < 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, cbret)) < 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, cbret)) < 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, cbret)) < 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] cbret Error buffer (set w netconf error if retval == 0)
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *cbret = cbuf_new();
* if ((ret = xml_yang_validate_add(h, x, cbret)) < 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,
cbuf *cbret)
{
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, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((ret = check_mandatory(xt, yt, cbret)) < 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(yt->ys_cv)) == 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(cbret, "application", yt->ys_argument, "Invalid NULL value") < 0)
goto done;
goto fail;
}
}
else{
if (cv_parse1(body, cv, &reason) != 1){
if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0)
goto done;
goto fail;
}
}
if ((ys_cv_validate(h, cv, yt, &reason)) != 1){
if (netconf_bad_element(cbret, "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, cbret)) < 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
*/
int
xml_yang_validate_list_key_only(clicon_handle h,
cxobj *xt,
cbuf *cbret)
{
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, cbret)) < 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, cbret)) < 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] cbret Error buffer (set w netconf error if retval == 0)
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *cbret = cbuf_new();
* if ((ret = xml_yang_validate_all(x, cbret)) < 0)
* err;
* if (ret == 0)
* fail;
* @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,
cbuf *cbret)
{
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;
/* 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 (netconf_unknown_element(cbret, "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
*/
if ((yc = yang_find(ys, Y_TYPE, NULL)) != NULL){
if (strcmp(yc->ys_argument, "leafref") == 0){
if (validate_leafref(xt, yc, cbret) < 0)
goto done;
}
else if (strcmp(yc->ys_argument, "identityref") == 0){
if (validate_identityref(xt, ys, yc, cbret) < 0)
goto done;
}
}
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, "%s", xpath)) < 0)
goto done;
if (!nr){
ye = yang_find(yc, Y_ERROR_MESSAGE, NULL);
if (netconf_operation_failed(cbret, "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, "%s", xpath)) < 0)
goto done;
if (!nr){
if (netconf_operation_failed(cbret, "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, cbret)) < 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, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Translate a single xml node to a cligen variable vector. Note not recursive
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
*/
int
xml_yang_validate_all_top(clicon_handle h,
cxobj *xt,
cbuf *cbret)
{
int ret;
cxobj *x;
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, cbret)) < 1)
return ret;
}
if ((ret = check_list_unique_minmax(xt, cbret)) < 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 = ys->ys_cv) != 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 : recurse; get next a,b
* a < b : add a in x0, get next a
* a > b : add b in x1, get next b
*/
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.
* Bot xml trees should be freed with xml_free()
*/
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,
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
static int
yang2api_path_fmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_stmt *yp; /* parent */
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
if ((yp = ys->ys_parent) == NULL){
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument);
goto done;
}
if (yp != NULL && /* XXX rm */
yp->ys_keyword != Y_MODULE &&
yp->ys_keyword != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
goto done;
if (yp->ys_keyword != Y_CHOICE && yp->ys_keyword != 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 (ys->ys_keyword == Y_LEAF && yp &&
yp->ys_keyword == Y_LIST &&
yang_key_match(yp, ys->ys_argument) == 1)
;
else
#endif
cprintf(cb, "/");
}
}
else /* top symbol - mark with name prefix */
cprintf(cb, "/%s:", yp->ys_argument);
if (inclkey){
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "%s", ys->ys_argument);
}
else{
if (ys->ys_keyword == Y_LEAF && yp &&
yp->ys_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */
}
else
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "%s", ys->ys_argument);
}
switch (ys->ys_keyword){
case Y_LIST:
cvk = ys->ys_cvec; /* 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
* @param[out] yang_arg yang-stmt argument name. 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"
*
* "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 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;
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;
assert(y->ys_cv);
if (!cv_flag(y->ys_cv, V_UNSET)){ /* Default value exists */
if (!xml_find(xt, y->ys_argument)){
#ifdef USE_XML_INSERT
if ((xc = xml_new(y->ys_argument, NULL, y)) == NULL)
goto done;
#else
if ((xc = xml_new(y->ys_argument, xt, y)) == NULL)
goto done;
#endif
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(y->ys_cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
added++;
#ifdef USE_XML_INSERT
if (xml_insert(xt, xc) < 0)
goto done;
#endif
}
}
}
}
#ifndef USE_XML_INSERT
if (added)
xml_sort(xt, NULL);
#endif
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){
/* kludge rpc -> input XXX THIS HIDES AN ERROR IN xml_spec_populate */
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
*/
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;
yspec = (yang_stmt*)arg;
xp = xml_parent(x);
name = xml_name(x);
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)
y = yang_find_schemanode(ymod, name);
}
if (y)
xml_spec_set(x, y);
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path in cvv form to xml xpath
* eg a/b=c -> a/[b=c]
* eg example:a/b -> ex:a/b
* @param[in] yspec Yang spec
* @param[in] cvv api-path as cvec
* @param[in] offset Offset of cvec, where api-path starts
* @param[out] xpath The xpath as cbuf variable string, must be initializeed
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called
* @retval -1 Fatal error, clicon_err called
*
* @note both retval 0 and -1 set clicon_err, but the later is fatal
* @note Not proper namespace translation from api-path 2 xpath
* 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"
* @code
* cbuf *xpath = cbuf_new();
* cvec *cvv = NULL;
* if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0)
* err;
* if ((ret = api_path2xpath(yspec, cvv, 0, cxpath)) < 0)
* err;
* if (ret == 0){
* ... access error string in clicon_err_reason
* clicon_err_reset();
* return;
* }
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @see api_path2xml For api-path to xml tree
*/
int
api_path2xpath(yang_stmt *yspec,
cvec *cvv,
int offset,
cbuf *xpath)
{
int retval = -1;
int i;
cg_var *cv;
char *nodeid;
char *prefix = NULL;
char *name = NULL;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
yang_stmt *ymod;
char *val;
char *v;
cg_var *cvi;
for (i=offset; i<cvec_len(cvv); i++){
cv = cvec_i(cvv, i);
nodeid = cv_name_get(cv);
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
clicon_debug(1, "%s [%d] cvname:%s", __FUNCTION__, i, name);
if (i == offset){ /* top-node */
if (prefix == NULL){
clicon_err(OE_XML, EINVAL, "'%s': Expected prefix:name", nodeid);
goto fail;
}
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
clicon_err(OE_YANG, ENOENT, "No such yang module: %s", prefix);
goto fail;
}
y = yang_find_datanode(ymod, name);
}
else
y = yang_find_datanode(y, name);
if (y == NULL){
clicon_err(OE_YANG, errno, "Unknown element: '%s'", name);
goto fail;
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
v = val;
/* XXX sync with yang */
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else
cprintf(xpath, "%s%s", (i==offset?"":"/"), name);
if (prefix){
free(prefix);
prefix = NULL;
}
if (name){
free(name);
name = NULL;
}
} /* for */
retval = 1; /* OK */
done:
if (prefix)
free(prefix);
if (name)
free(name);
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
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called
* @retval -1 Fatal error, clicon_err called
*
* @note both retval 0 and -1 set clicon_err, but the later is fatal
* @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)
{
int retval = -1;
int j;
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 *val2;
char **valvec = NULL;
int nvalvec;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ymod;
yang_stmt *ykey;
char *namespace = 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 */
/* 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){
clicon_err(OE_XML, EINVAL, "api-path element '%s', expected prefix:name", nodeid);
goto fail;
}
if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix);
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){
clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name);
goto fail;
}
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 = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
if (valvec){
free(valvec);
valvec = NULL;
}
if (restval==NULL){
if (strict){
clicon_err(OE_XML, 0, "malformed key, expected '=restval'");
goto fail;
}
}
else{
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (nvalvec > cvec_len(cvk)){
clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name);
goto fail;
}
}
cvi = NULL;
/* create list object */
if ((x = xml_new(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
j = 0;
/* Create keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
yang_argument_get(y), keyname);
goto done;
}
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);
val2 = valvec?valvec[j++]:NULL;
if (xml_value_set(xb, val2) <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)) < 1)
goto done;
ok:
retval = 1; /* OK */
done:
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
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, clicon_err called
* @retval -1 Fatal error, clicon_err called
* @note both retval 0 and -1 set clicon_err, but the later is fatal
* @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)
{
int retval = -1;
char **vec = NULL;
int nvec;
cxobj *xroot;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
if (*api_path!='/'){
clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')",
api_path);
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){
clicon_err(OE_XML, EINVAL, "Malformed api-path: %s", api_path);
goto fail;
}
nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict,
xbotp, ybotp)) < 1)
goto done;
xml_yang_root(*xbotp, &xroot);
if (xmlns_assign(xroot) < 0)
goto done;
// ok:
retval = 1;
done:
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);
if (keyword == Y_LEAF_LIST){
if ((b = xml_body(x)) != NULL)
cprintf(cb, "[.=\"%s\"]", b);
else
cprintf(cb, "[.=\"\"]");
} else if (keyword == 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:"");
}
}
}
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 0 /* debug test */
{
cxobj *xt = x;
cxobj *xcp;
cxobj *x2;
while (xml_parent(xt) != NULL &&
xml_spec(xt) != NULL)
xt = xml_parent(xt);
xcp = xml_parent(xt);
xml_parent_set(xt, NULL);
x2 = xpath_first(xt, "%s", xpath); /* +1: skip first / */
xml_parent_set(xt, xcp);
assert(x2 && x==x2);
if (x==x2)
clicon_debug(1, "%s %s match", __FUNCTION__, xpath);
else
clicon_debug(1, "%s %s no match", __FUNCTION__, xpath);
}
#endif
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;
}
/*! 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;
}
/*! 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,
yang_stmt *y0,
cxobj *x0p,
cxobj *x1,
char **reason)
{
int retval = -1;
char *x1name;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
cxobj *x0a; /* x0 xmlns attribute */
cxobj *x1a; /* x1 xmlns attribute */
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 LEAF|LEAF_LIST */
else { /* eg Y_CONTAINER, Y_LIST */
if (x0==NULL){
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
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;
}
} /* else Y_CONTAINER */
assert(x0);
/* Copy xmlns attributes (if it does not already exist) */
if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != NULL)
if (xml_find_type(x0, NULL, "xmlns", CX_ATTR)==NULL){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
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("No namespace in XML tree found")) == 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;
}