diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4c8be2..779370e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,13 @@ ## 4.8.0 Expected: October 2020 +### New features + +* Added support for the following XPATH functions: + * `contains`, see https://www.w3.org/TR/xpath-10 + * `derived-from` and `derived-from-or-self` + * in particular in augment/when statements as shown in eg RFC 7950. + ## 4.7.0 14 September 2020 diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 48ef85b7..d4e0224d 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -200,6 +200,10 @@ int yang_cvec_set(yang_stmt *ys, cvec *cvv); uint16_t yang_flag_get(yang_stmt *ys, uint16_t flag); int yang_flag_set(yang_stmt *ys, uint16_t flag); int yang_flag_reset(yang_stmt *ys, uint16_t flag); +char *yang_when_xpath_get(yang_stmt *ys); +int yang_when_xpath_set(yang_stmt *ys, char *xpath); +cvec *yang_when_nsc_get(yang_stmt *ys); +int yang_when_nsc_set(yang_stmt *ys, cvec *nsc); /* Other functions */ yang_stmt *yspec_new(void); diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 289a93ae..b92ec4ae 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -2,7 +2,9 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -58,6 +60,7 @@ int ys_resolve_type(yang_stmt *ys, void *arg); int yang2cv_type(char *ytype, enum cv_type *cv_type); char *cv2yang_type(enum cv_type cv_type); yang_stmt *yang_find_identity(yang_stmt *ys, char *identity); +yang_stmt *yang_find_identity_nsc(yang_stmt *yspec, char *identity, cvec *nsc); int ys_cv_validate(clicon_handle h, cg_var *cv, yang_stmt *ys, char **reason); int clicon_type2cv(char *type, char *rtype, yang_stmt *ys, enum cv_type *cvtype); int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype, diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index af9a8524..02e522b1 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -77,7 +77,7 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_path.c clixon_validate.c \ clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_optimize.c \ + clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c clixon_xpath_optimize.c \ clixon_sha1.c clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ clixon_netconf_lib.c clixon_stream.c clixon_nacm.c diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 0872588e..b605685c 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -1174,11 +1174,45 @@ xml_yang_validate_all(clicon_handle h, /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){ xpath = yang_argument_get(yc); /* "when" has xpath argument */ - if ((nr = xpath_vec_bool(xt, NULL, "%s", xpath)) < 0) + /* WHEN xpath needs namespace context */ + if (xml_nsctx_yang(ys, &nsc) < 0) goto done; - if (!nr){ + if ((nr = xpath_vec_bool(xt, nsc, + "%s", xpath)) < 0) + goto done; + if (nsc){ + xml_nsctx_free(nsc); + nsc = NULL; + } + if (nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Failed WHEN condition of %s in module %s", + xml_name(xt), + yang_argument_get(ys_module(ys))); if (netconf_operation_failed_xml(xret, "application", - "when xpath validation failed") < 0) + cbuf_get(cb)) < 0) + goto done; + goto fail; + } + } + /* Augmented when using special struct. */ + if ((xpath = yang_when_xpath_get(ys)) != NULL){ + if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys), + "%s", xpath)) < 0) + goto done; + if (nr == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "Failed augmented WHEN condition of %s in module %s", + xml_name(xt), + yang_argument_get(ys_module(ys))); + if (netconf_operation_failed_xml(xret, "application", + cbuf_get(cb)) < 0) goto done; goto fail; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index ae31a829..9af1bb8b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2180,3 +2180,4 @@ xml_copy_marked(cxobj *x0, done: return retval; } + diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c index 8d155ffc..d4d400ac 100644 --- a/lib/src/clixon_xpath_eval.c +++ b/lib/src/clixon_xpath_eval.c @@ -87,6 +87,7 @@ #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_xpath_optimize.h" +#include "clixon_xpath_function.h" #include "clixon_xpath_eval.h" /* Mapping between XPATH operator string <--> int */ @@ -178,8 +179,8 @@ nodetest_eval_node(cxobj *x, */ static int nodetest_eval_node_localonly(cxobj *x, - xpath_tree *xs, - cvec *nsc) + xpath_tree *xs, + cvec *nsc) { int retval = -1; char *name1 = xml_name(x); @@ -237,11 +238,11 @@ nodetest_eval(cxobj *x, /*! * @param[in] xn - * @param[in] nodetest XPATH stack + * @param[in] nodetest XPATH stack * @param[in] node_type * @param[in] flags * @param[in] nsc XML Namespace context - * @param[in] localonly Skip prefix and namespace tests (non-standard) + * @param[in] localonly Skip prefix and namespace tests (non-standard) * @param[out] vec0 * @param[out] vec0len */ @@ -333,7 +334,7 @@ xp_eval_step(xp_ctx *xc0, else{ if (nodetest->xs_type==XP_NODE_FN && nodetest->xs_s0 && - strcmp(nodetest->xs_s0,"current")==0){ + strcmp(nodetest->xs_s0, "current")==0){ if (cxvec_append(xc->xc_initial, &vec, &veclen) < 0) goto done; } @@ -522,7 +523,6 @@ xp_eval_predicate(xp_ctx *xc, if (xrc) ctx_free(xrc); } - } assert(xr0||xr1); if (xr1){ @@ -972,6 +972,25 @@ xp_eval(xp_ctx *xc, goto done; goto ok; break; + case XP_PRIME_FN: + if (xs->xs_s0){ + if (strcmp(xs->xs_s0, "contains") == 0){ + if (xp_function_contains(xc, xs->xs_c0, nsc, localonly, xrp) < 0) + goto done; + goto ok; + } + else if (strcmp(xs->xs_s0, "derived-from") == 0){ + if (xp_function_derived_from(xc, xs->xs_c0, nsc, localonly, 0, xrp) < 0) + goto done; + goto ok; + } + else if (strcmp(xs->xs_s0, "derived-from-or-self") == 0){ + if (xp_function_derived_from(xc, xs->xs_c0, nsc, localonly, 1, xrp) < 0) + goto done; + goto ok; + } + } + break; default: break; } @@ -1047,12 +1066,10 @@ xp_eval(xp_ctx *xc, xr0->xc_type = XT_STRING; xr0->xc_string = xs->xs_s0?strdup(xs->xs_s0):NULL; break; - case XP_PRIME_FN: - break; default: break; } - /* Eval second child c0 + /* Eval second child c1 * Note, some operators like locationpath, need transitive context (use_xr0) */ if (xs->xs_c1) diff --git a/lib/src/clixon_xpath_function.c b/lib/src/clixon_xpath_function.c new file mode 100644 index 00000000..69ff1fc0 --- /dev/null +++ b/lib/src/clixon_xpath_function.c @@ -0,0 +1,295 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 + * and rfc 7950 + * + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* NaN */ + +/* cligen */ +#include + +/* clicon */ +#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_options.h" +#include "clixon_yang.h" +#include "clixon_yang_type.h" +#include "clixon_xml.h" +#include "clixon_xml_map.h" +#include "clixon_yang_module.h" +#include "clixon_validate.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_eval.h" +#include "clixon_xpath_function.h" + +/*! Eval xpath function contains + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[in] nsc XML Namespace context + * @param[in] localonly Skip prefix and namespace tests (non-standard) + * @param[out] xrp Resulting context + * @retval 0 OK + * @retval -1 Error + * @see https://www.w3.org/TR/xpath-10/#NT-FunctionName 4.2 String Functions + */ +int +xp_function_contains(xp_ctx *xc, + struct xpath_tree *xs, + cvec *nsc, + int localonly, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr = NULL; + char *s0 = NULL; + char *s1 = NULL; + + /* contains two arguments in xs: boolean contains(string, string) */ + if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0) + goto done; + if (ctx2string(xr0, &s0) < 0) + goto done; + if (xp_eval(xc, xs->xs_c1, nsc, localonly, &xr1) < 0) + goto done; + if (ctx2string(xr1, &s1) < 0) + goto done; + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_type = XT_BOOL; + xr->xc_bool = (strstr(s0, s1) != NULL); + *xrp = xr; + xr = NULL; + retval = 0; + done: + if (xr0) + ctx_free(xr0); + if (xr1) + ctx_free(xr1); + if (s0) + free(s0); + if (s1) + free(s1); + return retval; +} + +/*! Helper function for derived-from(-and-self) - eval one node + * @param[in] nsc XML Namespace context + * @param[in] self If set, implements derived_from_or_self + * @retval 1 OK and match + * @retval 0 OK but not match + * @retval -1 Error + */ +static int +derived_from_one(char *baseidentity, + cvec *nsc, + cxobj *xleaf, + int self) +{ + int retval = -1; + yang_stmt *yleaf; + yang_stmt *ytype; + yang_stmt *ybaseid; + yang_stmt *ymod; + cvec *idrefvec; /* Derived identityref list: (module:id)**/ + char *node = NULL; + char *prefix = NULL; + char *id = NULL; + cbuf *cb = NULL; + char *baseid = NULL; + + /* Split baseidentity to get its id (w/o prefix) */ + if (nodeid_split(baseidentity, NULL, &baseid) < 0) + goto done; + if ((yleaf = xml_spec(xleaf)) == NULL) + goto nomatch; + if (yang_keyword_get(yleaf) != Y_LEAF && yang_keyword_get(yleaf) != Y_LEAF_LIST) + goto nomatch; + /* Node is of type identityref */ + if (yang_type_get(yleaf, NULL, &ytype, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (ytype == NULL || strcmp(yang_argument_get(ytype), "identityref")) + goto nomatch; + /* Find if the derivation chain is: identity ->...-> ytype + * Example: + * identity is ex:ethernet + * xleaf fast-ethernet + * yleaf type identityref{base interface-type;} + */ + /* Just get the object corresponding to the base identity */ + if ((ybaseid = yang_find_identity_nsc(ys_spec(yleaf), baseidentity, nsc)) == NULL) + goto nomatch; + /* Get its list of derived identities */ + idrefvec = yang_cvec_get(ybaseid); + /* Get and split the leaf id reference */ + if ((node = xml_body(xleaf)) == NULL) /* It may not be empty */ + goto nomatch; + if (nodeid_split(node, &prefix, &id) < 0) + goto done; + /* Get its module (prefixes are not used here) */ + if (prefix == NULL) + ymod = ys_module(yleaf); + else{ /* from prefix to name */ +#if 1 /* IDENTITYREF_KLUDGE */ + ymod = yang_find_module_by_prefix_yspec(ys_spec(yleaf), prefix); +#endif + } + if (ymod == NULL) + goto nomatch; + /* self special case, ie that the xleaf has a ref to itself */ + if (self && + ymod == ys_module(ybaseid) && + strcmp(baseid, id) == 0){ + ; /* match */ + } + else { + /* Allocate cbuf */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%s:%s", yang_argument_get(ymod), id); + if (cvec_find(idrefvec, cbuf_get(cb)) == NULL) + goto nomatch; + } + retval = 1; + done: + if (baseid) + free(baseid); + if (cb) + cbuf_free(cb); + if (id) + free(id); + if (prefix) + free(prefix); + return retval; + nomatch: + retval = 0; + goto done; +} + +/*! Eval xpath function derived-from(-and-self) + * @param[in] xc Incoming context + * @param[in] xs XPATH node tree + * @param[in] nsc XML Namespace context + * @param[in] localonly Skip prefix and namespace tests (non-standard) + * @param[in] self If set, implements derived_from_or_self + * @param[out] xrp Resulting context + * @retval 0 OK + * @retval -1 Error + * @see rfc7950 10.4.1 + * Returns "true" if any node in the argument "nodes" is a node of type "identityref" and its + * value is an identity that is derived from (see Section 7.18.2) the identity "identity" + * boolean derived-from(node-set nodes, string identity) + * @see validate_identityref for similar code other usage + */ +int +xp_function_derived_from(xp_ctx *xc, + struct xpath_tree *xs, + cvec *nsc, + int localonly, + int self, + xp_ctx **xrp) +{ + int retval = -1; + xp_ctx *xr0 = NULL; + xp_ctx *xr1 = NULL; + xp_ctx *xr = NULL; + char *identity = NULL; + int i; + int ret = 0; + + /* contains two arguments in xs: boolean derived-from(node-set, string) */ + /* This evolves to a set of (identityref) nodes */ + if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0) + goto done; + if (xr0->xc_type != XT_NODESET) + goto done; + /* This evolves to a string identity */ + if (xp_eval(xc, xs->xs_c1, nsc, localonly, &xr1) < 0) + goto done; + if (ctx2string(xr1, &identity) < 0) + goto done; + /* Allocate a return struct of type boolean */ + if ((xr = malloc(sizeof(*xr))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xr, 0, sizeof(*xr)); + xr->xc_type = XT_BOOL; + /* ANY node is an identityref and its value an identity that is derived ... */ + for (i=0; ixc_size; i++){ + if ((ret = derived_from_one(identity, nsc, xr0->xc_nodeset[i], self)) < 0) + goto done; + if (ret == 1) + break; + } + xr->xc_bool = ret; + *xrp = xr; + xr = NULL; + retval = 0; + done: + if (xr0) + ctx_free(xr0); + if (xr1) + ctx_free(xr1); + if (identity) + free(identity); + return retval; +} diff --git a/lib/src/clixon_xpath_function.h b/lib/src/clixon_xpath_function.h new file mode 100644 index 00000000..29942ced --- /dev/null +++ b/lib/src/clixon_xpath_function.h @@ -0,0 +1,47 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 ***** + + * Clixon XML XPATH 1.0 according to https://www.w3.org/TR/xpath-10 (Base XML) + * and rfc 7950 (YANG-specific) + */ +#ifndef _CLIXON_XPATH_FUNCTION_H +#define _CLIXON_XPATH_FUNCTION_H + +/* + * Prototypes + */ +int xp_function_contains(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, xp_ctx **xrp); +int xp_function_derived_from(xp_ctx *xc, struct xpath_tree *xs, cvec *nsc, int localonly, int self, xp_ctx **xrp); + +#endif /* _CLIXON_XPATH_FUNCTION_H */ diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l index c02dbd3b..79a411a8 100644 --- a/lib/src/clixon_xpath_parse.l +++ b/lib/src/clixon_xpath_parse.l @@ -113,6 +113,7 @@ ncname {namestart}{namechar}* last { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } position { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } count { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } +contains { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } re-match { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } deref { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } derived-from { clixon_xpath_parselval.string = strdup(yytext); return FUNCTIONNAME; } diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index e24c5fbb..4715585e 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -316,6 +316,87 @@ yang_flag_reset(yang_stmt *ys, return 0; } +/*! Get yang xpath for "when"-associated augment + * + * Ie, for yang structures like: augment { when ; ... } + * Will insert new yang nodes at with this special "when" struct (not yang node) + * @param[in] ys Yang statement + * @retval xpath xpath should evaluate to true at validation + * @retval NULL Not set + */ +char* +yang_when_xpath_get(yang_stmt *ys) +{ + return ys->ys_when_xpath; +} + +/*! Set yang xpath and namespace context for "when"-associated augment + * + * Ie, for yang structures like: augment { when ; ... } + * Will insert new yang nodes at with this special "when" struct (not yang node) + * @param[in] ys Yang statement + * @param[in] xpath If set, this xpath should evaluate to true at validation, copied + * @retval 0 OK + * @retval -1 Error + */ +int +yang_when_xpath_set(yang_stmt *ys, + char *xpath) +{ + int retval = -1; + + if (xpath == NULL){ + clicon_err(OE_YANG, EINVAL, "xpath is NULL"); + goto done; + } + if ((ys->ys_when_xpath = strdup(xpath)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + + } + retval = 0; + done: + return retval; +} + +/*! Get yang namespace context for "when"-associated augment + * + * Ie, for yang structures like: augment { when ; ... } + * Will insert new yang nodes at with this special "when" struct (not yang node) + * @param[in] ys Yang statement + * @retval nsc Namespace context + * @note retval is direct pointer, may need to be copied + */ +cvec * +yang_when_nsc_get(yang_stmt *ys) +{ + return ys->ys_when_nsc; +} + +/*! Set yang namespace context for "when"-associated augment + * + * Ie, for yang structures like: augment { when ; ... } + * Will insert new yang nodes at with this special "when" struct (not yang node) + * @param[in] ys Yang statement + * @param[in] nsc Namespace context for when xpath + * @retval 0 OK + * @retval -1 Error + */ +int +yang_when_nsc_set(yang_stmt *ys, + cvec *nsc) +{ + int retval = -1; + + if (nsc && (ys->ys_when_nsc = cvec_dup(nsc)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_dup"); + goto done; + } + retval = 0; + done: + return retval; +} + /* End access functions */ /*! Create new yang specification @@ -390,6 +471,10 @@ ys_free1(yang_stmt *ys, yang_type_cache_free(ys->ys_typecache); ys->ys_typecache = NULL; } + if (ys->ys_when_xpath) + free(ys->ys_when_xpath); + if (ys->ys_when_nsc) + cvec_free(ys->ys_when_nsc); if (self) free(ys); return 0; @@ -523,6 +608,17 @@ ys_cp(yang_stmt *ynew, if (yang_type_cache_cp(ynew, yold) < 0) goto done; } + if (yold->ys_when_xpath) + if ((ynew->ys_when_xpath = strdup(yold->ys_when_xpath)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + if (yold->ys_when_nsc){ + if ((ynew->ys_when_nsc = cvec_dup(yold->ys_when_nsc)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_dup"); + goto done; + } + } for (i=0; iys_len; i++){ yco = yold->ys_stmt[i]; if ((ycn = ys_dup(yco)) == NULL) diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index 74e4ccc1..8e65f33a 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -67,7 +67,7 @@ struct yang_stmt{ enum rfc_6020 ys_keyword; /* See clicon_yang_parse.tab.h */ char *ys_argument; /* String / argument depending on keyword */ - uint16_t ys_flags; /* Flags according to YANG_FLAG_* */ + uint16_t ys_flags; /* Flags according to YANG_FLAG_MARK and others */ yang_stmt *ys_mymodule; /* Shortcut to "my" module. Augmented nodes can belong to other modules than the ancestor module */ @@ -87,7 +87,10 @@ struct yang_stmt{ types as : list */ yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */ + char *ys_when_xpath; /* Special conditional for a "when"-associated augment xpath */ + cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment namespace ctx */ int _ys_vector_i; /* internal use: yn_each */ + }; diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 265239e8..da5a5128 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -89,6 +89,7 @@ #include "clixon_yang_internal.h" #include "clixon_hash.h" #include "clixon_xml.h" +#include "clixon_xml_nsctx.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_data.h" @@ -203,6 +204,10 @@ ys_grouping_resolve(yang_stmt *yuses, * All data nodes defined in the "augment" statement are defined as XML * elements in the XML namespace of the module where the "augment" is * specified. + * + * @note If the augment has a when statement, which is commonplace, the when statement is not copied as + * datanodes are, since it should not apply to the target node. Instead it is added as a special "when" + * struct to the yang statements being inserted. */ static int yang_augment_node(yang_stmt *ys, @@ -214,6 +219,9 @@ yang_augment_node(yang_stmt *ys, yang_stmt *yc0; yang_stmt *yc; yang_stmt *ymod; + yang_stmt *ywhen; + char *wxpath = NULL; /* xpath of when statement */ + cvec *wnsc = NULL; /* namespace context of when statement */ if ((ymod = ys_module(ys)) == NULL){ clicon_err(OE_YANG, 0, "My yang module not found"); @@ -226,6 +234,12 @@ yang_augment_node(yang_stmt *ys, goto done; if (ytarget == NULL) goto ok; + /* Find when statement, if present */ + if ((ywhen = yang_find(ys, Y_WHEN, NULL)) != NULL){ + wxpath = yang_argument_get(ywhen); + if (xml_nsctx_yang(ywhen, &wnsc) < 0) + goto done; + } /* Extend ytarget with ys' schemanode children */ yc0 = NULL; while ((yc0 = yn_each(ys, yc0)) != NULL) { @@ -236,10 +250,19 @@ yang_augment_node(yang_stmt *ys, yc->ys_mymodule = ymod; if (yn_insert(ytarget, yc) < 0) goto done; + /* If there is an associated when statement, add a special when struct to the yang */ + if (ywhen){ + if (yang_when_xpath_set(yc, wxpath) < 0) + goto done; + if (yang_when_nsc_set(yc, wnsc) < 0) + goto done; + } } ok: retval = 0; done: + if (wnsc) + cvec_free(wnsc); return retval; } diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index c9d3cc95..80eaaa27 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -1,8 +1,10 @@ /* * ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2020 Olof Hagsand + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -94,6 +96,7 @@ #include "clixon_hash.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_nsctx.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_options.h" @@ -1007,31 +1010,14 @@ ys_typedef_up(yang_stmt *ys) return (yang_stmt*)ys; } -/*! Find identity yang-stmt - * This is a sanity check of base identity of identity-ref and for identity - * statements when parsing. +/*! Find identity yang-stmt given a name and a yang statement for prefix context * - * Return true if node is identityref and is derived from identity_name - * The derived-from() function returns true if the (first) node (in - * document order in the argument "nodes") is a node of type identityref, - * and its value is an identity that is derived from the identity - * "identity-name" defined in the YANG module "module-name"; otherwise - * it returns false. - * - * Valid values for an identityref are any identities derived from the - * identityref's base identity. - * 1. (base) identity must exist (be found). This is a sanity check - * of the specification and also necessary for identity statements. - * (This is what is done here) - * 2. Check if a given node has value derived from base identity. This is - * a run-time check necessary when validating eg netconf. - * (This is validation) - * 3. Find all valid derived identities from a identityref base identity. - * (This is for cli generation) * @param[in] ys Yang spec of id statement - * @param[in] identity Identity string -check if it exists - * @retval 0 OK + * @param[in] identity Identity string on the form : + * @retval yid yang-stmt of type IDENTITY + * @retval NULL Not found * @see validate_identityref for (2) above + * @see xml_find_identity */ yang_stmt * yang_find_identity(yang_stmt *ys, @@ -1076,6 +1062,44 @@ yang_find_identity(yang_stmt *ys, return yid; } +/*! Find identity yang-stmt given a name and a xml node for prefix context + * + * @param[in] yspec Top-level yang-spec + * @param[in] identity Identity string on the form : + * @param[in] nsc Namespace context for + * @retval yid yang-stmt of type IDENTITY + * @retval NULL Not found + * @see validate_identityref for (2) above + * @see xml_find_identity + */ +yang_stmt * +yang_find_identity_nsc(yang_stmt *yspec, + char *identity, + cvec *nsc) +{ + char *id = NULL; + char *prefix = NULL; + yang_stmt *ymodule; + yang_stmt *yid = NULL; + char *ns = NULL; + + if (nodeid_split(identity, &prefix, &id) < 0) + goto done; + if ((ns = xml_nsctx_get(nsc, prefix)) == NULL) + goto done; + if ((ymodule = yang_find_module_by_namespace(yspec, ns)) == NULL) + goto done; + /* if ymodule is a sub-module, the identity may be found in a + * sub-module of ymod */ + yid = yang_find(ymodule, Y_IDENTITY, id); + done: + if (id) + free(id); + if (prefix) + free(prefix); + return yid; +} + /*! Resolve type restrictions, return constraining parameters * * This is for types with range/length/regexp restrictions of the base type diff --git a/test/test_augment.sh b/test/test_augment.sh index 2ae6809f..97e1f404 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -195,7 +195,7 @@ new "netconf set identity defined in other" expecteof "$clixon_netconf -qf $cfg" 0 " e2 - fddi + mymod:some-new-iftype true if:fddi ]]>]]>" "^]]>]]>$" @@ -207,7 +207,7 @@ new "netconf set identity defined in main" expecteof "$clixon_netconf -qf $cfg" 0 " e3 - fddi + mymod:some-new-iftype true mymod:you ]]>]]>" "^]]>]]>$" @@ -217,10 +217,11 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" " # restconf and augment new "restconf get augment json" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 200 OK" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' new "restconf get augment xml" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' 'e1mymod:some-new-iftypetrue808080e2fdditrueif:fddi808080e3fdditruemymod:you808080' +expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK ' ' +e1mymod:some-new-iftypetrue808080e2mymod:some-new-iftypetrueif:fddi808080e3mymod:some-new-iftypetruemymod:you808080' #e123' @@ -243,7 +244,7 @@ new "restconf POST augment multi-namespace path e2 (middle path)" expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e2 -d "$XML" )" 0 "HTTP/1.1 201 Created" new "restconf GET augment multi-namespace top" -expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"fddi","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"fddi","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' +expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080},{"name":"e2","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:other":"ietf-interfaces:fddi","example-augment:port":80,"example-augment:lport":8080},{"name":"e3","type":"example-augment:some-new-iftype","example-augment:mandatory-leaf":"true","example-augment:me":"you","example-augment:port":80,"example-augment:lport":8080}\]}}' new "restconf GET augment multi-namespace level 1" expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"e1","type":"example-augment:some-new-iftype","example-augment:ospf":{"reference-bandwidth":23},"example-augment:mandatory-leaf":"true","example-augment:port":80,"example-augment:lport":8080}\]}' diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 9fb8fe78..354bd770 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -117,7 +117,7 @@ new "when get config" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of static-routes in module example]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_xpath.sh b/test/test_xpath.sh index f0b64378..debf774b 100755 --- a/test/test_xpath.sh +++ b/test/test_xpath.sh @@ -129,7 +129,7 @@ new "xpath ../../../rt:address-family = 'v6ur:ipv6-unicast'" expecteof "$clixon_util_xpath -f $xml2 -i /aaa/bbb/here2/here" 0 "../../../rt:address-family = 'v6ur:ipv6-unicast'" "^bool:true$" new "xpath /if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" -expecteof "$clixon_util_xpath -f $xml2" 0 "/if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" "^bool:true$" +expecteof "$clixon_util_xpath -D 1 -f $xml2" 0 "/if:interfaces/if:interface[if:name=current()/rt:name]/ip:ipv6/ip:enabled='true'" "^bool:true$" new "xpath rt:address-family='v6ur:ipv6-unicast'" expecteof "$clixon_util_xpath -f $xml2 -i /aaa" 0 "rt:address-family='v6ur:ipv6-unicast'" "^bool:true$" @@ -174,7 +174,6 @@ new "xpath .[name='bar']" expecteof "$clixon_util_xpath -f $xml2 -p .[name='bar'] -i /aaa/bbb/routing/ribs/rib" 0 "" "^nodeset:0:barmyfamily$" new "xpath /aaa/bbb/namespace (namespace is xpath axisname)" -echo "$clixon_util_xpath -f $xml2 -p /aaa/bbb/namespace" expecteof "$clixon_util_xpath -f $xml2 -p /aaa/bbb/namespace" 0 "" "^nodeset:0:urn:example:foo$" # See https://github.com/clicon/clixon/issues/54 @@ -202,8 +201,17 @@ new "xpath bbb[ccc='fie']" expecteof "$clixon_util_xpath -f $xml3 -p bbb[ccc='fie']" 0 "" "^nodeset:$" # Just syntax - no semantic meaning +new "xpath derived-from 10.4.1" +expectpart "$($clixon_util_xpath -f $xml3 -p 'derived-from(../../change-operation,"modify")')" 0 "bool:false" + new "xpath derived-from-or-self" -expecteof "$clixon_util_xpath -f $xml3 -p 'derived-from-or-self(../../change-operation,modify)'" 0 "" "derived-from-or-self" +expectpart "$($clixon_util_xpath -f $xml3 -p 'derived-from-or-self(../../change-operation,"modify")')" 0 "bool:false" + +new "xpath contains" +expectpart "$($clixon_util_xpath -f $xml3 -p "contains(../../objectClass,'BTSFunction') or contains(../../objectClass,'RNCFunction')")" 0 "bool:false" + +# Just syntax - no semantic meaning + rm -rf $dir diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh new file mode 100755 index 00000000..f7d3bd95 --- /dev/null +++ b/test/test_xpath_functions.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# From both: +# XPATH base https://www.w3.org/TR/xpath-10/ +# YANG XPATH functions: https://tools.ietf.org/html/rfc7950 +# Tests: +# - Contains + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf_yang.xml +fyang=$dir/$APPNAME.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + $dir + $IETFRFC + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/backend + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + false + +EOF + +cat < $fyang +module $APPNAME{ + yang-version 1.1; + prefix ex; + namespace "urn:example:clixon"; + identity interface-type; + + identity atm { + base interface-type; + } + identity ethernet { + base interface-type; + } + identity fast-ethernet { + base ethernet; + } + identity gigabit-ethernet { + base ethernet; + } + + container top{ + leaf class { /* contains */ + type string; + } + list mylist{ /* contains */ + key id; + leaf id { + type string; + } + leaf site { + /* If the XPath expression is defined in a substatement to a data + * node that represents configuration, the accessible tree is the + * data in the datastore where the context node exists. + * The "when" statement makes its parent data definition statement conditional. + */ + when "contains(../../class,'foo') or contains(../../class,'bar')"; + type int32; + } + } + } + list interface { /* derived-from */ + key name; + leaf name{ + type string; + } + leaf type { + type identityref { + base interface-type; + } + } + } + augment "/ex:interface" { + when 'derived-from(type, "ex:ethernet")'; + leaf mtu { + type uint32; + } + } + augment "/ex:interface" { + when 'derived-from-or-self(type, "ex:ethernet")'; + leaf crc { + type uint32; + } + } +} +EOF + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + + new "waiting" + wait_backend +fi + +# contains +new "contains: Set site to foo that validates site OK" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "foo1242]]>]]>" "^]]>]]>" + +new "netconf validate OK" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^]]>]]>$" + +new "Set site to fie which invalidates the when contains" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "fie]]>]]>" "^]]>]]>" + +new "netconf validate not OK" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^applicationoperation-failederrorFailed WHEN condition of site in module example]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# derived-from +new "derived-from: Set mtu to interface OK on GE" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0fast-ethernet1500]]>]]>" "^]]>]]>" + +new "netconf validate OK" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^]]>]]>$" + +new "Change type to atm" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0atm]]>]]>" "^]]>]]>" + +new "netconf validate not OK (mtu not allowed)" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition of mtu in module example]]>]]>$" + +new "Change type to ethernet (self)" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0ethernet]]>]]>" "^]]>]]>" + +new "netconf validate not OK (mtu not allowed on self)" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition of mtu in module example]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +# derived-from-or-self +new "derived-from-or-self: Set crc to interface OK on GE" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0fast-ethernet42]]>]]>" "^]]>]]>" + +new "netconf validate OK" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^]]>]]>$" + +new "Change type to atm" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0atm]]>]]>" "^]]>]]>" + +new "netconf validate not OK (crc not allowed)" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^applicationoperation-failederrorFailed augmented WHEN condition of crc in module example]]>]]>$" + +new "Change type to ethernet (self)" +expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "e0ethernet]]>]]>" "^]]>]]>" + +new "netconf validate OK (self)" +expecteof "$clixon_netconf -qf $cfg" 0 "^]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + sudo pkill -u root -f clixon_backend +fi + +rm -rf $dir