Optimized search performance for large lists by sorting and binary search

This commit is contained in:
Olof hagsand 2017-12-27 11:34:47 +01:00
parent b743b0a080
commit 4b92dbdc10
28 changed files with 1405 additions and 701 deletions

View file

@ -64,7 +64,7 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$
SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \
clixon_string.c clixon_handle.c \
clixon_xml.c clixon_xml_map.c clixon_file.c \
clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \
clixon_json.c clixon_yang.c clixon_yang_type.c \
clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.c \

View file

@ -382,6 +382,11 @@ clicon_options_main(clicon_handle h)
/* Read configfile */
if (clicon_option_readfile_xml(copt, configfile, yspec) < 0)
goto done;
/* Specific option handling */
if (clicon_option_bool(h, "CLICON_XML_SORT") == 1)
xml_child_sort = 1;
else
xml_child_sort = 0;
}
else {
#ifdef CONFIG_COMPAT
@ -409,6 +414,7 @@ clicon_options_main(clicon_handle h)
/*! Check if a clicon option has a value
* @param[in] h clicon_handle
* @param[in] name option name
* @retval
*/
int
clicon_option_exists(clicon_handle h,

View file

@ -82,21 +82,37 @@
/*! xml tree node, with name, type, parent, children, etc
* Note that this is a private type not visible from externally, use
* access functions.
* A word on ordering of x_children:
* If there is no yang specification, xml children are ordered as they are entered.
* If there is a yang specification (and the appropriate functions are called) the
* xml children are ordered as follows:
* 1) After yang specification order.
* 2) list and leaf-list are sorted alphabetically unless ordered-by user.
* Example:
* container c{
* leaf a;
* leaf-list x;
* }
* then regardless in which order the xml is entered, it will be sorted as follows:
* <c>
* <a/>
* <x>a</<x>
* <x>b</<x>
* </c>
*/
struct xml{
char *x_name; /* name of node */
char *x_namespace; /* namespace, if any */
struct xml *x_up; /* parent node in hierarchy if any */
struct xml **x_childvec; /* vector of children nodes */
int x_childvec_len; /* length of vector */
int x_childvec_len;/* length of vector */
enum cxobj_type x_type; /* type of node: element, attribute, body */
char *x_value; /* attribute and body nodes have values */
int _x_vector_i; /* internal use: xml_child_each */
int x_flags; /* Flags according to XML_FLAG_* above */
yang_stmt *x_spec; /* Pointer to specification, eg yang, by
reference, dont free */
cg_var *x_cv; /* If body this contains the typed value */
clicon_hash_t *x_hash; /* Hash of children */
cg_var *x_cv; /* If body this contains the typed value */
};
/* Mapping between xml type <--> string */
@ -108,10 +124,6 @@ static const map_str2int xsmap[] = {
{NULL, -1}
};
/* Hash for XML trees list entries
* Experimental XXX DOES NOT WORK
*/
int xml_child_hash = 0;
/*! Translate from xml type in enum form to string keyword
* @param[in] type Xml type
@ -475,11 +487,11 @@ xml_child_append(cxobj *x,
return 0;
}
/*! Set a a childvec to a speciufic size, fill with children after
/*! Set a a childvec to a specific size, fill with children after
* @code
* xml_childvec_set(x, 2);
* xml_child_i(x, 0) = xc0;
* xml_child_i(x, 1) = xc1;
* xml_child_i_set(x, 0, xc0)
* xml_child_i_set(x, 1, xc1);
* @endcode
*/
int
@ -502,9 +514,9 @@ xml_childvec_get(cxobj *x)
/*! Create new xml node given a name and parent. Free with xml_free().
*
* @param[in] name Name of new
* @param[in] xp The parent where the new xml node should be inserted
* @param[in] spec Yang stmt or NULL.
* @param[in] name Name of XML node
* @param[in] xp The parent where the new xml node will be appended
* @param[in] spec Yang statement of this XML or NULL.
* @retval xml Created xml object if successful. Free with xml_free()
* @retval NULL Error and clicon_err() called
* @code
@ -536,8 +548,6 @@ xml_new(char *name,
if (xp && xml_child_append(xp, x) < 0)
return NULL;
x->x_spec = spec; /* Can be NULL */
if (xml_child_hash && spec && xml_hash_add(x) < 0)
return NULL;
return x;
}
@ -655,8 +665,6 @@ xml_purge(cxobj *xc)
int i;
cxobj *xp;
if (xml_child_hash)
xml_hash_rm_entry(xc);
if ((xp = xml_parent(xc)) != NULL){
/* Find child order i in parent*/
for (i=0; i<xml_child_nr(xp); i++)
@ -928,8 +936,6 @@ xml_free(cxobj *x)
x->x_childvec[i] = NULL;
}
}
if (x->x_hash)
hash_free(x->x_hash);
if (x->x_childvec)
free(x->x_childvec);
free(x);
@ -1830,276 +1836,93 @@ xml_operation2str(enum operation_type op)
}
}
/*! Given an XML object and a child name, return yang stmt of child
* If no xml parent, find root yang stmt matching name
* @param[in] name Name of child
* @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
/*! Help function to qsort for sorting entries in xml child vector
* @param[in] arg1
* @param[in] arg2
* @retval 0 If equal
* @retval <0 if arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2
* @note must be in clixon_xml.c since it uses internal (hidden) struct xml
*/
int
xml_child_spec(char *name,
cxobj *xp,
yang_spec *yspec,
yang_stmt **yresult)
{
yang_stmt *y; /* result yang node */
yang_stmt *yparent; /* parent yang */
if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yparent, name);
else if (yspec)
y = yang_find_topnode(yspec, name, 0); /* still NULL for config */
else
y = NULL;
*yresult = y;
return 0;
}
/*! Return yang hash
* Not necessarily set. Either has not been set yet (by xml_spec_set( or anyxml.
*/
clicon_hash_t *
xml_hash(cxobj *x)
{
return x->x_hash;
}
static int
xml_hash_init(cxobj *x)
xml_cmp(const void* arg1,
const void* arg2)
{
if ((x->x_hash = hash_init()) < 0)
return -1;
return 0;
}
/*! XML remove hash only. Not in parent.
* @param[in] x XML object
* eg same as xml_hash_op(x, -1)
*/
int
xml_hash_rm_only(cxobj *x)
{
if (x && x->x_hash){
hash_free(x->x_hash);
x->x_hash = NULL;
}
return 0;
}
/* Compute hash key for xml entry
* @param[in] x
* @param[in] y
* @param[out] key
* key: yangtype+x1name
* LEAFLIST: b0
* LIST: b2vec+b0 -> x0c
*/
int
xml_hash_key(cxobj *x,
yang_stmt *y,
cbuf *key)
{
int retval = -1;
yang_stmt *ykey;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
char *b;
char *str;
switch (y->ys_keyword){
case Y_CONTAINER: str = "c"; break;
case Y_LEAF: str = "e"; break;
case Y_LEAF_LIST: str = "l"; break;
case Y_LIST: str = "i"; break;
default:
str = "xx"; break;
break;
}
cprintf(key, "%s%s", str, xml_name(x));
switch (y->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
if ((b = xml_body(x)) == NULL){
cbuf_reset(key);
goto ok;
}
cprintf(key, "%s", xml_body(x));
break;
case Y_LIST: /* Match with key values */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
keyname = cv_string_get(cvi);
if ((b = xml_find_body(x, keyname)) == NULL){
cbuf_reset(key);
goto ok;
}
cprintf(key, "/%s", b);
}
break;
default:
break;
}
ok:
retval = 0;
done:
if (cvk)
cvec_free(cvk);
return retval;
}
/*! XML hash add. Create hash and add key/value to parent
*
* @param[in] arg add flag. If 1, else if 0 remove.
* Typically called for a whole tree.
*/
int
xml_hash_op(cxobj *x,
void *arg)
{
int add = (intptr_t)arg;
#if 1
if (add)
return xml_hash_add(x);
else
return xml_hash_rm_entry(x);
#else
struct xml *x1 = *(struct xml**)arg1;
struct xml *x2 = *(struct xml**)arg2;
yang_stmt *y1;
yang_stmt *y2;
int yi1;
int yi2;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
int equal = 0;
char *b1;
char *b2;
char *keyname;
int retval = -1;
cxobj *xp;
clicon_hash_t *ph;
yang_stmt *y;
cbuf *key = NULL; /* cligen buffer hash key */
if (xml_hash(x)==NULL){
if (add)
xml_hash_init(x);
if (x1 == NULL){
if (x2 == NULL)
return 0;
else
return -1;
}
else if (!add)
xml_hash_rm_only(x);
if ((xp = xml_parent(x)) == NULL)
goto ok;
if ((ph = xml_hash(xp))==NULL)
goto ok;
if ((y = xml_spec(x)) == NULL)
goto ok;
if ((key = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
else if (x2 == NULL)
return 1;
y1 = xml_spec(x1);
y2 = xml_spec(x2);
if (y1==NULL || y2==NULL)
return 0; /* just ignore */
if (y1 != y2){
yi1 = yang_order(y1);
yi2 = yang_order(y2);
if ((equal = yi1-yi2) != 0)
return equal;
}
if (xml_hash_key(x, y, key) < 0)
goto done;
if (cbuf_len(key)){
// fprintf(stderr, "%s add %s = 0x%x\n", __FUNCTION__, cbuf_get(key), (unsigned int)x);
if (add){
if (hash_add(ph, cbuf_get(key), &x, sizeof(x)) == NULL)
/* Now y1=y2, same Yang spec, can only be list or leaf-list,
* sort according to key
*/
if (yang_find((yang_node*)y1, Y_ORDERED_BY, "user") != NULL)
return 0; /* Ordered by user: maintain existing order */
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
equal = strcmp(xml_body(x1), xml_body(x2));
break;
case Y_LIST: /* Match with key values
* Use Y_LIST cache (see struct yang_stmt)
*/
cvk = y1->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
b1 = xml_find_body(x1, keyname);
b2 = xml_find_body(x2, keyname);
if ((equal = strcmp(b1,b2)) != 0)
goto done;
}
else
if (hash_del(ph, cbuf_get(key)) < 0)
goto done;
equal = 0;
break;
default:
break;
}
ok:
retval = 0;
done:
if (key)
cbuf_free(key);
return retval;
#endif
return equal;
}
/*! XML hash add. Create hash and add key/value to parent
*
* @param[in] x XML object
* eg same as xml_hash_op(x, 1)
/*! Sort children of an XML node
* Assume populated by yang spec.
* @param[in] x0 XML node
* @param[in] arg Dummy so it can be called by xml_apply()
* @note must be in clixon_xml.c since it uses internal (hidden) struct xml
*/
int
xml_hash_add(cxobj *x)
xml_sort(cxobj *x,
void *arg)
{
int retval = -1;
cxobj *xp;
clicon_hash_t *ph;
yang_stmt *y;
yang_stmt *yp;
cbuf *key = NULL; /* cligen buffer hash key */
if ((ph = xml_hash(x))==NULL){
xml_hash_init(x);
ph = xml_hash(x);
}
if ((xp = xml_parent(x)) == NULL)
goto ok;
yp = xml_spec(xp);
if (yp && yp->ys_keyword != Y_LIST)
goto ok;
if ((y = xml_spec(x)) == NULL)
goto ok;
if ((key = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xml_hash_key(x, y, key) < 0)
goto done;
if (cbuf_len(key)){
if (hash_add(ph, cbuf_get(key), &x, sizeof(x)) == NULL)
goto done;
}
ok:
retval = 0;
done:
if (key)
cbuf_free(key);
return retval;
qsort(x->x_childvec, x->x_childvec_len, sizeof(struct xml*), xml_cmp);
return 0;
}
/*! XML hash rm. Create hash and add key/value to parent
*
* @param[in] x XML object
* eg same as xml_hash_op(x, 0)
*/
int
xml_hash_rm_entry(cxobj *x)
{
int retval = -1;
cxobj *xp;
clicon_hash_t *ph;
yang_stmt *y;
cbuf *key = NULL; /* cligen buffer hash key */
if (xml_hash(x)!=NULL)
xml_hash_rm_only(x);
if ((xp = xml_parent(x)) == NULL)
goto ok;
if ((ph = xml_hash(xp))==NULL)
goto ok;
if ((y = xml_spec(x)) == NULL)
goto ok;
if ((key = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xml_hash_key(x, y, key) < 0)
goto done;
if (cbuf_len(key)){
if (hash_del(ph, cbuf_get(key)) < 0)
goto done;
}
ok:
retval = 0;
done:
if (key)
cbuf_free(key);
return retval;
}
/*
* Turn this on to get a xml parse and pretty print test program

View file

@ -85,6 +85,7 @@
#include "clixon_xsl.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_map.h"
/* Something to do with reverse engineering of junos syntax? */
@ -590,136 +591,6 @@ cvec2xml_1(cvec *cvv,
return retval;
}
/*! Given child tree x1c, find matching child in base tree x0
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child
* param[out] x0cp Matching base tree child (if any)
* @note XXX: room for optimization? on 1K calls we have 1M body calls and
500K xml_child_each/cvec_each calls.
The outer loop is large for large lists
The inner loop is small
Major time in xml_find_body()
Can one do a binary search in the x0 list?
*/
int
match_base_child(cxobj *x0,
cxobj *x1c,
cxobj **x0cp,
yang_stmt *yc)
{
int retval = -1;
char *x1cname;
cxobj *x0c = NULL; /* x0 child */
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *b0;
char *b1;
yang_stmt *ykey;
char *keyname;
int equal;
char **b1vec = NULL;
int i;
cxobj **p;
cbuf *key = NULL; /* cligen buffer hash key */
size_t vlen;
if (xml_child_hash){
*x0cp = NULL; /* return value */
if (xml_hash(x0) == NULL)
goto nohash;
if ((key = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done1;
}
if (xml_hash_key(x1c, yc, key) < 0)
goto done;
x0c = NULL;
if (cbuf_len(key))
if ((p = hash_value(xml_hash(x0), cbuf_get(key), &vlen)) != NULL){
assert(vlen == sizeof(x0c));
x0c = *p;
}
// fprintf(stderr, "%s get %s = 0x%x\n", __FUNCTION__, cbuf_get(key), (unsigned int)x0c);
*x0cp = x0c;
retval = 0;
done1:
if (key)
cbuf_free(key);
return retval;
}
nohash:
*x0cp = NULL; /* return value */
x1cname = xml_name(x1c);
switch (yc->ys_keyword){
case Y_CONTAINER: /* Equal regardless */
case Y_LEAF: /* Equal regardless */
x0c = xml_find(x0, x1cname);
break;
case Y_LEAF_LIST: /* Match with name and value */
if ((b1 = xml_body(x1c)) == NULL)
goto ok;
x0c = xml_find_body_obj(x0, x1cname, b1);
break;
case Y_LIST: /* Match with key values */
if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, yc->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL; i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL)
i++;
if ((b1vec = calloc(i, sizeof(b1))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
cvi = NULL; i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((b1 = xml_find_body(x1c, keyname)) == NULL)
goto ok; /* not found */
b1vec[i++] = b1;
}
/* Iterate over x0 tree to (1) find a child that matches name
(2) that have keys that matches */
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL){
equal = 0;
if (strcmp(xml_name(x0c), x1cname))
continue;
/* Must be inner loop */
cvi = NULL; i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
b1 = b1vec[i++];
equal = 0;
keyname = cv_string_get(cvi);
if ((b0 = xml_find_body(x0c, keyname)) == NULL)
break; /* error case */
if (strcmp(b0, b1))
break; /* stop as soon as inequal key found */
equal=1; /* reaches here for all keynames, x0c is found. */
}
if (equal) /* x0c and x1c equal, otherwise look for other */
break;
} /* while x0c */
break;
default:
break;
}
ok:
*x0cp = x0c;
retval = 0;
done:
if (b1vec)
free(b1vec);
if (cvk)
cvec_free(cvk);
return retval;
}
/*! Find next yang node, either start from yang_spec or some yang-node
* @param[in] y Node spec or sny yang-node
@ -895,7 +766,6 @@ yang2api_path_fmt_1(yang_stmt *ys,
cbuf *cb)
{
yang_node *yp; /* parent */
yang_stmt *ykey;
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
@ -926,14 +796,7 @@ yang2api_path_fmt_1(yang_stmt *ys,
switch (ys->ys_keyword){
case Y_LIST:
if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, ys->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvk = ys->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
@ -951,8 +814,6 @@ yang2api_path_fmt_1(yang_stmt *ys,
} /* switch */
retval = 0;
done:
if (cvk)
cvec_free(cvk);
return retval;
}
@ -1370,13 +1231,16 @@ xml_order(cxobj *xt,
goto done;
}
j0 = 0;
/* Go through xml children and ensure they are same order as yspec children */
/* 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 */
/* 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)
@ -1528,7 +1392,6 @@ api_path2xpath_cvv(yang_spec *yspec,
yang_stmt *y = NULL;
char *val;
char *v;
yang_stmt *ykey;
cg_var *cvi;
for (i=offset; i<cvec_len(cvv); i++){
@ -1559,17 +1422,7 @@ api_path2xpath_cvv(yang_spec *yspec,
*v = '\0';
v++;
}
/* Find keys */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
clicon_debug(1, "ykey:%s", ykey->ys_argument);
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/%s", name);
@ -1647,7 +1500,6 @@ api_path2xml_vec(char **vec,
char *name;
char *restval = NULL;
char *restval_enc;
yang_stmt *ykey;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
@ -1709,15 +1561,7 @@ api_path2xml_vec(char **vec,
goto done;
break;
case Y_LIST:
/* Get the yang list key */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvk = y->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
if (valvec){
free(valvec);
valvec = NULL;
@ -1754,10 +1598,6 @@ api_path2xml_vec(char **vec,
if (xml_value_set(xb, val2) <0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default: /* eg Y_CONTAINER, Y_LEAF */
if ((x = xml_new(name, x0, y)) == NULL)

View file

@ -75,6 +75,7 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_sort.h"
#include "clixon_xml_parse.h"
void

476
lib/src/clixon_xml_sort.c Normal file
View file

@ -0,0 +1,476 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 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 search functions when used with YANG
*/
#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 <string.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <assert.h>
#include <syslog.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_sort.h"
/*
* Variables
*/
/* Sort and binary search of XML children
* Experimental
*/
int xml_child_sort = 1;
/*! Given an XML object and a child name, return yang stmt of child
* If no xml parent, find root yang stmt matching name
* @param[in] name Name of child
* @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
*/
int
xml_child_spec(char *name,
cxobj *xp,
yang_spec *yspec,
yang_stmt **yresult)
{
yang_stmt *y; /* result yang node */
yang_stmt *yparent; /* parent yang */
if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yparent, name);
else if (yspec)
y = yang_find_topnode(yspec, name, 0); /* still NULL for config */
else
y = NULL;
*yresult = y;
return 0;
}
/*!
* @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
* @param[out] userorder If set, this yang order is user ordered, linear search
* @retval 0 If equal (or userorder set)
* @retval <0 if arg1 is less than arg2
* @retval >0 if arg1 is greater than arg2
*/
static int
xml_cmp1(cxobj *x,
yang_stmt *y,
char *name,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval,
int *userorder)
{
char *b;
int i;
char *keyname;
char *key;
/* Check if same yang spec (order in yang stmt list) */
switch (keyword){
case Y_CONTAINER: /* Match with name */
case Y_LEAF: /* Match with name */
return strcmp(name, xml_name(x));
break;
case Y_LEAF_LIST: /* Match with name and value */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)
*userorder=1;
b=xml_body(x);
return strcmp(keyval[0], b);
break;
case Y_LIST: /* Match with array of key values */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)
*userorder=1;
for (i=0; i<keynr; i++){
keyname = keyvec[i];
key = keyval[i];
if ((b = xml_find_body(x, keyname)) == NULL)
break; /* error case */
return strcmp(key, b);
}
return 0;
break;
default:
break;
}
return 0; /* should not reach here */
}
static cxobj *
xml_search_userorder(cxobj *x0,
yang_stmt *y,
char *name,
int yangi,
int mid,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval)
{
int i;
cxobj *xc;
for (i=mid+1; i<xml_child_nr(x0); i++){ /* First increment */
xc = xml_child_i(x0, i);
y = xml_spec(xc);
if (yangi!=yang_order(y))
break;
if (xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, NULL) == 0)
return xc;
}
for (i=mid-1; i>=0; i--){ /* Then decrement */
xc = xml_child_i(x0, i);
y = xml_spec(xc);
if (yangi!=yang_order(y))
break;
if (xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, NULL) == 0)
return xc;
}
return NULL; /* Not found */
}
/*!
* @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
* @param[in] low Lower bound of childvec search interval
* @param[in] upper Lower bound of childvec search interval
*/
static cxobj *
xml_search1(cxobj *x0,
char *name,
int yangi,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval,
int low,
int upper)
{
int mid;
int cmp;
cxobj *xc;
yang_stmt *y;
int userorder= 0;
if (upper < low)
return NULL; /* not found */
mid = (low + upper) / 2;
if (mid >= xml_child_nr(x0)) /* beyond range */
return NULL;
xc = xml_child_i(x0, mid);
assert(y = xml_spec(xc));
if ((cmp = yangi-yang_order(y)) == 0){
cmp = xml_cmp1(xc, y, name, keyword, keynr, keyvec, keyval, &userorder);
if (userorder && cmp) /* Look inside this yangi order */
return xml_search_userorder(x0, y, name, yangi, mid, keyword, keynr, keyvec, keyval);
}
if (cmp == 0)
return xc;
else if (cmp < 0)
return xml_search1(x0, name, yangi, keyword,
keynr, keyvec, keyval, low, mid-1);
else
return xml_search1(x0, name, yangi, keyword,
keynr, keyvec, keyval, mid+1, upper);
return NULL;
}
/*!
* @param[in] yangi yang child order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
*/
cxobj *
xml_search(cxobj *x0,
char *name,
int yangi,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval)
{
return xml_search1(x0, name, yangi, keyword, keynr, keyvec, keyval,
0, xml_child_nr(x0));
}
/*! Find matching xml child given name and optional key values
* container: x0, y->keyword, name
* list: x0, y->keyword, y->key, name
*
* The function needs a vector of key values (or single for LEAF_LIST).
* What format?
* 1) argc/argv:: "val1","val2" <<==
* 2) cv-list?
* 3) va-list?
*
* yc - LIST (interface) -
* ^
* |
* x0-->x0c-->(name=interface)+->x(name=name)->xb(value="eth0") <==this is
* |
* v
* x1c->name (interface)
* x1c->x(name=name)->xb(value="eth0")
*
* CONTAINER:name
* LEAF: name
* LEAFLIST: name/body... #b0
* LIST: name/key0/key1... #b2vec+b0 -> x0c
* <interface><name>eth0</name></interface>
* <interface><name>eth1</name></interface>
* <interface><name>eth2</name></interface>
* @param[in] x0 XML node. Find child of this node.
* @param[in] keyword Yang keyword. Relevant: container, list, leaf, leaf_list
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
* @param[out] xp Return value on success, pointer to XML child node
* @note If keyword is:
* - list, keyvec and keyval should be an array with keynr length
* - leaf_list, keyval should be 1 and keyval should contain one element
* - otherwise, keyval should be 0 and keyval and keyvec should be both NULL.
*/
cxobj *
xml_match(cxobj *x0,
char *name,
enum rfc_6020 keyword,
int keynr,
char **keyvec,
char **keyval)
{
char *key;
char *keyname;
char *b0;
cxobj *x = NULL;
int equal;
int i;
x = NULL;
switch (keyword){
case Y_CONTAINER: /* Match with name */
case Y_LEAF: /* Match with name */
if (keynr != 0){
clicon_err(OE_XML, EINVAL, "Expected no key argument to CONTAINER or LEAF");
goto ok;
}
x = xml_find(x0, name);
break;
case Y_LEAF_LIST: /* Match with name and value */
if (keynr != 1)
goto ok;
x = xml_find_body_obj(x0, name, keyval[0]);
break;
case Y_LIST: /* Match with array of key values */
i = 0;
while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL){
equal = 0;
if (strcmp(xml_name(x), name))
continue;
/* Must be inner loop */
for (i=0; i<keynr; i++){
keyname = keyvec[i];
key = keyval[i];
equal = 0;
if ((b0 = xml_find_body(x, keyname)) == NULL)
break; /* error case */
if (strcmp(b0, key))
break; /* stop as soon as inequal key found */
equal=1; /* reaches here for all keynames, x is found. */
}
if (equal) /* x matches, oyherwise look for other */
break;
} /* while x */
break;
default:
break;
}
ok:
return x;
}
/*! Given child tree x1c, find matching child in base tree x0
* param[in] x0 Base tree node
* param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child
* param[out] x0cp Matching base tree child (if any)
* @note XXX: room for optimization? on 1K calls we have 1M body calls and
500K xml_child_each/cvec_each calls.
The outer loop is large for large lists
The inner loop is small
Major time in xml_find_body()
Can one do a binary search in the x0 list?
*/
int
match_base_child(cxobj *x0,
cxobj *x1c,
cxobj **x0cp,
yang_stmt *yc)
{
int retval = -1;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *b1;
char *keyname;
char **keyval = NULL;
char **keyvec = NULL;
char keynr = 0;
int i;
*x0cp = NULL; /* return value */
switch (yc->ys_keyword){
case Y_CONTAINER: /* Equal regardless */
case Y_LEAF: /* Equal regardless */
break;
case Y_LEAF_LIST: /* Match with name and value */
keynr = 1;
if ((keyval = calloc(keynr+1, sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
if ((keyval[0] = xml_body(x1c)) == NULL)
goto ok;
break;
case Y_LIST: /* Match with key values */
cvk = yc->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */
/* Count number of key indexes */
cvi = NULL; keynr = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL)
keynr++;
if ((keyval = calloc(keynr+1, sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
if ((keyvec = calloc(keynr+1, sizeof(char*))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
cvi = NULL; i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
keyvec[i] = keyname;
if ((b1 = xml_find_body(x1c, keyname)) == NULL)
goto ok; /* not found */
keyval[i++] = b1;
}
break;
default:
break;
}
/* Get match */
{
yang_node *y0;
int yorder;
if ((y0 = yc->ys_parent) == NULL)
goto done;
/* XXX: No we cant do this. on uppermost layer it can look like this:
* config
* ximport-----ymod = ietf
* interfaces--ymod = example
* Which means yang order can be different for different children in the
* same childvec.
*/
if (xml_child_sort==0)
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
else{
#if 1
if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){
yorder = yang_order(yc);
*x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
}
else{
clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__);
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
}
#else
cxobj *xx;
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
if (xml_child_nr(x0) && xml_spec(xml_child_i(x0,0))!=NULL){
xx = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
if (xx!=*x0cp){
clicon_log(LOG_WARNING, "%s mismatch", __FUNCTION__);
fprintf(stderr, "mismatch\n");
xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
assert(0);
}
}
else
clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__);
#endif
}
}
ok:
retval = 0;
done:
if (keyval)
free(keyval);
if (keyvec)
free(keyvec);
return retval;
}

View file

@ -373,7 +373,7 @@ yn_each(yang_node *yn,
*
* @param[in] yn Yang node, current context node.
* @param[in] keyword if 0 match any keyword
* @param[in] argument String compare w wrgument. if NULL, match any.
* @param[in] argument String compare w argument. if NULL, match any.
* This however means that if you actually want to match only a yang-stmt with
* argument==NULL you cannot, but I have not seen any such examples.
* @see yang_find_datanode
@ -384,8 +384,8 @@ yang_find(yang_node *yn,
char *argument)
{
yang_stmt *ys = NULL;
int i;
int match = 0;
int i;
int match = 0;
for (i=0; i<yn->yn_len; i++){
ys = yn->yn_stmt[i];
@ -558,6 +558,54 @@ yang_find_myprefix(yang_stmt *ys)
return prefix;
}
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
* @retval 0 not found
* @retval 1 found
*/
static int
order1(yang_node *yp,
yang_stmt *y,
int *index)
{
yang_stmt *ys;
int i;
for (i=0; i<yp->yn_len; i++){
ys = yp->yn_stmt[i];
if (!yang_datanode(ys))
continue;
if (ys==y)
return 1;
(*index)++;
}
return 0;
}
/*! Return order of yang statement y in parents child vector
* @retval i Order of child with specified argument
* @retval -1 Not found
*/
int
yang_order(yang_stmt *y)
{
yang_node *yp;
yang_node *ypp;
yang_node *yn;
int i;
int j=0;
yp = y->ys_parent;
if (yp->yn_keyword == Y_MODULE ||yp->yn_keyword == Y_SUBMODULE){
ypp = yp->yn_parent;
for (i=0; i<ypp->yn_len; i++){
yn = (yang_node*)ypp->yn_stmt[i];
if (order1(yn, y, &j) == 1)
return j;
}
}
order1(yp, y, &j);
return j;
}
/*! Reset flag in complete tree, arg contains flag */
static int
@ -880,6 +928,20 @@ ys_populate_leaf(yang_stmt *ys,
return retval;
}
static int
ys_populate_list(yang_stmt *ys,
void *arg)
{
yang_stmt *ykey;
if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL)
return 0;
cvec_free(ys->ys_cvec);
if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL)
return -1;
return 0;
}
/*! Populate range and length statements
*
* Create cvec variables "range_min" and "range_max". Assume parent is type.
@ -1061,6 +1123,10 @@ ys_populate(yang_stmt *ys,
if (ys_populate_leaf(ys, arg) < 0)
goto done;
break;
case Y_LIST:
if (ys_populate_list(ys, arg) < 0)
goto done;
break;
case Y_RANGE:
case Y_LENGTH:
if (ys_populate_range(ys, arg) < 0)