Merge branch 'master' of https://github.com/clicon/clixon
This commit is contained in:
commit
70221742f7
26 changed files with 1314 additions and 314 deletions
|
|
@ -966,13 +966,90 @@ netconf_malformed_message_xml(cxobj **xret,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Create Netconf data-not-unique error message according to RFC 7950 15.1
|
||||
*
|
||||
* A NETCONF operation would result in configuration data where a
|
||||
* "unique" constraint is invalidated.
|
||||
* @param[out] cb CLIgen buf. Error XML is written in this buffer
|
||||
* @param[in] x List element containing duplicate
|
||||
* @param[in] cvk List of comonents in x that are non-unique
|
||||
* @see RFC7950 Sec 15.1
|
||||
*/
|
||||
int
|
||||
netconf_data_not_unique(cbuf *cb,
|
||||
cxobj *x,
|
||||
cvec *cvk)
|
||||
{
|
||||
int retval = -1;
|
||||
cg_var *cvi = NULL;
|
||||
cxobj *xi;
|
||||
|
||||
if (cprintf(cb, "<rpc-reply><rpc-error>"
|
||||
"<error-type>protocol</error-type>"
|
||||
"<error-tag>operation-failed</error-tag>"
|
||||
"<error-app-tag>data-not-unique</error-app-tag>"
|
||||
"<error-severity>error</error-severity>"
|
||||
"<error-info>") < 0)
|
||||
goto err;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL){
|
||||
if ((xi = xml_find(x, cv_string_get(cvi))) == NULL)
|
||||
continue; /* ignore, shouldnt happen */
|
||||
cprintf(cb, "<non-unique>");
|
||||
clicon_xml2cbuf(cb, xi, 0, 0);
|
||||
cprintf(cb, "</non-unique>");
|
||||
}
|
||||
if (cprintf(cb, "</error-info></rpc-error></rpc-reply>") <0)
|
||||
goto err;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
err:
|
||||
clicon_err(OE_XML, errno, "cprintf");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Create Netconf too-many/few-elements err msg according to RFC 7950 15.2/15.3
|
||||
*
|
||||
* A NETCONF operation would result in configuration data where a
|
||||
list or a leaf-list would have too many entries, the following error
|
||||
* @param[out] cb CLIgen buf. Error XML is written in this buffer
|
||||
* @param[in] x List element containing duplicate
|
||||
* @param[in] max If set, return too-many, otherwise too-few
|
||||
* @see RFC7950 Sec 15.1
|
||||
*/
|
||||
int
|
||||
netconf_minmax_elements(cbuf *cb,
|
||||
cxobj *x,
|
||||
int max)
|
||||
{
|
||||
int retval = -1;
|
||||
|
||||
if (cprintf(cb, "<rpc-reply><rpc-error>"
|
||||
"<error-type>protocol</error-type>"
|
||||
"<error-tag>operation-failed</error-tag>"
|
||||
"<error-app-tag>too-%s-elements</error-app-tag>"
|
||||
"<error-severity>error</error-severity>"
|
||||
"<error-path>%s</error-path>"
|
||||
"</rpc-error></rpc-reply>",
|
||||
max?"many":"few",
|
||||
xml_name(x)) < 0) /* XXX should be xml2xpath */
|
||||
goto err;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
err:
|
||||
clicon_err(OE_XML, errno, "cprintf");
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
||||
/*! Help function: merge - check yang - if error make netconf errmsg
|
||||
* @param[in] x XML tree
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[in,out] xret Existing XML tree, merge x into this
|
||||
* @retval -1 Error (fatal)
|
||||
* @retval 0 OK
|
||||
* @retval 1 Statedata callback failed
|
||||
* @retval 0 Statedata callback failed
|
||||
* @retval 1 OK
|
||||
*/
|
||||
int
|
||||
netconf_trymerge(cxobj *x,
|
||||
|
|
@ -995,15 +1072,17 @@ netconf_trymerge(cxobj *x,
|
|||
xml_purge(xc);
|
||||
if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
|
||||
goto done;
|
||||
retval = 1;
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
retval = 1;
|
||||
done:
|
||||
if (reason)
|
||||
free(reason);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Load ietf netconf yang module and set enabled features
|
||||
|
|
|
|||
|
|
@ -503,7 +503,6 @@ rpc_callback_call(clicon_handle h,
|
|||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
rpc_callback_t *rc;
|
||||
char *name;
|
||||
char *prefix;
|
||||
|
|
@ -520,7 +519,7 @@ rpc_callback_call(clicon_handle h,
|
|||
if (strcmp(rc->rc_name, name) == 0 &&
|
||||
namespace && rc->rc_namespace &&
|
||||
strcmp(rc->rc_namespace, namespace) == 0){
|
||||
if ((ret = rc->rc_callback(h, xe, cbret, arg, rc->rc_arg)) < 0){
|
||||
if (rc->rc_callback(h, xe, cbret, arg, rc->rc_arg) < 0){
|
||||
clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,6 +495,7 @@ xml_yang_validate_rpc(cxobj *xrpc,
|
|||
* @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,
|
||||
|
|
@ -502,27 +503,59 @@ check_choice(cxobj *xt,
|
|||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yc;
|
||||
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 ((yc = yang_choice(yt)) == NULL)
|
||||
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 */
|
||||
x = NULL; /* Find a child with same yang spec */
|
||||
while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
|
||||
if ((x != xt) &&
|
||||
(y = xml_spec(x)) != NULL &&
|
||||
(yp = yang_choice(y)) != NULL &&
|
||||
yp == yc){
|
||||
if (netconf_bad_element(cbret, "application", xml_name(x), "Element in choice statement already exists") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
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:
|
||||
|
|
@ -671,6 +704,311 @@ check_list_key(cxobj *xt,
|
|||
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.
|
||||
|
|
@ -850,9 +1188,9 @@ xml_yang_validate_all(cxobj *xt,
|
|||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (ys != NULL && yang_config(ys) != 0){
|
||||
if (yang_config(ys) != 0){
|
||||
/* Node-specific validation */
|
||||
switch (ys->ys_keyword){
|
||||
switch (yang_keyword_get(ys)){
|
||||
case Y_ANYXML:
|
||||
case Y_ANYDATA:
|
||||
goto ok;
|
||||
|
|
@ -872,33 +1210,6 @@ xml_yang_validate_all(cxobj *xt,
|
|||
if (validate_identityref(xt, ys, yc, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if ((yc = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
|
||||
/* 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.
|
||||
*/
|
||||
#if 0
|
||||
cxobj *xp;
|
||||
cxobj *x;
|
||||
int i;
|
||||
|
||||
if ((xp = xml_parent(xt)) != NULL){
|
||||
nr = atoi(yc->ys_argument);
|
||||
x = NULL;
|
||||
i = 0;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL)
|
||||
i++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if ((yc = yang_find(ys, Y_MAX_ELEMENTS, NULL)) != NULL){
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -941,6 +1252,14 @@ xml_yang_validate_all(cxobj *xt,
|
|||
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:
|
||||
|
|
@ -961,12 +1280,14 @@ xml_yang_validate_all_top(cxobj *xt,
|
|||
{
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_yang_validate_all(x, cbret)) < 1)
|
||||
return ret;
|
||||
}
|
||||
if ((ret = check_list_unique_minmax(xt, cbret)) < 1)
|
||||
return ret;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -416,11 +416,9 @@ xml_search1(cxobj *xp,
|
|||
cmp = yangi-yang_order(y);
|
||||
/* Here is right yang order == same yang? */
|
||||
if (cmp == 0){
|
||||
if (userorder){
|
||||
cmp = xml_cmp(x1, xc, 0);
|
||||
if (cmp && userorder) /* Ordered by user (if not equal) */
|
||||
return xml_search_userorder(xp, x1, y, yangi, mid);
|
||||
}
|
||||
else /* Ordered by system */
|
||||
cmp = xml_cmp(x1, xc, 0);
|
||||
}
|
||||
if (cmp == 0)
|
||||
return xc;
|
||||
|
|
|
|||
|
|
@ -759,12 +759,12 @@ yang_choice(yang_stmt *y)
|
|||
yang_stmt *yp;
|
||||
|
||||
if ((yp = y->ys_parent) != NULL){
|
||||
switch (yp->ys_keyword){
|
||||
switch (yang_keyword_get(yp)){
|
||||
case Y_CHOICE:
|
||||
return yp;
|
||||
break;
|
||||
case Y_CASE:
|
||||
return yp->ys_parent;
|
||||
return yang_parent_get(yp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -773,31 +773,57 @@ yang_choice(yang_stmt *y)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*! Find matching y in yp:s children, "yang order" of y when y is choice
|
||||
* @param[in] yp Choice node
|
||||
* @param[in] y Yang datanode to find
|
||||
* @param[out] index Index of y in yp:s list of children
|
||||
* @retval 0 not found (must be datanode)
|
||||
* @retval 1 found
|
||||
* @see order1 the main function
|
||||
* There are two distinct cases, either (1) the choice has case statements, or
|
||||
* (2) it uses shortcut mode without case statements.
|
||||
* In (1) we need to count how many sub-statements and keep a max
|
||||
* In (2) we increment with only 1.
|
||||
*/
|
||||
static int
|
||||
order1_choice(yang_stmt *yp,
|
||||
yang_stmt *y)
|
||||
yang_stmt *y,
|
||||
int *index)
|
||||
{
|
||||
yang_stmt *ys;
|
||||
yang_stmt *yc;
|
||||
int i;
|
||||
int j;
|
||||
int shortcut=0;
|
||||
int max=0;
|
||||
|
||||
for (i=0; i<yp->ys_len; i++){
|
||||
for (i=0; i<yp->ys_len; i++){ /* Loop through choice */
|
||||
ys = yp->ys_stmt[i];
|
||||
if (ys->ys_keyword == Y_CASE){
|
||||
if (ys->ys_keyword == Y_CASE){ /* Loop through case */
|
||||
for (j=0; j<ys->ys_len; j++){
|
||||
yc = ys->ys_stmt[j];
|
||||
if (yang_datanode(yc) && yc == y)
|
||||
if (yang_datanode(yc) && yc == y){
|
||||
*index += j;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (j>max)
|
||||
max = j;
|
||||
}
|
||||
else {
|
||||
shortcut = 1; /* Shortcut, no case */
|
||||
if (yang_datanode(ys) && ys == y)
|
||||
return 1;
|
||||
}
|
||||
else if (yang_datanode(ys) && ys == y)
|
||||
return 1;
|
||||
}
|
||||
if (shortcut)
|
||||
(*index)++;
|
||||
else
|
||||
*index += max;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
|
||||
/*! Find matching y in yp:s children, return "yang order" of y or -1 if not found
|
||||
* @param[in] yp Parent
|
||||
* @param[in] y Yang datanode to find
|
||||
* @param[out] index Index of y in yp:s list of children
|
||||
|
|
@ -815,7 +841,7 @@ order1(yang_stmt *yp,
|
|||
for (i=0; i<yp->ys_len; i++){
|
||||
ys = yp->ys_stmt[i];
|
||||
if (ys->ys_keyword == Y_CHOICE){
|
||||
if (order1_choice(ys, y) == 1) /* If one of the choices is "y" */
|
||||
if (order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
|
|
@ -823,8 +849,8 @@ order1(yang_stmt *yp,
|
|||
continue;
|
||||
if (ys==y)
|
||||
return 1;
|
||||
(*index)++;
|
||||
}
|
||||
(*index)++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -848,7 +874,7 @@ yang_order(yang_stmt *y)
|
|||
|
||||
if (y == NULL)
|
||||
return -1;
|
||||
/* Some special handling if yp is choice (or case) and maybe union?
|
||||
/* Some special handling if yp is choice (or case)
|
||||
* if so, the real parent (from an xml point of view) is the parents
|
||||
* parent.
|
||||
*/
|
||||
|
|
@ -1213,7 +1239,6 @@ yang_print_cbuf(cbuf *cb,
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! Populate yang leafs after parsing. Create cv and fill it in.
|
||||
*
|
||||
* Populate leaf in 2nd round of yang parsing, now that context is complete:
|
||||
|
|
@ -1308,7 +1333,8 @@ ys_populate_list(yang_stmt *ys,
|
|||
|
||||
if ((ykey = yang_find(ys, Y_KEY, NULL)) == NULL)
|
||||
return 0;
|
||||
cvec_free(ys->ys_cvec);
|
||||
if (ys->ys_cvec)
|
||||
cvec_free(ys->ys_cvec);
|
||||
if ((ys->ys_cvec = yang_arg2cvec(ykey, " ")) == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
|
|
@ -1681,6 +1707,18 @@ ys_populate_feature(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Populate the unique statement with a cvec
|
||||
*/
|
||||
static int
|
||||
ys_populate_unique(yang_stmt *ys)
|
||||
{
|
||||
if (ys->ys_cvec)
|
||||
cvec_free(ys->ys_cvec);
|
||||
if ((ys->ys_cvec = yang_arg2cvec(ys, " ")) == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Populate unknown node with extension
|
||||
*/
|
||||
static int
|
||||
|
|
@ -1770,6 +1808,10 @@ ys_populate(yang_stmt *ys,
|
|||
if (ys_populate_identity(ys, NULL) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_UNIQUE:
|
||||
if (ys_populate_unique(ys) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case Y_UNKNOWN:
|
||||
if (ys_populate_unknown(ys) < 0)
|
||||
goto done;
|
||||
|
|
@ -3202,7 +3244,9 @@ ys_parse(yang_stmt *ys,
|
|||
*
|
||||
* The cv:s created in parse-tree as follows:
|
||||
* fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass)
|
||||
*
|
||||
* revision: cv as uint32 date: Integer version as YYYYMMDD
|
||||
* min-elements: cv as uint32
|
||||
* max-elements: cv as uint32, '0' means unbounded
|
||||
* @see ys_populate
|
||||
*/
|
||||
int
|
||||
|
|
@ -3212,8 +3256,15 @@ ys_parse_sub(yang_stmt *ys,
|
|||
int retval = -1;
|
||||
uint8_t fd;
|
||||
uint32_t date = 0;
|
||||
|
||||
switch (ys->ys_keyword){
|
||||
char *arg;
|
||||
enum rfc_6020 keyword;
|
||||
char *reason = NULL;
|
||||
int ret;
|
||||
uint32_t minmax;
|
||||
|
||||
arg = yang_argument_get(ys);
|
||||
keyword = yang_keyword_get(ys);
|
||||
switch (keyword){
|
||||
case Y_FRACTION_DIGITS:
|
||||
if (ys_parse(ys, CGV_UINT8) == NULL)
|
||||
goto done;
|
||||
|
|
@ -3225,7 +3276,7 @@ ys_parse_sub(yang_stmt *ys,
|
|||
break;
|
||||
case Y_REVISION:
|
||||
case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */
|
||||
if (ys_parse_date_arg(ys->ys_argument, &date) < 0)
|
||||
if (ys_parse_date_arg(arg, &date) < 0)
|
||||
goto done;
|
||||
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_new");
|
||||
|
|
@ -3234,14 +3285,37 @@ ys_parse_sub(yang_stmt *ys,
|
|||
cv_uint32_set(ys->ys_cv, date);
|
||||
break;
|
||||
case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */
|
||||
if (strcmp(ys->ys_argument, "current") &&
|
||||
strcmp(ys->ys_argument, "deprecated") &&
|
||||
strcmp(ys->ys_argument, "obsolete")){
|
||||
clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", ys->ys_argument);
|
||||
if (strcmp(arg, "current") &&
|
||||
strcmp(arg, "deprecated") &&
|
||||
strcmp(arg, "obsolete")){
|
||||
clicon_err(OE_YANG, errno, "Invalid status: \"%s\", expected current, deprecated, or obsolete", arg);
|
||||
goto done;
|
||||
|
||||
}
|
||||
break;
|
||||
case Y_MAX_ELEMENTS:
|
||||
case Y_MIN_ELEMENTS:
|
||||
if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_new");
|
||||
goto done;
|
||||
}
|
||||
if (keyword == Y_MAX_ELEMENTS &&
|
||||
strcmp(arg, "unbounded") == 0)
|
||||
cv_uint32_set(ys->ys_cv, 0); /* 0 means unbounded for max */
|
||||
else{
|
||||
if ((ret = parse_uint32(arg, &minmax, &reason)) < 0){
|
||||
clicon_err(OE_YANG, errno, "parse_uint32");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0){
|
||||
clicon_err(OE_YANG, EINVAL, "element-min/max parse error: %s", reason);
|
||||
if (reason)
|
||||
free(reason);
|
||||
goto done;
|
||||
}
|
||||
cv_uint32_set(ys->ys_cv, minmax);
|
||||
}
|
||||
break;
|
||||
case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
|
||||
but it may not be */
|
||||
if (extra == NULL)
|
||||
|
|
@ -3273,8 +3347,7 @@ int
|
|||
yang_mandatory(yang_stmt *ys)
|
||||
{
|
||||
yang_stmt *ym;
|
||||
char *reason = NULL;
|
||||
uint8_t min_elements; /* XXX change to 32 (need new cligen version) */
|
||||
cg_var *cv;
|
||||
|
||||
/* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
|
||||
* statement with the value "true". */
|
||||
|
|
@ -3289,12 +3362,8 @@ yang_mandatory(yang_stmt *ys)
|
|||
* value greater than zero. */
|
||||
else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){
|
||||
if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
|
||||
/* XXX change to 32 (need new cligen version) */
|
||||
if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){
|
||||
clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8");
|
||||
return 0; /* XXX ignore error */
|
||||
}
|
||||
return min_elements > 0;
|
||||
cv = yang_cv_get(ym);
|
||||
return cv_uint32_get(cv) > 0;
|
||||
}
|
||||
}
|
||||
/* 3) A container node without a "presence" statement and that has at
|
||||
|
|
|
|||
|
|
@ -255,8 +255,8 @@ yms_build(clicon_handle h,
|
|||
* @param[in] brief Just name, revision and uri (no cache)
|
||||
* @param[in,out] xret Existing XML tree, merge x into this
|
||||
* @retval -1 Error (fatal)
|
||||
* @retval 0 OK
|
||||
* @retval 1 Statedata callback failed
|
||||
* @retval 0 Statedata callback failed
|
||||
* @retval 1 OK
|
||||
* @notes NYI: schema, deviation
|
||||
x +--ro modules-state
|
||||
x +--ro module-set-id string
|
||||
|
|
@ -288,6 +288,7 @@ yang_modules_state_get(clicon_handle h,
|
|||
char *msid; /* modules-set-id */
|
||||
cxobj *x1;
|
||||
cbuf *cb = NULL;
|
||||
int ret;
|
||||
|
||||
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
|
||||
if ((x = clicon_modst_cache_get(h, brief)) != NULL){
|
||||
|
|
@ -313,8 +314,7 @@ yang_modules_state_get(clicon_handle h,
|
|||
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
|
||||
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
|
||||
goto done;
|
||||
retval = 1;
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (xml_rootchild(x, 0, &x) < 0)
|
||||
goto done;
|
||||
|
|
@ -326,10 +326,12 @@ yang_modules_state_get(clicon_handle h,
|
|||
/* Wrap x (again) with new top-level node "top" which merge wants */
|
||||
if ((x = xml_wrap(x, "top")) < 0)
|
||||
goto done;
|
||||
if (netconf_trymerge(x, yspec, xret) < 0)
|
||||
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
retval = 0;
|
||||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||
if (cb)
|
||||
|
|
@ -337,6 +339,9 @@ yang_modules_state_get(clicon_handle h,
|
|||
if (x)
|
||||
xml_free(x);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! For single module state with namespace, get revisions and send upgrade callbacks
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue