diff --git a/CHANGELOG.md b/CHANGELOG.md index 554fef62..0fdc9955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,17 @@ Expected: September, 2021 ### New features +* YANG Leafref feature update + * Closer adherence to RFC 7950. Some of this is changed behavior, some is new feature. + * Essentially instead of looking at the referring leaf, context is referred(target) node + * Validation uses referred node + * Validation changed to use type of referred node, instead of just "string" + * Auto-cli + * Changed to use type of referred node for typecheck + * Completion uses referred node + * Required instance / less strict validation + * New: Leafrefs must refer to existing data leaf ONLY IF YANG `required-instance` is true + * Previous: All leafrefs must refer to existing data leaf node * Restconf YANG PATCH according to RFC 8072 (Work in progress) * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h * Thanks to Alan Yaniger for providing this patch @@ -43,6 +54,9 @@ Expected: September, 2021 Users may have to change how they access the system +* See changes under new feature "YANG leafref feature update" + * Validation of referred node type (not referring) + * Leafref required-instance must be set to make strict data-node check * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` * New clixon-config@2021-07-11.yang revision @@ -56,6 +70,8 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [Autocli does not offer completions for leafref to identityref #254](https://github.com/clicon/clixon/issues/254) + * This is a part of YANG Leafref feature update * Fixed: [clixon_netconf errors on client XML Declaration with valid encoding spec](https://github.com/clicon/clixon/issues/250) * Fixed: Yang patterns: \n and other non-printable characters were broken * Example: Clixon interpereted them two characters: `\\ n` instead of ascii 10 diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 2f37a5aa..ee170005 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -106,14 +106,16 @@ You can see which CLISPEC it generates via clixon_cli -D 2: * @param[in] options * @param[in] fraction_digits * @param[out] cb The string where the result format string is inserted. - + * @retval 1 Hide, dont show helptext etc + * @retval 0 OK + * @retval -1 Error * @see expand_dbvar This is where the expand string is used * @note XXX only fraction_digits handled,should also have mincv, maxcv, pattern */ static int cli_expand_var_generate(clicon_handle h, yang_stmt *ys, - enum cv_type cvtype, + char *cvtypestr, int options, uint8_t fraction_digits, cbuf *cb) @@ -129,8 +131,7 @@ cli_expand_var_generate(clicon_handle h, } if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0) goto done; - cprintf(cb, "|<%s:%s", yang_argument_get(ys), - cv_type2str(cvtype)); + cprintf(cb, "|<%s:%s", yang_argument_get(ys), cvtypestr); if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); cprintf(cb, " %s(\"candidate\",\"%s\")>", @@ -254,7 +255,7 @@ yang2cli_var_identityref(yang_stmt *ys, free(id); return retval; } - + /*! Generate range check statements for CLI variables * @param[in] ys Yang statement * @param[in] options Flags field of optional values, eg YANG_OPTIONS_RANGE @@ -495,12 +496,17 @@ yang2cli_var_union_one(clicon_handle h, &ytype, &options, /* resolved type */ &cvv, patterns, NULL, &fraction_digits) < 0) goto done; + if (ytype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } restype = ytype?yang_argument_get(ytype):NULL; if (restype && strcmp(restype, "union") == 0){ /* recursive union */ if (yang2cli_var_union(h, ys, origtype, ytype, helptext, cb) < 0) goto done; } + /* XXX leafref inside union ? */ else { if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0) goto done; @@ -554,9 +560,59 @@ yang2cli_var_union(clicon_handle h, return retval; } +static int +yang2cli_var_leafref(clicon_handle h, + yang_stmt *ys, + yang_stmt *yrestype, + char *helptext, + enum cv_type cvtype, + int options, + cvec *cvv, + cvec *patterns, + uint8_t fraction_digits, + cbuf *cb) +{ + int retval = -1; + char *type; + int completionp; + char *cvtypestr; + int ret; + + /* Give up: use yreferred + * XXX: inline of else clause below + */ + type = yrestype?yang_argument_get(yrestype):NULL; + cvtypestr = cv_type2str(cvtype); + if (type) + completionp = clicon_cli_genmodel_completion(h) && + strcmp(type, "enumeration") != 0 && + strcmp(type, "identityref") != 0 && + strcmp(type, "bits") != 0; + else + completionp = clicon_cli_genmodel_completion(h); + if (completionp) + cprintf(cb, "("); + if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype, + options, cvv, patterns, fraction_digits, cb) < 0) + goto done; + if (completionp){ + if ((ret = cli_expand_var_generate(h, ys, cvtypestr, + options, fraction_digits, + cb)) < 0) + goto done; + if (ret == 0) + yang2cli_helptext(cb, helptext); + cprintf(cb, ")"); + } + retval = 0; + done: + return retval; +} + /*! Generate CLI code for Yang leaf statement to CLIgen variable * @param[in] h Clixon handle - * @param[in] ys Yang statement + * @param[in] ys Yang statement of original leaf + * @param[in] ys Yang statement of referred node for type (leafref) * @param[in] helptext CLI help text * @param[out] cb Buffer where cligen code is written @@ -566,10 +622,15 @@ yang2cli_var_union(clicon_handle h, * sub-types. * eg type union{ type int32; type string } --> (| ) * Another is multiple ranges + * @note leafrefs are troublesome. In this code their cligen type are string, but they should really + * be the type of the referred node. But since the path pointing to the referred node is XML, and + * only YANG is known here, we cannot easily determine the YANG node of the referred XML node, + * and thus its type. */ static int yang2cli_var(clicon_handle h, - yang_stmt *ys, + yang_stmt *ys, + yang_stmt *yreferred, char *helptext, cbuf *cb) { @@ -581,67 +642,79 @@ yang2cli_var(clicon_handle h, cvec *patterns = NULL; uint8_t fraction_digits = 0; enum cv_type cvtype; + char *cvtypestr; int options = 0; - int completionp, result; - char *type; + int result; if ((patterns = cvec_new(0)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); goto done; } - if (yang_type_get(ys, &origtype, &yrestype, + if (yang_type_get(yreferred, &origtype, &yrestype, &options, &cvv, patterns, NULL, &fraction_digits) < 0) goto done; - restype = yrestype?yang_argument_get(yrestype):NULL; + restype = yang_argument_get(yrestype); - if (restype && strcmp(restype, "empty") == 0){ - retval = 0; - goto done; - } - if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0) + if (strcmp(restype, "empty") == 0) + goto ok; + if (clicon_type2cv(origtype, restype, yreferred, &cvtype) < 0) goto done; + cvtypestr = cv_type2str(cvtype); /* Note restype can be NULL here for example with unresolved hardcoded uuid */ - if (restype && strcmp(restype, "union") == 0){ + + if (strcmp(restype, "union") == 0){ /* Union: loop over resolved type's sub-types (can also be recursive unions) */ cprintf(cb, "("); if (yang2cli_var_union(h, ys, origtype, yrestype, helptext, cb) < 0) goto done; if (clicon_cli_genmodel_completion(h)){ - result = cli_expand_var_generate(h, ys, cvtype, - options, fraction_digits, - cb); - if (result < 0) + if ((result = cli_expand_var_generate(h, ys, cvtypestr, + options, fraction_digits,cb)) < 0) goto done; - if (result == 0) - yang2cli_helptext(cb, helptext); + if (result == 0) + yang2cli_helptext(cb, helptext); } cprintf(cb, ")"); } - else{ - type = yrestype?yang_argument_get(yrestype):NULL; - if (type) - completionp = clicon_cli_genmodel_completion(h) && - strcmp(type, "enumeration") != 0 && - strcmp(type, "identityref") != 0 && - strcmp(type, "bits") != 0; - else - completionp = clicon_cli_genmodel_completion(h); - if (completionp) - cprintf(cb, "("); - if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype, - options, cvv, patterns, fraction_digits, cb) < 0) + else if (strcmp(restype,"leafref")==0){ + yang_stmt *ypath; + char *path_arg; + yang_stmt *yref = NULL; + + if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){ + clicon_err(OE_YANG, 0, "No Y_PATH for leafref"); goto done; - if (completionp){ - result = cli_expand_var_generate(h, ys, cvtype, - options, fraction_digits, - cb); - if (result < 0) - goto done; - if (result == 0) - yang2cli_helptext(cb, helptext); - cprintf(cb, ")"); + } + if ((path_arg = yang_argument_get(ypath)) == NULL){ + clicon_err(OE_YANG, 0, "No argument for Y_PATH"); + goto done; + } + if (yang_path_arg(yreferred, path_arg, &yref) < 0) + goto done; + if (yref == NULL){ + /* Give up: use yreferred + */ + if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options, + cvv, patterns, fraction_digits, cb) < 0) + goto done; + } + else { + if (yreferred == yref){ + clicon_err(OE_YANG, 0, "Referred YANG node for leafref path %s points to self", path_arg); + goto done; + } + /* recurse call with new referred node */ + if (yang2cli_var(h, ys, yref, helptext, cb) < 0) + goto done; } } + else{ + if (yang2cli_var_leafref(h, ys, yrestype, helptext, cvtype, options, + cvv, patterns, fraction_digits, cb) < 0) + goto done; + } + + ok: retval = 0; done: if (origtype) @@ -704,7 +777,7 @@ yang2cli_leaf(clicon_handle h, cprintf(cb, ",hide-database-auto-completion{"); extralevel = 1; } - if (yang2cli_var(h, ys, helptext, cb) < 0) + if (yang2cli_var(h, ys, ys, helptext, cb) < 0) goto done; } else{ @@ -718,7 +791,7 @@ yang2cli_leaf(clicon_handle h, } else{ if (!show_tree || key_leaf) { - if (yang2cli_var(h, ys, helptext, cb) < 0) + if (yang2cli_var(h, ys, ys, helptext, cb) < 0) goto done; } } diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 114f178b..c7e524bd 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -73,6 +73,7 @@ #include "cli_common.h" /* internal functions */ /*! Given an xpath encoded in a cbuf, append a second xpath into the first + * * The method reuses prefixes from xpath1 if they exist, otherwise the module prefix * from y is used. Unless the element is .., . * XXX: Predicates not handled @@ -87,10 +88,10 @@ and traverse_canonical */ static int -xpath_myappend(cbuf *xpath0, - char *xpath1, - yang_stmt *y, - cvec *nsc) +xpath_append(cbuf *cb0, + char *xpath1, + yang_stmt *y, + cvec *nsc) { int retval = -1; char **vec = NULL; @@ -100,27 +101,50 @@ xpath_myappend(cbuf *xpath0, char *myprefix; char *id = NULL; char *prefix = NULL; + int initialups = 1; /* If starts with ../../.. */ + char *xpath0; - if (xpath0 == NULL){ - clicon_err(OE_XML, EINVAL, "xpath0 is NULL"); + if (cb0 == NULL){ + clicon_err(OE_XML, EINVAL, "cb0 is NULL"); goto done; } + if (xpath1 == NULL || strlen(xpath1)==0) + goto ok; if ((myprefix = yang_find_myprefix(y)) == NULL) goto done; if ((vec = clicon_strsep(xpath1, "/", &nvec)) == NULL) goto done; - if (xpath1 && xpath1[0] == '/') - cbuf_reset(xpath0); + if (xpath1[0] == '/') + cbuf_reset(cb0); + xpath0 = cbuf_get(cb0); for (i=0; i= 0; j--){ + if (xpath0[j] != '/') + continue; + cbuf_trunc(cb0, j); + break; + } + } + else{ + initialups = 0; + cprintf(cb0, "/%s", id); + } + } + else{ + initialups = 0; + cprintf(cb0, "/%s:%s", prefix?prefix:myprefix, id); + } if (prefix){ free(prefix); prefix = NULL; @@ -130,6 +154,7 @@ xpath_myappend(cbuf *xpath0, id = NULL; } } + ok: retval = 0; done: if (prefix) @@ -182,13 +207,13 @@ expand_dbvar(void *h, cxobj *xbot = NULL; /* xpath, NULL if datastore */ yang_stmt *y = NULL; /* yang spec of xpath */ yang_stmt *yp; - yang_stmt *ytype; - yang_stmt *ypath; char *reason = NULL; cvec *nsc = NULL; int ret; int cvvi = 0; cbuf *cbxpath = NULL; + yang_stmt *ypath; + yang_stmt *ytype; if (argv == NULL || cvec_len(argv) != 2){ clicon_err(OE_PLUGIN, EINVAL, "requires arguments: "); @@ -238,7 +263,6 @@ expand_dbvar(void *h, } if (y==NULL) goto ok; - /* Transform api-path to xpath for netconf */ if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0) goto done; @@ -287,7 +311,7 @@ expand_dbvar(void *h, /* */ /* Extend xpath with leafref path: Append yang_argument_get(ypath) to xpath */ - if (xpath_myappend(cbxpath, yang_argument_get(ypath), y, nsc) < 0) + if (xpath_append(cbxpath, yang_argument_get(ypath), y, nsc) < 0) goto done; } /* Get configuration based on cbxpath */ @@ -297,7 +321,7 @@ expand_dbvar(void *h, clixon_netconf_error(xe, "Get configuration", NULL); goto ok; } - if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0) + if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, cbuf_get(cbxpath)) < 0) goto done; /* Loop for inserting into commands cvec. * Detect duplicates: for ordered-by system assume list is ordered, so you need diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 24fadf6a..028761d1 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -101,6 +101,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_xpath_yang.h b/lib/clixon/clixon_xpath_yang.h new file mode 100644 index 00000000..1a695f80 --- /dev/null +++ b/lib/clixon/clixon_xpath_yang.h @@ -0,0 +1,47 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 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 + * Note: for YANG which is constrained to path-arg as defined in rfc7950 + * See: clixon_xpath.[ch] for full XML XPATH implementation + */ +#ifndef _CLIXON_XPATH_YANG_H +#define _CLIXON_XPATH_YANG_H + +/* + * Prototypes + */ +int yang_path_arg(yang_stmt *ys, const char *xpath, yang_stmt **yref); + +#endif /* _CLIXON_XPATH_YANG_H */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 2032239a..a69f2c2b 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -80,7 +80,8 @@ 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_function.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_xpath_yang.c \ clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 710d209b..99fc11d8 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -88,7 +88,21 @@ * @retval 1 Validation OK * @retval 0 Validation failed * @retval -1 Error - * From rfc7950 Sec 9.9.2 + * From rfc7950 + * Sec 9.9: + * The leafref built-in type is restricted to the value space of some + * leaf or leaf-list node in the schema tree and optionally further + * restricted by corresponding instance nodes in the data tree. The + * "path" substatement (Section 9.9.2) is used to identify the referred + * leaf or leaf-list node in the schema tree. The value space of the + * referring node is the value space of the referred node. + * + * If the "require-instance" property (Section 9.9.3) is "true", there + * MUST exist a node in the data tree, or a node with a default value in + * use (see Sections 7.6.1 and 7.7.2), of the referred schema tree leaf + * or leaf-list node with the same value as the leafref value in a valid + * data tree. + * Sec 9.9.2: * The "path" XPath expression is evaluated in the following context, * in addition to the definition in Section 6.4.1: * o If the "path" statement is defined within a typedef, the context @@ -96,6 +110,7 @@ * references the typedef. (ie ys) * o Otherwise, the context node is the node in the data tree for which * the "path" statement is defined. (ie yc) + * */ static int validate_leafref(cxobj *xt, @@ -105,6 +120,7 @@ validate_leafref(cxobj *xt, { int retval = -1; yang_stmt *ypath; + yang_stmt *yreqi; cxobj **xvec = NULL; cxobj *x; int i; @@ -113,10 +129,18 @@ validate_leafref(cxobj *xt, char *leafbody; cvec *nsc = NULL; cbuf *cberr = NULL; - char *path; + char *path_arg; yang_stmt *ymod; + cg_var *cv; + int require_instance = 0; - if ((leafrefbody = xml_body(xt)) == NULL) + /* require instance */ + if ((yreqi = yang_find(ytype, Y_REQUIRE_INSTANCE, NULL)) != NULL){ + if ((cv = yang_cv_get(yreqi)) != NULL) /* shouldnt happen */ + require_instance = cv_bool_get(cv); + } + /* Find referred XML instances */ + if (require_instance == 0) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ if ((cberr = cbuf_new()) == NULL){ @@ -130,11 +154,16 @@ validate_leafref(cxobj *xt, goto done; goto fail; } + if ((path_arg = yang_argument_get(ypath)) == NULL){ + clicon_err(OE_YANG, 0, "No argument for Y_PATH"); + goto done; + } + if ((leafrefbody = xml_body(xt)) == NULL) + goto ok; /* See comment^: If path is defined in typedef or not */ if (xml_nsctx_node(xt, &nsc) < 0) goto done; - path = yang_argument_get(ypath); - if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path) < 0) + if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path_arg) < 0) goto done; for (i = 0; i < xlen; i++) { x = xvec[i]; @@ -151,7 +180,7 @@ validate_leafref(cxobj *xt, ymod = ys_module(ys); cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d", leafrefbody, - path, + path_arg, yang_argument_get(ymod), yang_linenum_get(ys)); if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) @@ -1029,8 +1058,9 @@ xml_yang_validate_add(clicon_handle h, clicon_err(OE_UNIX, errno, "cv_dup"); goto done; } - /* In the union case, value is parsed as generic REST type, + /* In the union and leafref case, value is parsed as generic REST type, * needs to be reparsed when concrete type is selected + * see ys_cv_validate_union_one and ys_cv_validate_leafref */ if ((body = xml_body(xt)) == NULL){ /* We do not allow ints to be empty. Otherwise NULL strings diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 0422e650..9a57ef99 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2154,6 +2154,10 @@ yang_enum_int_value(cxobj *node, if (yang_type_resolve(ys, ys, ytype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) goto done; + if (yrestype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } if (yrestype==NULL || strcmp(yang_argument_get(yrestype), "enumeration")) goto done; if ((yenum = yang_find(yrestype, Y_ENUM, xml_body(node))) == NULL) @@ -2327,3 +2331,4 @@ yang_check_when_xpath(cxobj *xn, xml_nsctx_free(nsc); return retval; } + diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c index a9906c7d..bb3cb9b7 100644 --- a/lib/src/clixon_xpath_ctx.c +++ b/lib/src/clixon_xpath_ctx.c @@ -90,7 +90,7 @@ ctx_free(xp_ctx *xc) xp_ctx * ctx_dup(xp_ctx *xc0) { - static xp_ctx *xc = NULL; + xp_ctx *xc = NULL; if ((xc = malloc(sizeof(*xc))) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c index b67976a2..d02c4a01 100644 --- a/lib/src/clixon_xpath_eval.c +++ b/lib/src/clixon_xpath_eval.c @@ -480,15 +480,15 @@ xp_eval_predicate(xp_ctx *xc, cxobj *x; xp_ctx *xcc; - if (xs->xs_c0 == NULL){ /* empty */ - if ((xr0 = ctx_dup(xc)) == NULL) - goto done; - } - else{ /* eval previous predicates */ + if (xs->xs_c0 != NULL){ /* eval previous predicates */ if (xp_eval(xc, xs->xs_c0, nsc, localonly, &xr0) < 0) goto done; } - if (xs->xs_c1){ + else{ /* empty */ + if ((xr0 = ctx_dup(xc)) == NULL) + goto done; + } + if (xs->xs_c1){ /* Second child */ /* Loop over each node in the nodeset */ assert (xr0->xc_type == XT_NODESET); if ((xr1 = malloc(sizeof(*xr1))) == NULL){ @@ -537,16 +537,18 @@ xp_eval_predicate(xp_ctx *xc, ctx_free(xrc); } } - assert(xr0||xr1); + if (xr0 == NULL && xr1 == NULL){ + clicon_err(OE_XML, EFAULT, "Internal error: no result produced"); + goto done; + } if (xr1){ *xrp = xr1; xr1 = NULL; } - else - if (xr0){ - *xrp = xr0; - xr0 = NULL; - } + else if (xr0){ + *xrp = xr0; + xr0 = NULL; + } retval = 0; done: if (xr0) @@ -1005,12 +1007,11 @@ xp_eval(xp_ctx *xc, /* // is short for /descendant-or-self::node()/ */ if (xs->xs_int == A_DESCENDANT_OR_SELF) xc->xc_descendant = 1; /* XXX need to set to 0 in sub */ - break; case XP_STEP: /* XP_NODE is first argument -not called explicitly */ if (xp_eval_step(xc, xs, nsc, localonly, xrp) < 0) goto done; - goto ok; + goto ok; /* Skip generic child traverse */ break; case XP_PRED: if (xp_eval_predicate(xc, xs, nsc, localonly, xrp) < 0) @@ -1167,12 +1168,11 @@ xp_eval(xp_ctx *xc, /* Eval second child c1 * Note, some operators like locationpath, need transitive context (use_xr0) */ - if (xs->xs_c1) + if (xs->xs_c1){ if (xp_eval(use_xr0?xr0:xc, xs->xs_c1, nsc, localonly, &xr1) < 0) goto done; - /* Actions after second child - */ - if (xs->xs_c1) + /* Actions after second child + */ switch (xs->xs_type){ case XP_AND: /* combine and and or ops */ if (xp_logop(xr0, xr1, xs->xs_int, &xr2) < 0) @@ -1192,15 +1192,12 @@ xp_eval(xp_ctx *xc, default: break; } + } xc->xc_descendant = 0; -#if 0 - assert(xr0||xr1||xr2); /* for debugging */ -#else if (xr0 == NULL && xr1 == NULL && xr2 == NULL){ clicon_err(OE_XML, EFAULT, "Internal error: no result produced"); goto done; } -#endif if (xr2){ *xrp = xr2; xr2 = NULL; diff --git a/lib/src/clixon_xpath_yang.c b/lib/src/clixon_xpath_yang.c new file mode 100644 index 00000000..f2638fb8 --- /dev/null +++ b/lib/src/clixon_xpath_yang.c @@ -0,0 +1,463 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020-2021 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 + * Note: for YANG which is constrained to path-arg as defined in rfc7950 + * See: clixon_xpath.[ch] for full XML XPATH implementation + */ +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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_yang.h" +#include "clixon_xml.h" +#include "clixon_xml_nsctx.h" +#include "clixon_yang_module.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_function.h" +#include "clixon_xpath_yang.h" + +/* Assume single yang node context */ +struct xp_yang_ctx{ + enum xp_objtype xy_type; + yang_stmt *xy_node; /* If type is XT_NODESET */ + int xy_bool; /* If type is XT_BOOL */ + yang_stmt *xy_initial; /* RFC 7960 10.1.1 extension: for current() */ +}; +typedef struct xp_yang_ctx xp_yang_ctx; + +/* Forward */ +static int xp_yang_eval(xp_yang_ctx *xy, xpath_tree *xptree, xp_yang_ctx **xyr); + +/*! Duplicate xpath yang context */ +xp_yang_ctx * +xy_dup(xp_yang_ctx *xy0) +{ + xp_yang_ctx *xy = NULL; + + if ((xy = malloc(sizeof(*xy))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(xy, 0, sizeof(*xy)); + if (xy0) + *xy = *xy0; + else + xy->xy_type = XT_NODESET; + done: + return xy; +} + +/*! xpath yang equality operator returns true + * + * rfc7950 sec 9.9.2: + * Predicates are used only for constraining the values for the + * key nodes for list entries. Each predicate consists of exactly one + * equality test per key + * Always evaluates to true since there are no instances + */ +static int +xp_yang_op_eq(xp_yang_ctx *xy1, + xp_yang_ctx *xy2, + xp_yang_ctx **xyr) +{ + int retval = -1; + xp_yang_ctx *xy = NULL; + + if ((xy = xy_dup(xy1)) == NULL) + goto done; + if(xy1 == NULL || xy2 == NULL || xy1->xy_node == NULL || xy2->xy_node == NULL){ + clicon_err(OE_YANG, EINVAL, "Error in xy1 or xy2 "); + goto done; + } + xy->xy_type = XT_BOOL; + xy->xy_bool = 1; + xy->xy_node = NULL; + *xyr = xy; + retval = 0; + done: + return retval; +} + +/*! Evaluate leafref PATH-ARG step rule on a YANG tree + * + * @param[in] xy0 Incoming context + * @param[in] xpath_tree XPATH parse-tree + * @param[out] xyr Resulting context + * @retval 0 OK + * @retval -1 Error + * @see xp_eval_step + */ +static int +xp_yang_eval_step(xp_yang_ctx *xy0, + xpath_tree *xptree, + xp_yang_ctx **xyr) +{ + int retval = -1; + xpath_tree *nodetest; /* needed if child */ + char *prefix; + yang_stmt *ys; + xp_yang_ctx *xy = NULL; + + /* Create new xy */ + if ((xy = xy_dup(xy0)) == NULL) + goto done; + ys = xy->xy_node; + switch (xptree->xs_int){ + case A_CHILD: + if ((nodetest = xptree->xs_c0) == NULL){ + clicon_err(OE_YANG, 0, "child step nodetest expected"); + goto done; + } + switch (nodetest->xs_type){ + case XP_NODE: + if ((prefix = nodetest->xs_s0) != NULL){ + if (yang_keyword_get(ys) == Y_MODULE){ /* This means top */ + yang_stmt *ys1 = NULL; + /* XXX: Kludge with prefixes */ + if ((ys1 = yang_find_module_by_prefix(ys, prefix)) == NULL) + ys1 = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix); + if (ys1 != NULL) + ys = ys1; + } + } + xy->xy_node = yang_find_schemanode(ys, nodetest->xs_s1); + if (xy->xy_node == NULL){ + *xyr = xy; + xy = NULL; + goto ok; + } + break; + case XP_NODE_FN: + break; + default: + clicon_err(OE_YANG, 0, "Invalid xpath-tree nodetest: %s", + xpath_tree_int2str(nodetest->xs_type)); + goto done; + break; + } /* nodetest xs_type */ + break; + case A_PARENT: + xy->xy_node = yang_parent_get(ys); + break; + default: + clicon_err(OE_YANG, 0, "Invalid path-arg step: %s", + axis_type_int2str(xptree->xs_int)); + goto done; + break; + } + if (xptree->xs_c1){ + if (xp_yang_eval(xy, xptree->xs_c1, xyr) < 0) + goto done; + } + else{ + *xyr = xy; + xy = NULL; + } + ok: + retval = 0; + done: + if (xy) + free(xy); + return retval; +} + +/*! Evaluate leafref PATH-ARG predicate rule on a YANG tree + * + * @param[in] xy Incoming context + * @param[in] xpath_tree XPATH parse-tree + * @param[out] xyr Resulting context + * @retval 0 OK + * @retval -1 Error + * @see xp_eval_predicate + */ +static int +xp_yang_eval_predicate(xp_yang_ctx *xy, + xpath_tree *xptree, + xp_yang_ctx **xyr) +{ + int retval = -1; + xp_yang_ctx *xy0 = NULL; + xp_yang_ctx *xy1 = NULL; + + if (xptree->xs_c0 != NULL){ /* eval previous predicates */ + if (xp_yang_eval(xy, xptree->xs_c0, &xy0) < 0) + goto done; + } + else{ /* empty */ + if ((xy0 = xy_dup(xy)) == NULL) + goto done; + } + if (xptree->xs_c1){ /* Second child */ + // if ((xy1 = xy_dup(xy)) == NULL) + // goto done; + /* the PredicateExpr is evaluated with the node as the context node */ + if (xp_yang_eval(xy0, xptree->xs_c1, &xy1) < 0) + goto done; + /* Check xrc: if "true" then xyr=xy0? */ + if (xy1->xy_type == XT_BOOL && xy1->xy_bool) + ; + else + xy0->xy_node = NULL; + } + *xyr = xy0; + xy0 = NULL; + retval = 0; + done: + if (xy0) + free(xy0); + if (xy1) + free(xy1); + return retval; +} + +/*! Evaluate leafref PATH-ARG on a YANG tree + * + * @param[in] xy Incoming context + * @param[in] xpath_tree XPATH parse-tree + * @param[out] xyr Resulting context + * @retval 0 OK + * @retval -1 Error + * @see xp_eval + */ +static int +xp_yang_eval(xp_yang_ctx *xy, + xpath_tree *xptree, + xp_yang_ctx **xyr) +{ + int retval = -1; + int use_xy0 = 0; + xp_yang_ctx *xy0 = NULL; + xp_yang_ctx *xy1 = NULL; + xp_yang_ctx *xy2 = NULL; + + /* If empty npodeset, quit, cannot continue */ + if (xy->xy_type == XT_NODESET && xy->xy_node == NULL) + goto ok; + /* Pre-actions before check first child c0 + */ + switch (xptree->xs_type){ + case XP_EXP: + case XP_AND: + case XP_ADD: + case XP_UNION: + if (xptree->xs_c1 != NULL){ + clicon_err(OE_XML, 0, "Function %s having two args is invalid for path-arg", xptree->xs_s0); + goto done; + } + break; + case XP_RELEX: + case XP_PATHEXPR: + case XP_FILTEREXPR: + break; + case XP_LOCPATH: + case XP_NODE: + case XP_NODE_FN: + break; + case XP_RELLOCPATH: + break; + case XP_PRIME_FN: + if (xptree->xs_s0){ + switch (xptree->xs_int){ + case XPATHFN_CURRENT: + if ((*xyr = xy_dup(xy)) == NULL) + goto done; + (*xyr)->xy_node = (*xyr)->xy_initial; + goto ok; + break; + default: + clicon_err(OE_XML, 0, "Function %s invalid for path-arg", xptree->xs_s0); + goto done; + } + } + break; + case XP_ABSPATH: + /* Set context node to top node, and nodeset to that node only */ + xy->xy_node = ys_module(xy->xy_node); + break; + case XP_PRED: + if (xp_yang_eval_predicate(xy, xptree, xyr) < 0) + goto done; + goto ok; /* Skip generic child traverse */ + break; + case XP_STEP: /* XP_NODE is first argument -not called explicitly */ + if (xp_yang_eval_step(xy, xptree, xyr) < 0) + goto done; + goto ok; /* Skip generic child traverse */ + break; + default: /* Here we explicitly fail on node types for those not appearing in path-arg */ + clicon_err(OE_YANG, 0, "Invalid xpath-tree node name: %s", + xpath_tree_int2str(xptree->xs_type)); + goto done; + break; + } + /* Eval first child c0 + */ + if (xptree->xs_c0){ + if (xp_yang_eval(xy, xptree->xs_c0, &xy0) < 0) + goto done; + } + /* Actions between first and second child + */ + switch (xptree->xs_type){ + case XP_RELLOCPATH: + case XP_ABSPATH: + use_xy0++; + break; + case XP_PATHEXPR: + if (xptree->xs_c1) + use_xy0++; + break; + default: + break; + } + /* Eval second child c1 + * Note, some operators like locationpath, need transitive context (use_xr0) + */ + if (xptree->xs_c1){ + if (xp_yang_eval(use_xy0?xy0:xy, xptree->xs_c1, &xy1) < 0) + goto done; + /* Actions after second child + */ + switch (xptree->xs_type){ + case XP_RELEX: /* relexpr --> addexpr | relexpr relop addexpr */ + /* Check op: only EQ allowed in path-arg */ + if (xptree->xs_int != XO_EQ){ + clicon_err(OE_YANG, 0, "Invalid xpath-tree relational operator: %d, only eq allowed", + xptree->xs_int); + goto done; + } + if (xp_yang_op_eq(xy0, xy1, &xy2) < 0) + goto done; + break; + default: + break; + } + } + if (xy0 == NULL && xy1 == NULL && xy2 == NULL){ + clicon_err(OE_XML, EFAULT, "Internal error: no result produced"); + goto done; + } + if (xy2){ + *xyr = xy2; + xy2 = NULL; + } + else if (xy1){ + *xyr = xy1; + xy1 = NULL; + } + else if (xy0){ + *xyr = xy0; + xy0 = NULL; + } + ok: + retval = 0; + done: + if (xy2) + free(xy2); + if (xy1) + free(xy1); + if (xy0) + free(xy0); + return retval; +} + +/*! Resolve a yang node given a start yang node and a leafref path-arg + * + * Leafrefs have a path arguments that are used both for finding referred XML node instances as well + * as finding a referred YANG node for typechecks. + * Such a path-arg is defined as: + * The syntax for a path argument is a subset of the XPath abbreviated + * syntax. Predicates are used only for constraining the values for the + * key nodes for list entries. Each predicate consists of exactly one + * equality test per key, and multiple adjacent predicates MAY be + * present if a list has multiple keys. + * @param[in] ys YANG referring leaf node + * @param[in] path_arg Leafref path-arg + * @param[out] yref YANG referred node + * @note this function uses XPATH parser, which is (much too) general + * @see rfc7950 Sec 9.9.2 + * @see rfc7950 Sec 14 (leafref path) + */ +int +yang_path_arg(yang_stmt *ys, + const char *path_arg, + yang_stmt **yref) +{ + int retval = -1; + xpath_tree *xptree = NULL; + xp_yang_ctx *xyr = NULL; + xp_yang_ctx *xy = NULL; + + if (xpath_parse(path_arg, &xptree) < 0) + goto done; + if ((xy = xy_dup(NULL)) == NULL) + goto done; + xy->xy_node = ys; + xy->xy_initial = ys; + if (xp_yang_eval(xy, xptree, &xyr) < 0) + goto done; + if (xyr != NULL) + *yref = xyr->xy_node; + retval = 0; + done: + if (xptree) + xpath_tree_free(xptree); + if (xyr) + free(xyr); + if (xy) + free(xy); + return retval; +} diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7234fd09..49101cc5 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2054,6 +2054,10 @@ ys_populate_range(clicon_handle h, if (yang_type_resolve(ys, ys, (yang_stmt*)yparent, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; + if (yrestype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } restype = yrestype?yrestype->ys_argument:NULL; if (nodeid_split(yang_argument_get(yparent), NULL, &origtype) < 0) goto done; @@ -2540,6 +2544,7 @@ ys_populate2(yang_stmt *ys, break; case Y_MANDATORY: /* call yang_mandatory() to check if set */ case Y_CONFIG: + case Y_REQUIRE_INSTANCE: if (ys_parse(ys, CGV_BOOL) == NULL) goto done; break; diff --git a/lib/src/clixon_yang_internal.h b/lib/src/clixon_yang_internal.h index a1af0679..06a73d4c 100644 --- a/lib/src/clixon_yang_internal.h +++ b/lib/src/clixon_yang_internal.h @@ -80,6 +80,7 @@ struct yang_stmt{ leaf-list, config: boolean true or false mandatory: boolean true or false + require-instance: true or false fraction-digits for fraction-digits unknown-stmt (optional argument) */ diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index a2b5294b..b5ed24df 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -96,6 +96,9 @@ #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_xml_nsctx.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xpath_yang.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_options.h" @@ -122,7 +125,7 @@ static const map_str2int ytmap[] = { {"int8", CGV_INT8}, {"int16", CGV_INT16}, {"int64", CGV_INT64}, - {"leafref", CGV_STRING}, /* XXX */ + {"leafref", CGV_REST}, /* Is replaced by actual type */ {"uint8", CGV_UINT8}, {"uint16", CGV_UINT16}, {"uint32", CGV_UINT32}, @@ -148,7 +151,7 @@ static const map_str2int ytmap2[] = { {"int32", CGV_INT32}, {"int64", CGV_INT64}, {"int8", CGV_INT8}, - {"leafref", CGV_STRING}, /* XXX */ + {"leafref", CGV_REST}, /* Is replaced by actual type */ {"string", CGV_STRING}, {"uint16", CGV_UINT16}, {"uint32", CGV_UINT32}, @@ -250,7 +253,10 @@ ys_resolve_type(yang_stmt *ys, ys, &resolved, &options, &cvv, patterns, NULL, &fraction) < 0) goto done; - + if (resolved == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } /* Cache the type resolving locally. Only place where this is done. * Why not do it in yang_type_resolve? (compile regexps needs clicon_handle) */ @@ -341,20 +347,12 @@ clicon_type2cv(char *origtype, yang_stmt *ys, enum cv_type *cvtype) { - int retval = -1; + int retval = -1; yang_stmt *ym; *cvtype = CGV_ERR; ym = ys_module(ys); - if (restype != NULL){ - yang2cv_type(restype, cvtype); - if (*cvtype == CGV_ERR){ - clicon_err(OE_YANG, 0, "%s: \"%s\" type not translated", - yang_argument_get(ym), restype); - goto done; - } - } - else{ + if (restype == NULL){ /* * Not resolved, but we can use special cligen types, eg ipv4addr * Note this is a kludge or at least if we intend of using rfc types @@ -366,6 +364,15 @@ clicon_type2cv(char *origtype, goto done; } } + else { + yang2cv_type(restype, cvtype); + if (*cvtype == CGV_ERR){ + clicon_err(OE_YANG, 0, "%s: \"%s\" type not translated", + yang_argument_get(ym), restype); + goto done; + } + + } retval = 0; done: return retval; @@ -742,6 +749,57 @@ cv_validate1(clicon_handle h, static int ys_cv_validate_union(clicon_handle h,yang_stmt *ys, char **reason, yang_stmt *yrestype, char *type, char *val, yang_stmt **ysubp); +static int +ys_cv_validate_leafref(clicon_handle h, + char *body, + yang_stmt *ys, + yang_stmt *yrestype, + yang_stmt **ysub, + char **reason) +{ + int retval = -1; + yang_stmt *yref = NULL; + char *path_arg; + yang_stmt *ypath; + cg_var *cv = NULL; + int ret; + + if ((ypath = yang_find(yrestype, Y_PATH, NULL)) == NULL){ + clicon_err(OE_YANG, 0, "No Y_PATH for leafref"); + goto done; + } + if ((path_arg = yang_argument_get(ypath)) == NULL){ + clicon_err(OE_YANG, 0, "No argument for Y_PATH"); + goto done; + } + if (yang_path_arg(ys, path_arg, &yref) < 0) + goto done; + if (yref == NULL){ + clicon_err(OE_YANG, 0, "No referred YANG node found for leafref path %s", path_arg); + goto done; + } + /* reparse cv with new type */ + if ((cv = cv_dup(yang_cv_get(yref))) == NULL){ + clicon_err(OE_UNIX, errno, "cv_dup"); + goto done; + } + if ((ret = cv_parse1(body, cv, reason)) < 0){ + clicon_err(OE_UNIX, errno, "cv_parse"); + goto done; + } + if (ret == 0) + goto fail; + /* Recursive call to this function, but using refererred YANG node */ + retval = ys_cv_validate(h, cv, yref, ysub, reason); + done: + if (cv) + cv_free(cv); + return retval; + fail: + retval = 0; + goto done; +} + /*! * @param[in] h Clixon handle * @param[in] ys Yang statement (union) @@ -762,7 +820,7 @@ ys_cv_validate_union_one(clicon_handle h, char *val) { int retval = -1; - yang_stmt *yrt; /* union subtype */ + yang_stmt *yrestype; /* union subtype */ int options = 0; cvec *cvv = NULL; cvec *regexps = NULL; @@ -781,12 +839,21 @@ ys_cv_validate_union_one(clicon_handle h, clicon_err(OE_UNIX, errno, "cvec_new"); goto done; } - if (yang_type_resolve(ys, ys, yt, &yrt, &options, &cvv, patterns, regexps, + if (yang_type_resolve(ys, ys, yt, &yrestype, &options, &cvv, patterns, regexps, &fraction) < 0) goto done; - restype = yrt?yang_argument_get(yrt):NULL; + if (yrestype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } + restype = yrestype?yang_argument_get(yrestype):NULL; if (restype && strcmp(restype, "union") == 0){ /* recursive union */ - if ((retval = ys_cv_validate_union(h, ys, reason, yrt, type, val, &ysubt)) < 0) + if ((retval = ys_cv_validate_union(h, ys, reason, yrestype, type, val, &ysubt)) < 0) + goto done; + } + /* Leafref needs to resolve referred node for type information */ + else if (restype && strcmp(restype,"leafref") == 0){ + if ((retval = ys_cv_validate_leafref(h, val, ys, yrestype, NULL, reason)) < 0) /* XXX: ysub? */ goto done; } else { @@ -821,7 +888,7 @@ ys_cv_validate_union_one(clicon_handle h, goto done; } if ((retval = cv_validate1(h, cvt, cvtype, options, cvv, - regexps, yrt, restype, reason)) < 0) + regexps, yrestype, restype, reason)) < 0) goto done; } done: @@ -963,7 +1030,10 @@ ys_cv_validate(clicon_handle h, } /* Note restype can be NULL here for example with unresolved hardcoded uuid */ if (restype && strcmp(restype, "union") == 0){ - assert(cvtype == CGV_REST); + if (cvtype != CGV_REST){ + clicon_err(OE_YANG, 0, "union must be rest cv type but is %d", cvtype); + goto done; + } /* Instead of NULL, give an empty string to validate, this is to avoid cv_parse * errors and may actually be the wrong thing to do. */ @@ -987,6 +1057,28 @@ ys_cv_validate(clicon_handle h, regexps) < 0) goto done; } + /* Leafref needs to resolve referred node for type information + * From rfc7950 Sec 9.9: + * The leafref built-in type is restricted to the value space of some + * leaf or leaf-list node in the schema tree and optionally further + * restricted by corresponding instance nodes in the data tree. The + * "path" substatement (Section 9.9.2) is used to identify the referred + * leaf or leaf-list node in the schema tree. The value space of the + * referring node is the value space of the referred node. + */ + if (restype && strcmp(restype,"leafref") == 0){ + if (cvtype != CGV_REST){ + clicon_err(OE_YANG, 0, "leafref must be rest cv type but is %d", cvtype); + goto done; + } + /* Instead of NULL, give an empty string to validate, this is to avoid cv_parse + * errors and may actually be the wrong thing to do. + */ + if ((val = cv_string_get(cv)) == NULL) + val = ""; + retval = ys_cv_validate_leafref(h, val, ys, yrestype, ysub, reason); + goto done; + } if ((retval = cv_validate1(h, cv, cvtype, options, cvv, regexps, yrestype, restype, reason)) < 0) goto done; @@ -1296,6 +1388,10 @@ yang_type_resolve(yang_stmt *yorig, patterns, regexps, fraction) < 0) goto done; + if (yrestype && *yrestype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } /* appends patterns, overwrites others if any */ if (yang_type_resolve_restrictions(ytype, options, cvv, patterns, fraction) < 0) goto done; @@ -1303,6 +1399,10 @@ yang_type_resolve(yang_stmt *yorig, ok: retval = 0; done: +#if 1 + if (retval == 0 && yrestype != NULL) /* Assert that on success, yrestype is set */ + assert(*yrestype); +#endif if (prefix) free(prefix); if (type) @@ -1383,6 +1483,10 @@ yang_type_get(yang_stmt *ys, if (yang_type_resolve(ys, ys, ytype, yrestype, options, cvv, patterns, regexps, fraction) < 0) goto done; + if (yrestype && *yrestype == NULL){ + clicon_err(OE_YANG, 0, "result-type should not be NULL"); + goto done; + } retval = 0; done: if (type) diff --git a/test/test_cli_leafref.sh b/test/test_cli_leafref.sh new file mode 100755 index 00000000..5fd11eac --- /dev/null +++ b/test/test_cli_leafref.sh @@ -0,0 +1,261 @@ +#!/usr/bin/env bash +# transitive leafref->leafref leafref->identityref completion + +# 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 + +# include err() and new() functions and creates $dir + +cfg=$dir/conf_yang.xml +fyang=$dir/example-leafref.yang + +# Use yang in example + +cat < $cfg + + $cfg + ietf-netconf:startup + /usr/local/share/clixon + $dir + example-leafref + /usr/local/lib/$APPNAME/backend + $APPNAME + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + +EOF + +cat < $fyang +module example-leafref{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + + identity crypto-alg { + description + "Base identity from which all crypto algorithms + are derived. (from: RFC7950 Sec 7.18 and 9.10)"; + } + identity des { + base "ex:crypto-alg"; + description "DES crypto algorithm."; + } + identity des2 { + base "ex:crypto-alg"; + description "DES crypto algorithm."; + } + identity des3 { + base "ex:crypto-alg"; + description "Triple DES crypto algorithm."; + } + /* Basic config data */ + container table{ + list parameter{ + key name; + leaf name{ + type uint32; + } + leaf value{ + type string; + } + } + } + /* first level leafref */ + container leafrefs { + description "Relative path"; + list leafref{ + key name; + leaf name { + type leafref{ + path "../../../table/parameter/name"; + } + } + } + } + /* first level leafref absolute */ + container leafrefstop { + description "Same but absolute path"; + list leafref{ + key name; + leaf name { + type leafref{ + path "/table/parameter/name"; + } + } + } + } + /* first level leafref require-instance */ + container leafrefsreqi { + description "Same but absolute path"; + list leafref{ + key name; + leaf name { + type leafref{ + path "/table/parameter/name"; + require-instance true; + } + } + } + } + /* first level identityrefs */ + container identityrefs { + list identityref{ + key name; + leaf name { + type identityref{ + base "ex:crypto-alg"; + } + } + } + } + /* second level leafref */ + container leafrefs2 { + list leafref{ + key name; + leaf name { + type leafref{ + path "../../../leafrefs/leafref/name"; + } + } + } + } + /* second level identityref */ + container identityrefs2 { + list identityref{ + key name; + leaf name { + type leafref{ + path "../../../identityrefs/identityref/name"; + } + } + } + } +} +EOF + +cat < $dir/startup_db +<${DATASTORE_TOP}> + + + 91 + + + 92 + + + 93 + +
+ +EOF + +new "test params: -f $cfg -s startup" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +new "expand identityref 1st level" +expectpart "$(echo "set identityrefs identityref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "ex:des" "ex:des2" "ex:des3" + +new "expand leafref 1st level" +expectpart "$(echo "set leafrefs leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93" + +new "expand leafref top" +expectpart "$(echo "set leafrefstop leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93" + +new "expand leafref require-instance" +expectpart "$(echo "set leafrefsreqi leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "92" "93" + +# First level id/leaf refs +new "set identityref des" +expectpart "$($clixon_cli -1 -f $cfg set identityrefs identityref ex:des)" 0 "^$" + +new "set identityref des3" +expectpart "$($clixon_cli -1 -f $cfg set identityrefs identityref ex:des3)" 0 "^$" + +new "set leafref 91" +expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 91)" 0 "^$" + +new "set leafref 93" +expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 93)" 0 "^"$ + +new "cli commit" +expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$" + +new "set leafref str (expect failure)" +expectpart "$($clixon_cli -1 -l o -f $cfg set leafrefs leafref str)" 255 "'str' is not a number" + +# Make a netconf request to set wrong type to fail in validate +new "netconf set leafref str wrong type" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOstrmerge]]>]]>" "^]]>]]>$" + +new "cli validate" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "'str' is not a number name" + +new "cli discard" +expectpart "$($clixon_cli -1 -f $cfg -l o discard)" 0 "" + +new "set leafref 99 (non-existent)" +expectpart "$($clixon_cli -1 -f $cfg set leafrefs leafref 99)" 0 "^"$ + +new "cli commit" +expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$" + +# require-instance +new "set leafref require-instance 99 (non-existent)" +expectpart "$($clixon_cli -1 -f $cfg set leafrefsreqi leafref 99)" 0 "^"$ + +new "cli validate expect failure" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Leafref validation failed: No leaf 99 matching path" + +new "cli discard" +expectpart "$($clixon_cli -1 -f $cfg -l o discard)" 0 "" + +# Second level id/leaf refs +new "expand identityref 2nd level" +expectpart "$(echo "set identityrefs2 identityref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "ex:des" "ex:des2" "ex:des3" + +new "expand leafref 2nd level" +expectpart "$(echo "set leafrefs2 leafref ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 "91" "93" --not-- "92" + +new "set identityref2 des" +expectpart "$($clixon_cli -1 -f $cfg set identityrefs2 identityref ex:des)" 0 "^$" + +new "set leafref2 91" +expectpart "$($clixon_cli -1 -f $cfg set leafrefs2 leafref 91)" 0 "^$" + +new "cli commit" +expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$" + +new "show config" +expectpart "$($clixon_cli -1 -f $cfg -l o show config cli)" 0 "set table parameter 91" "set table parameter 92" "set table parameter 93" "set leafrefs leafref 91" "set leafrefs leafref 93" "set identityrefs identityref ex:des" "set identityrefs identityref ex:des3" "set leafrefs2 leafref 91" "set identityrefs2 identityref ex:des" --not-- "set identityrefs identityref ex:des2" "set leafrefs leafref 92" + +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 +fi + +rm -rf $dir + +new "endtest" +endtest diff --git a/test/test_leafref.sh b/test/test_leafref.sh index a86e411d..a8ba47b0 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -47,11 +47,13 @@ module example{ description "Absolute references existing interfaces in if module"; type leafref { path "/if:interfaces/if:interface/if:name"; + require-instance true; } } leaf relname { type leafref { path "../../if:interfaces/if:interface/if:name"; + require-instance true; } } leaf address { @@ -59,12 +61,14 @@ module example{ type leafref { path "../../if:interfaces/if:interface[if:name = current()/../relname]" + "/ip:ipv4/ip:address/ip:ip"; + require-instance true; } } leaf wrong { description "References leading nowhere in yang"; type leafref { path "/ip:interfaces/ip:interface/ip:name"; + require-instance true; } } } @@ -76,6 +80,7 @@ module example{ leaf template{ type leafref{ path "/sender/name"; + require-instance true; } } } diff --git a/test/test_leafref_augment.sh b/test/test_leafref_augment.sh index 4c5032a2..9efd0b69 100755 --- a/test/test_leafref_augment.sh +++ b/test/test_leafref_augment.sh @@ -59,12 +59,14 @@ module leafref{ description "For testing leafref across augment and grouping"; type leafref { path "/ex:sender/ex:name"; + require-instance true; } } typedef sender-ref-local { description "For testing leafref local"; type leafref { path "/example:sender/example:name"; + require-instance true; } } list sender{ @@ -83,7 +85,6 @@ module leafref{ description "top-level ref (right prefix)"; type sender-ref-local; } - } } EOF @@ -113,6 +114,7 @@ module augment{ leaf name{ type leafref { path "/ex:sender/ex:name"; + require-instance true; } } } diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 31ce8cd5..990e4878 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -59,6 +59,7 @@ module leafref{ leaf ref{ type leafref { path "/ex:sender-config/ex:name"; + require-instance true; } } }