clixon/lib/src/clixon_xml_map.c
2018-07-18 09:51:33 +02:00

2008 lines
54 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 |
* +---------+
*/
#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_yang_type.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xsl.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_xml_sort.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 *c;
if (xml_type(x) != CX_ELMNT)
return 0;
if (xml_child_nr(x) != 1)
return 0;
c = xml_child_i(x, 0);
return (xml_child_nr(c) == 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 *xe = NULL;
int children=0;
char *term = NULL;
int retval = -1;
xe = NULL; /* count children */
while ((xe = xml_child_each(x, xe, -1)) != NULL)
children++;
if (!children){
if (xml_type(x) == CX_BODY){
term = xml_value(x);
}
else{
fprintf(f, "%*s", 4*level, "");
term = xml_name(x);
}
fprintf(f, "%s;\n", term);
retval = 0;
goto done;
}
fprintf(f, "%*s", 4*level, "");
fprintf(f, "%s ", xml_name(x));
if (!tleaf(x))
fprintf(f, "{\n");
xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){
if (xml2txt(f, xe, level+1) < 0)
break;
}
if (!tleaf(x))
fprintf(f, "%*s}\n", 4*level, "");
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;
ys = xml_spec(x);
if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){
if (prepend0)
fprintf(f, "%s", prepend0);
body = xml_body(x);
if (gt == GT_ALL || gt == GT_VARS)
fprintf(f, "%s ", xml_name(x));
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 (ys->ys_keyword == 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((yang_node*)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 (ys->ys_keyword == Y_LIST){
if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0)
goto done;
if (match)
continue;
}
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
*/
static int
validate_leafref(cxobj *xt,
yang_stmt *ytype)
{
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)
return 0;
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
goto done;
}
if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 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){
clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s",
leafrefbody);
goto done;
}
retval = 0;
done:
if (xvec)
free(xvec);
return retval;
}
/*! 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
* @see ys_populate_identity where the derived types are set
* @see RFC7950 Sec 9.10.2:
*/
static int
validate_identityref(cxobj *xt,
yang_stmt *ys,
yang_stmt *ytype)
{
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 ((node = xml_body(xt)) == NULL)
return 0;
if (strchr(node, ':') == NULL){
prefix = yang_find_myprefix(ys);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "%s:%s", prefix, node);
node = cbuf_get(cb);
}
/* This is the type's base reference */
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){
clicon_err(OE_DB, 0, "Identityref validation failed, no base");
goto done;
}
/* This is the actual base identity */
if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){
clicon_err(OE_DB, 0, "Identityref validation failed, no base identity");
goto done;
}
/* Here check if node is in the derived node list of the base identity */
if (cvec_find(ybaseid->ys_cvec, node) == NULL){
clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument);
goto done;
}
retval = 0;
done:
return retval;
}
/*! 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
* @retval 0 Valid OK
* @retval -1 Validation failed
* @see xml_yang_validate_all
*/
int
xml_yang_validate_add(cxobj *xt,
void *arg)
{
int retval = -1;
cg_var *cv = NULL;
char *reason = NULL;
yang_stmt *yc;
int i;
yang_stmt *ys;
char *body;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){
switch (ys->ys_keyword){
case Y_INPUT:
case Y_LIST:
/* fall thru */
case Y_CONTAINER:
for (i=0; i<ys->ys_len; i++){
yc = ys->ys_stmt[i];
if (yc->ys_keyword != Y_LEAF)
continue;
if (yang_config(yc)==0)
continue;
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
clicon_err(OE_CFG, 0,"Missing mandatory variable: %s",
yc->ys_argument);
goto done;
}
}
break;
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
/* validate value against ranges, etc */
if ((cv = cv_dup(ys->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){
if (cv_parse(body, cv) <0){
clicon_err(OE_UNIX, errno, "cv_parse");
goto done;
}
if ((ys_cv_validate(cv, ys, &reason)) != 1){
clicon_err(OE_DB, 0,
"validation of %s failed %s",
ys->ys_argument, reason?reason:"");
if (reason)
free(reason);
goto done;
}
}
break;
default:
break;
}
}
retval = 0;
done:
if (cv)
cv_free(cv);
return retval;
}
/*! 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
* @retval 0 Valid OK
* @retval -1 Validation failed
* @see xml_yang_validate_add
*/
int
xml_yang_validate_all(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys; /* yang node */
yang_stmt *yc; /* yang child */
yang_stmt *ye; /* yang must error-message */
char *xpath;
int b;
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((ys = xml_spec(xt)) != NULL &&
yang_config(ys) != 0){
/* Node-specific validation */
switch (ys->ys_keyword){
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((yang_node*)ys, Y_TYPE, NULL)) != NULL){
if (strcmp(yc->ys_argument, "leafref") == 0){
if (validate_leafref(xt, yc) < 0)
goto done;
}
else if (strcmp(yc->ys_argument, "identityref") == 0){
if (validate_identityref(xt, ys, yc) < 0)
goto done;
}
}
break;
default:
break;
}
/* must sub-node RFC 7950 Sec 7.5.3. Can be several. */
yc = NULL;
while ((yc = yn_each((yang_node*)ys, yc)) != NULL) {
if (yc->ys_keyword != Y_MUST)
continue;
xpath = yc->ys_argument; /* "must" has xpath argument */
if ((b = xpath_vec_bool(xt, "%s", xpath)) < 0)
goto done;
if (!b){
if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL)
clicon_err(OE_DB, 0, "%s", ye->ys_argument);
else
clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
goto done;
}
}
/* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
if ((yc = yang_find((yang_node*)ys, Y_WHEN, NULL)) != NULL){
xpath = yc->ys_argument; /* "when" has xpath argument */
if ((b = xpath_vec_bool(xt, "%s", xpath)) < 0)
goto done;
if (!b){
clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
goto done;
}
}
}
retval = 0;
done:
return retval;
}
/*! 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((yang_node*)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;
}
/*! Find next yang node, either start from yang_spec or some yang-node
* @param[in] y Node spec or sny yang-node
* @param[in] name Name of childnode to find
* @retval ys yang statement
* @retval NULL Error: no node found
*/
static yang_stmt *
yang_next(yang_node *y,
char *name)
{
yang_stmt *ys;
if (y->yn_keyword == Y_SPEC)
ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE);
else
ys = yang_find_datanode(y, name);
if (ys == NULL)
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
return ys;
}
/*! Recursive help function to compute differences between two xml trees
* @param[in] x1 First XML tree
* @param[in] x2 Second XML tree
* @param[out] x1vec Pointervector to XML nodes existing in only first tree
* @param[out] x1veclen Length of first vector
* @param[out] x2vec Pointervector to XML nodes existing in only second tree
* @param[out] x2veclen Length of x2vec vector
* @param[out] changed_x1 Pointervector to XML nodes changed orig value
* @param[out] changed_x2 Pointervector to XML nodes changed wanted value
* @param[out] changedlen Length of changed vector
*/
static int
xml_diff1(yang_stmt *ys,
cxobj *x1,
cxobj *x2,
cxobj ***x1vec,
size_t *x1veclen,
cxobj ***x2vec,
size_t *x2veclen,
cxobj ***changed_x1,
cxobj ***changed_x2,
size_t *changedlen)
{
int retval = -1;
cxobj *x1c = NULL; /* x1 child */
cxobj *x2c = NULL; /* x2 child */
yang_stmt *yc;
char *b1;
char *b2;
clicon_debug(2, "%s: %s", __FUNCTION__, ys->ys_argument?ys->ys_argument:"yspec");
/* Check nodes present in x1 and x2 + nodes only in x1
* Loop over x1
* XXX: room for improvement. Compare with match_base_child()
*/
x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){
if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL)
goto done;
if (match_base_child(x2, x1c, &x2c, yc) < 0)
goto done;
if (x2c == NULL){
if (cxvec_append(x1c, x1vec, x1veclen) < 0)
goto done;
}
else{
if (yc->ys_keyword == Y_LEAF){
if ((b1 = xml_body(x1c)) == NULL) /* empty type */
break;
if ((b2 = xml_body(x2c)) == NULL) /* empty type */
break;
if (strcmp(b1, b2)){
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
goto done;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x2c, changed_x2, changedlen) < 0)
goto done;
}
}
if (xml_diff1(yc, x1c, x2c,
x1vec, x1veclen,
x2vec, x2veclen,
changed_x1, changed_x2, changedlen)< 0)
goto done;
}
} /* while x1 */
/* Check nodes present only in x2
* Loop over x2
*/
x2c = NULL;
while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){
if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL)
goto done;
if (match_base_child(x1, x2c, &x1c, yc) < 0)
goto done;
if (x1c == NULL)
if (cxvec_append(x2c, x2vec, x2veclen) < 0)
goto done;
} /* while x1 */
retval = 0;
done:
return retval;
}
/*! Compute differences between two xml trees
* @param[in] yspec Yang specification
* @param[in] x1 First XML tree
* @param[in] x2 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] changed1 Pointervector to XML nodes changed orig value
* @param[out] changed2 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_spec *yspec,
cxobj *x1,
cxobj *x2,
cxobj ***first,
size_t *firstlen,
cxobj ***second,
size_t *secondlen,
cxobj ***changed1,
cxobj ***changed2,
size_t *changedlen)
{
int retval = -1;
*firstlen = 0;
*secondlen = 0;
*changedlen = 0;
if (x1 == NULL && x2 == NULL)
return 0;
if (x2 == NULL){
if (cxvec_append(x1, first, firstlen) < 0)
goto done;
goto ok;
}
if (x1 == NULL){
if (cxvec_append(x1, second, secondlen) < 0)
goto done;
goto ok;
}
if (xml_diff1((yang_stmt*)yspec, x1, x2,
first, firstlen,
second, secondlen,
changed1, changed2, 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: /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,
*/
static int
yang2api_path_fmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_node *yp; /* parent */
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
yp = ys->ys_parent;
if (yp != NULL &&
yp->yn_keyword != Y_MODULE &&
yp->yn_keyword != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0)
goto done;
}
if (inclkey){
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "/%s", ys->ys_argument);
}
else{
#if 1
if (ys->ys_keyword == Y_LEAF && yp &&
yp->yn_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */
}
else
#endif
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.
*/
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
*/
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(str, &strenc) < 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() or xmldb_get_vec
* @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"
*/
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,
#ifdef COMPAT_XSL
"[%s=%s]",
#else
"[%s='%s']",
#endif
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((yang_node*)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((yang_node*)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, NULL);
* @endcode
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
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 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
*/
int
xml_default(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *ys;
yang_stmt *y;
int i;
cxobj *xc;
cxobj *xb;
char *str;
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){
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)){
if ((xc = xml_new(y->ys_argument, xt, y)) == NULL)
goto done;
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);
}
}
}
}
xml_sort(xt, NULL);
retval = 0;
done:
return retval;
}
/*! Order XML children according to YANG
* @param[in] xt XML top of tree
*/
int
xml_order(cxobj *xt,
void *arg)
{
int retval = -1;
yang_stmt *y;
yang_stmt *yc;
int i;
int j0;
int j;
cxobj *xc;
cxobj *xj;
char *yname; /* yang child name */
char *xname; /* xml child name */
if ((y = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
j0 = 0;
/* Go through yang node's children and ensure that the
* xml children follow this order.
* Do not order the list or leaf-list children (have same name).
*/
for (i=0; i<y->ys_len; i++){
yc = y->ys_stmt[i];
if (!yang_datanode(yc))
continue;
yname = yc->ys_argument;
/* First go thru xml children with same name in rest of list */
for (; j0<xml_child_nr(xt); j0++){
xc = xml_child_i(xt, j0);
if (xml_type(xc) != CX_ELMNT)
continue;
xname = xml_name(xc);
if (strcmp(xname, yname))
break;
}
/* Now we have children not with same name */
for (j=j0; j<xml_child_nr(xt); j++){
xc = xml_child_i(xt, j);
if (xml_type(xc) != CX_ELMNT)
continue;
xname = xml_name(xc);
if (strcmp(xname, yname))
continue;
/* reorder */
xj = xml_child_i(xt, j0);
xml_child_i_set(xt, j0, xc);
xml_child_i_set(xt, j, xj);
j0++;
}
}
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 backpoint to XML node
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @note This may be unnecessary if yspec us 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_spec *yspec = (yang_spec*)arg;
yang_stmt *y=NULL; /* yang node */
if (xml_child_spec(xml_name(x), xml_parent(x), yspec, &y) < 0)
goto done;
#if 0
if ((xp = xml_parent(x)) != NULL &&
(yp = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
#endif
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]
* @param[in] yspec Yang spec
* @param[in] pcvec api-path as cvec
* @param[in] pi Offset of cvec, where api-path starts
* @param[out] path The xpath as cbuf variable string, must be initializeed
* The api-path has some wierd encoding, use api_path2xpath() if you are
* confused.
* 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();
* if (api_path2xpath_cvv(yspec, cvv, i, xpath)
* err;
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
* @see api_path2xpath for string api-path argument
*/
int
api_path2xpath_cvv(yang_spec *yspec,
cvec *cvv,
int offset,
cbuf *xpath)
{
int retval = -1;
int i;
cg_var *cv;
char *name;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
char *val;
char *v;
cg_var *cvi;
for (i=offset; i<cvec_len(cvv); i++){
cv = cvec_i(cvv, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == offset){
if ((y = yang_find_topnode(yspec, name, YC_DATANODE)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
assert(y!=NULL);
if ((y = yang_find_datanode((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* 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,
#ifdef COMPAT_XSL
"[%s=%s]",
#else
"[%s='%s']",
#endif
cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else{
cprintf(xpath, "%s%s", (i==offset?"":"/"), name);
}
}
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path to xml xpath
* eg a/b=c -> a/[b=c]
* @param[in] yspec Yang spec
* @param[in] str API-path as string
* @param[out] path The xpath as cbuf variable string, must be initializeed
* @code
* cbuf *xpath = cbuf_new();
* if (api_path2xpath(yspec, "a/b=c", xpath)
* err;
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath)
* @endcode
*/
int
api_path2xpath(yang_spec *yspec,
char *api_path,
cbuf *xpath)
{
int retval = -1;
cvec *api_path_cvv = NULL;
/* rest url eg /album=ricky/foo */
if (str2cvec(api_path, '/', '=', &api_path_cvv) < 0)
goto done;
if (api_path2xpath_cvv(yspec, api_path_cvv, 0, xpath) < 0)
goto done;
retval = 0;
done:
if (api_path_cvv)
cvec_free(api_path_cvv);
return retval;
}
/*! 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
* @see api_path2xml
*/
static int
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_node *y0,
yang_class nodeclass,
cxobj **xpathp,
yang_node **ypathp)
{
int retval = -1;
int j;
char *name;
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;
char *local;
if ((name = vec[0]) == NULL){
if (xpathp)
*xpathp = x0;
if (ypathp)
*ypathp = y0;
return 0;
} /* E.g "x=1,2" -> name:x restval=1,2 */
/* restval is RFC 3896 encoded */
if ((restval_enc = index(name, '=')) != NULL){
*restval_enc = '\0';
restval_enc++;
if (uri_percent_decode(restval_enc, &restval) < 0)
goto done;
}
/* Split into prefix and localname, ignore prefix for now */
if ((local = index(name, ':')) != NULL){
*local = '\0';
local++;
name = local;
}
if (y0->yn_keyword == Y_SPEC){ /* top-node */
y = yang_find_topnode((yang_spec*)y0, name, nodeclass);
}
else {
y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name):
yang_find_datanode((yang_node*)y0, name);
}
if (y == NULL){
clicon_err(OE_YANG, errno, "No yang node found: %s", name);
goto done;
}
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){
// XXX patch to allow for lists without restval to be backward compat
// clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
// goto done;
}
else{
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if (nvalvec > cvec_len(cvk)){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
}
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 ((xn = xml_new(keyname, x, NULL)) == 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_new(name, x0, y)) == NULL)
goto done;
xml_type_set(x, CX_ELMNT);
break;
}
if (api_path2xml_vec(vec+1, nvec-1,
x, (yang_node*)y,
nodeclass,
xpathp, ypathp) < 0)
goto done;
retval = 0;
done:
if (restval)
free(restval);
if (valvec)
free(valvec);
return retval;
}
/*! Create xml tree from api-path
* @param[in] api_path 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
* @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
* @see api_path2xml_vec
*/
int
api_path2xml(char *api_path,
yang_spec *yspec,
cxobj *xtop,
yang_class nodeclass,
cxobj **xbotp,
yang_node **ybotp)
{
int retval = -1;
char **vec = NULL;
int nvec;
if (*api_path!='/'){
clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path);
goto ok;
}
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, 0, "Malformed key: %s", api_path);
goto done;
}
nvec--; /* NULL-terminated */
if (api_path2xml_vec(vec+1, nvec,
xtop, (yang_node*)yspec, nodeclass,
xbotp, ybotp) < 0)
goto done;
ok:
retval = 0;
done:
if (vec)
free(vec);
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
* @see put in clixon_keyvalue.c
*/
static int
xml_merge1(cxobj *x0,
yang_node *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 */
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->yn_keyword == Y_LEAF_LIST || y0->yn_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, &x0c, yc) < 0)
goto done;
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
goto done;
if (*reason != NULL)
goto ok;
}
} /* 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_spec *yspec,
char **reason)
{
int retval = -1;
char *x1cname; /* child name */
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc;
cbuf *cbr = NULL; /* Reason buffer */
/* 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_topnode(yspec, x1cname, YC_DATANODE)) == 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, &x0c, yc) < 0)
goto done;
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
goto done;
if (*reason != NULL)
break;
}
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_spec *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((yang_node *)ys, Y_TYPE, NULL)) == NULL)
goto done;
if (yang_type_resolve(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((yang_node *)yrestype, Y_ENUM, xml_body(node))) == NULL)
goto done;
/* Should assign value if yval not found */
if ((yval = yang_find((yang_node *)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;
}
/*
* Turn this on for uni-test programs
* Usage: clixon_string join
* Example compile:
gcc -g -o clixon_xml_map -I. -I../clixon ./clixon_xml_map.c -lclixon -lcligen
* Example run:
/interfaces/interface=%s/name --> interfaces/interface/name
/interfaces/interface=%s/ipv4/address=%s e --> /interfaces/interface=e/ipv4/address
/interfaces/interface=%s,%s/ipv4/address=%s e f --> /interfaces/interface=e,f/ipv4/address
/interfaces/interface=%s/ipv4/address=%s,%s e f --> /interfaces/interface=e/ipv4/address=f
/interfaces/interface=%s/ipv4/address=%s/prefix-length eth 1.2.3.4 -->
/interfaces/interface=eth/ipv4/address=1.2.3.4/prefix-length
*/
#if 0 /* Test program */
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s <api_path_fmt> <cv0>, <cv1>,...\n", argv0);
exit(0);
}
int
main(int argc, char **argv)
{
int nvec;
char **vec;
char *str0;
char *str1;
int i;
char *api_path_fmt;
cg_var *cv;
cvec *cvv;
char *api_path=NULL;
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if (argc < 2){
usage(argv[0]);
return 0;
}
api_path_fmt = argv[1];
if ((cvv = cvec_new(0)) == NULL){
perror("cvec_new");
return -1;
}
cv = cv_new(CGV_STRING);
cv_string_set(cv, "CLI base command");
cvec_append_var(cvv, cv);
for (i=2; i<argc; i++){
cv = cv_new(CGV_STRING);
if (cv_parse(argv[i], cv) < 0){
perror("cv_parse");
return -1;
}
cvec_append_var(cvv, cv);
}
if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0)
return -1;
printf("%s\n", api_path);
return 0;
}
#endif /* Test program */