diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index 40bc88ae..8e1cc062 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -149,7 +149,10 @@ cxobj *xpath_first(cxobj *xcur, cvec *nsc, const char *xpformat, ...) __attribu cxobj *xpath_first_localonly(cxobj *xcur, const char *xpformat, ...) __attribute__ ((format (printf, 2, 3))); int xpath_vec(cxobj *xcur, cvec *nsc, const char *xpformat, cxobj ***vec, size_t *veclen, ...) __attribute__ ((format (printf, 3, 6))); -int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1, cbuf **cbreason); +int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, + char **xpath1, cvec **nsc1, cbuf **cbreason); +int xpath2canonical1(const char *xpath0, cvec *nsc0, yang_stmt *yspec, int exprstr, + char **xpath1, cvec **nsc1, cbuf **cbreason); int xpath_count(cxobj *xcur, cvec *nsc, const char *xpath, uint32_t *count); int xml2xpath(cxobj *x, cvec *nsc, int spec, int apostrophe, char **xpath); int xpath2xml(char *xpath, cvec *nsc, cxobj *xtop, yang_stmt *ytop, diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index b21dc430..3fccfa4e 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -261,8 +261,8 @@ int yang_flag_set(yang_stmt *ys, uint16_t flag); int yang_flag_reset(yang_stmt *ys, uint16_t flag); yang_stmt *yang_when_get(clixon_handle h, yang_stmt *ys); int yang_when_set(clixon_handle h, yang_stmt *ys, yang_stmt *ywhen); -char *yang_when_xpath_get(yang_stmt *ys); -cvec *yang_when_nsc_get(yang_stmt *ys); +int yang_when_xpath_get(yang_stmt *ys, char **xpath, cvec **nsc); +int yang_when_canonical_xpath_get(yang_stmt *ys, char **xpath, cvec **nsc); const char *yang_filename_get(yang_stmt *ys); int yang_filename_set(yang_stmt *ys, const char *filename); uint32_t yang_linenum_get(yang_stmt *ys); diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index cc35d13d..2eea8f80 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -281,56 +281,94 @@ check_body_namespace(cxobj *x0, * @retval 1 OK * @retval 0 Failed (cbret set) * @retval -1 Error - * @note There may be some combination cases (x0+x1) that are not covered in this function. + * XXX this code is a mess. It tries multiple methods, one after the other. The solution + * is probably to make xpath evaluation namespace aware in combination with XML evaluation + * (3+4) */ static int -check_when_condition(cxobj *x0p, - cxobj *x1, - yang_stmt *y0, - cbuf *cbret) +check_when_condition(cxobj *x0p, + cxobj *x1, + yang_stmt *y0, + cbuf *cbret) { - int retval = -1; + int retval = -1; char *xpath = NULL; cvec *nsc = NULL; int nr; yang_stmt *y = NULL; cbuf *cberr = NULL; cxobj *x1p; + cvec *cnsc = NULL; + cvec *nnsc = NULL; + char *cxpath = NULL; if ((y = y0) != NULL || (y = (yang_stmt*)xml_spec(x1)) != NULL){ - if ((xpath = yang_when_xpath_get(y)) != NULL){ - nsc = yang_when_nsc_get(y); - x1p = xml_parent(x1); - if ((nr = xpath_vec_bool(x1p, nsc, "%s", xpath)) < 0) /* Try request */ - goto done; - if (nr == 0){ - /* Try existing tree */ - if ((nr = xpath_vec_bool(x0p, nsc, "%s", xpath)) < 0) - goto done; - if (nr == 0){ - if ((cberr = cbuf_new()) == NULL){ - clixon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cberr, "Node '%s' tagged with 'when' condition '%s' in module '%s' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)", - yang_argument_get(y), - xpath, - yang_argument_get(ys_module(y))); - if (netconf_unknown_element(cbret, "application", yang_argument_get(y), - cbuf_get(cberr)) < 0) - goto done; - goto fail; - } - } + x1p = xml_parent(x1); + if (yang_when_xpath_get(y, &xpath, &nsc) < 0) + goto done; + if (xpath == NULL) + goto ok; + /* 1. Try yang context for existing xml + * Sufficient for all clixon/controller tests. + * Required for test_augment */ + if ((nr = xpath_vec_bool(x0p, nsc, "%s", xpath)) < 0) + goto done; + if (nr != 0) + goto ok; + if (xml_nsctx_node(x1p, &nnsc) < 0) + goto done; + /* 2. Try yang context for incoming xml */ + if ((nr = xpath_vec_bool(x1p, nsc, "%s", xpath)) < 0) + goto done; + if (nr != 0) + goto ok; + /* 3. Try xml context for incoming xml */ + if ((nr = xpath_vec_bool(x1p, nnsc, "%s", xpath)) < 0) /* Try request */ + goto done; + if (nr != 0) + goto ok; + /* 4. Try xml context for existing xml */ + if ((nr = xpath_vec_bool(x0p, nnsc, "%s", xpath)) < 0) /* Try request */ + goto done; + if (nr != 0) + goto ok; + if (yang_when_canonical_xpath_get(y, &cxpath, &cnsc) < 0) + goto done; + /* 5. Try yang canonical context for incoming xml */ + if ((nr = xpath_vec_bool(x1p, cnsc, "%s", cxpath)) < 0) + goto done; + if (nr != 0) + goto ok; + /* 6. Try yang canonical context for existing xml */ + if ((nr = xpath_vec_bool(x0p, cnsc, "%s", cxpath)) < 0) + goto done; + if (nr != 0) + goto ok; + if ((cberr = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; } + cprintf(cberr, "Node '%s' tagged with 'when' condition '%s' in module '%s' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)", + yang_argument_get(y), + xpath, + yang_argument_get(ys_module(y))); + if (netconf_unknown_element(cbret, "application", yang_argument_get(y), + cbuf_get(cberr)) < 0) + goto done; + goto fail; } + ok: retval = 1; done: if (nsc) cvec_free(nsc); if (cberr) cbuf_free(cberr); + if (cxpath) + free(cxpath); + if (nnsc) + cvec_free(nnsc); return retval; fail: retval = 0; @@ -466,7 +504,6 @@ choice_other_match(cxobj *x0, goto done; } - /*! Modify a base tree x0 with x1 with yang spec y according to operation op * * @param[in] h Clixon handle diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 592d98bb..95f69606 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2091,9 +2091,10 @@ yang_check_when_xpath(cxobj *xn, int xmalloc = 0; /* ugly help variable to clean temporary object */ /* First variant */ - if ((xpath = yang_when_xpath_get(yn)) != NULL){ + if (yang_when_canonical_xpath_get(yn, &xpath, &nsc) < 0) + goto done; + if (xpath != NULL){ x = xp; - nsc = yang_when_nsc_get(yn); *hit = 1; } /* Second variant */ diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 841fc73a..9b174293 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -974,21 +974,78 @@ xpath_vec_bool(cxobj *xcur, return retval; } +/*! Translate literal string to "canonical" form + * + * the prefix according to actual namespace. + * Actually it depends on context if the rewrite should be made. + */ +static int +traverse_canonical_str(xpath_tree *xs, + yang_stmt *yspec, + cvec *nsc0, + cvec *nsc1) +{ + int retval = -1; + char *name = NULL; + char *prefix0 = NULL; + char *prefix1 = NULL; + char *namespace; + yang_stmt *ymod; + cbuf *cb = NULL; + + if (nodeid_split(xs->xs_s0, &prefix0, &name) < 0) + goto done; + if (prefix0 != NULL && name != NULL && + (namespace = xml_nsctx_get(nsc0, prefix0)) != NULL) { + if ((ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL && + (prefix1 = yang_find_myprefix(ymod)) != NULL){ + if (xml_nsctx_get(nsc1, prefix1) == NULL) + if (xml_nsctx_add(nsc1, prefix1, namespace) < 0) + goto done; + free(xs->xs_s0); + xs->xs_s0 = NULL; + if ((cb = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%s:%s", prefix1, name); + if ((xs->xs_s0 = strdup(cbuf_get(cb))) == NULL){ + clixon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (name) + free(name); + if (prefix0) + free(prefix0); + return retval; +} + /*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes * + * Returns new namespace context and rewrites the xpath tree * @param[in] xs Parsed xpath - xpath_tree * @param[in] yspec Yang spec containing all modules, associated with namespaces * @param[in] nsc0 Input namespace context + * @param[in] exprstr Interpret strings as : (primaryexpr/literal/string) * @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free * @param[out] reason Error reason if result is 0 - failed * @retval 1 OK with nsc1 containing the transformed nsc * @retval 0 XPath failure with reason set to why * @retval -1 Fatal Error + * @note Setting str to 1 requires a knowledge of the context of the xpath, ie that strings are + * something like identityref and is safe to translate into canonical form */ static int xpath_traverse_canonical(xpath_tree *xs, yang_stmt *yspec, cvec *nsc0, + int exprstr, cvec *nsc1, cbuf **reason) { @@ -999,9 +1056,13 @@ xpath_traverse_canonical(xpath_tree *xs, yang_stmt *ymod; cbuf *cb = NULL; int ret; - // char *name; switch (xs->xs_type){ + case XP_PRIME_STR: + if (exprstr != 0) + if (traverse_canonical_str(xs, yspec, nsc0, nsc1) < 0) + goto done; + break; case XP_NODE: /* s0 is namespace prefix, s1 is name */ /* Nodetest = * needs no prefix */ if (xs->xs_s1 && strcmp(xs->xs_s1, "*") == 0) @@ -1061,13 +1122,13 @@ xpath_traverse_canonical(xpath_tree *xs, break; } if (xs->xs_c0){ - if ((ret = xpath_traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0) + if ((ret = xpath_traverse_canonical(xs->xs_c0, yspec, nsc0, exprstr, nsc1, reason)) < 0) goto done; if (ret == 0) goto fail; } if (xs->xs_c1){ - if ((ret = xpath_traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0) + if ((ret = xpath_traverse_canonical(xs->xs_c1, yspec, nsc0, exprstr, nsc1, reason)) < 0) goto done; if (ret == 0) goto fail; @@ -1085,8 +1146,10 @@ xpath_traverse_canonical(xpath_tree *xs, * @param[in] xpath0 Input xpath * @param[in] nsc0 Input namespace context * @param[in] yspec Yang spec containing all modules, associated with namespaces + * @param[in] exprstr Interpret strings as : (primaryexpr/literal/string) * @param[out] xpath1 Output xpath. Free after use with free * @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free + * @param[out] cbreason reason if retval = 0 * @retval 1 OK, xpath1 and nsc1 allocated * @retval 0 XPath failure with reason set to why * @retval -1 Fatal Error @@ -1116,12 +1179,13 @@ xpath_traverse_canonical(xpath_tree *xs, * @see xpath2xml */ int -xpath2canonical(const char *xpath0, - cvec *nsc0, - yang_stmt *yspec, - char **xpath1, - cvec **nsc1p, - cbuf **cbreason) +xpath2canonical1(const char *xpath0, + cvec *nsc0, + yang_stmt *yspec, + int exprstr, + char **xpath1, + cvec **nsc1p, + cbuf **cbreason) { int retval = -1; xpath_tree *xpt = NULL; @@ -1133,13 +1197,14 @@ xpath2canonical(const char *xpath0, /* Parse input xpath into an xpath-tree */ if (xpath_parse(xpath0, &xpt) < 0) goto done; + // xpath_tree_print(stderr, xpt); /* Create new nsc */ if ((nsc1 = xml_nsctx_init(NULL, NULL)) == NULL) goto done; /* Traverse tree to find prefixes, transform them to canonical form and * create a canonical network namespace */ - if ((ret = xpath_traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0) + if ((ret = xpath_traverse_canonical(xpt, yspec, nsc0, exprstr, nsc1, cbreason)) < 0) goto done; if (ret == 0) goto fail; @@ -1174,6 +1239,17 @@ xpath2canonical(const char *xpath0, goto done; } +int +xpath2canonical(const char *xpath0, + cvec *nsc0, + yang_stmt *yspec, + char **xpath1, + cvec **nsc1p, + cbuf **cbreason) +{ + return xpath2canonical1(xpath0, nsc0, yspec, 0, xpath1, nsc1p, cbreason); +} + /*! Return a count(xpath) * * @param[in] xcur xml-tree where to search diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 4c91b1aa..1b44b847 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -77,6 +77,8 @@ #include "clixon_log.h" #include "clixon_debug.h" #include "clixon_xml_nsctx.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_data.h" @@ -524,45 +526,68 @@ yang_when_set(clixon_handle h, return retval; } -/*! Get yang xpath for "when"-associated augment +/*! Get 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) + * Inserts 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 - * @note xpath context is PARENT which is different from when actual when child which is - * child itself + * @param[out] xpath + * @param[out] nsc */ -char* -yang_when_xpath_get(yang_stmt *ys) +int +yang_when_xpath_get(yang_stmt *ys, + char **xpath, + cvec **nsc) { - yang_stmt *ywhen; - - if ((ywhen = yang_when_get(NULL, ys)) != NULL) - return yang_argument_get(ywhen); - return NULL; -} - -/*! 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 (caller frees with cvec_free) - * @note retval is direct pointer, may need to be copied - */ -cvec * -yang_when_nsc_get(yang_stmt *ys) -{ - yang_stmt *ywhen; - cvec *wnsc = NULL; + int retval = -1; + yang_stmt *ywhen; + cvec *nsc0 = NULL; if ((ywhen = yang_when_get(NULL, ys)) != NULL) { - if (xml_nsctx_yang(ywhen, &wnsc) < 0) - wnsc = NULL; + if (xml_nsctx_yang(ywhen, nsc) < 0) + goto done; + if (xpath) + *xpath = yang_argument_get(ywhen); } - return wnsc; + retval = 0; + done: + if (nsc0) + cvec_free(nsc0); + return retval; +} + +/*! Get canonical xpath and namespace context for "when"-associated augment + * + * Ie, for yang structures like: augment { when ; ... } + * Inserts new yang nodes at with this special "when" struct (not yang node) + * @param[in] ys Yang statement + * @param[out] xpath + * @param[out] nsc + */ +int +yang_when_canonical_xpath_get(yang_stmt *ys, + char **xpath, + cvec **nsc) +{ + int retval = -1; + yang_stmt *ywhen; + char *xpath0; + cvec *nsc0 = NULL; + + if ((ywhen = yang_when_get(NULL, ys)) != NULL) { + if (xml_nsctx_yang(ywhen, &nsc0) < 0) + goto done; + xpath0 = yang_argument_get(ywhen); + if (xpath0 && nsc0){ + if (xpath2canonical1(xpath0, nsc0, ys_spec(ys), 1, xpath, nsc, NULL) < 0) + goto done; + } + } + retval = 0; + done: + if (nsc0) + cvec_free(nsc0); + return retval; } /*! Get yang filename for error/debug purpose (only modules) diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index 3e7854d6..ef7fb83c 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -14,15 +14,37 @@ fi # canonical namespace xpath tests # need yang modules +cat < $ydir/t.yang +module t { + namespace "urn:example:t"; + prefix t; + + identity baseid { + description "Base identity"; + } + identity des { + base "baseid"; + } +} +EOF + cat < $ydir/a.yang module a{ - namespace "urn:example:a"; - prefix a; - container x{ - leaf xa{ - type string; - } - } + namespace "urn:example:a"; + prefix a; + import t { + prefix t; + } + container x{ + leaf xa{ + type string; + } + leaf xb{ + type identityref { + base "t:baseid"; + } + } + } } EOF @@ -37,6 +59,17 @@ module b{ } } EOF +new "xpath canonical identity predicate" +expectpart "$($clixon_util_xpath -c -y $ydir -p "/x[xb=t2:des]" -n null:urn:example:a -n t2:urn:example:t)" 0 "/a:x\[a:xb=t:des\]" '0 : a = "urn:example:a"' '1 : t = "urn:example:t"' + +new "xpath canonical identity predicate" +expectpart "$($clixon_util_xpath -c -y $ydir -p "/x[xb='t2:des']" -n null:urn:example:a -n t2:urn:example:t)" 0 "/a:x\[a:xb='t:des'\]" '0 : a = "urn:example:a"' '1 : t = "urn:example:t"' + +new "xpath canonical identity boolean" +expectpart "$($clixon_util_xpath -c -y $ydir -p /x/xb='t2:des' -n null:urn:example:a -n t2:urn:example:t)" 0 "/a:x/a:xb=t:des" '0 : a = "urn:example:a"' '1 : t = "urn:example:t"' + +new "xpath canonical identity boolean with quotes" +expectpart "$($clixon_util_xpath -c -y $ydir -p "/x/xb='t2:des'" -n null:urn:example:a -n t2:urn:example:t)" 0 "/a:x/a:xb='t:des'" '0 : a = "urn:example:a"' '1 : t = "urn:example:t"' new "xpath canonical form (already canonical)" expectpart "$($clixon_util_xpath -c -y $ydir -p /a:x/b:y -n a:urn:example:a -n b:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' diff --git a/test/test_xpath_functions.sh b/test/test_xpath_functions.sh index 8745ecbc..dbc44fa8 100755 --- a/test/test_xpath_functions.sh +++ b/test/test_xpath_functions.sh @@ -189,13 +189,13 @@ new "Change type to atm" expecteof_netconf "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO" "e0atm" "" "" new "netconf validate not OK (mtu not allowed)" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(ex:type,'ex:ethernet'))" new "Change type to ethernet (self)" expecteof_netconf "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO" "e0ethernet" "" "" new "netconf validate not OK (mtu not allowed on self)" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(type, \"ex:ethernet\"))" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of mtu in module example (WHEN xpath is derived-from(ex:type,'ex:ethernet'))" new "netconf discard-changes" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" @@ -211,7 +211,7 @@ new "Change type to atm" expecteof_netconf "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO" "e0atm" "" "" new "netconf validate not OK (crc not allowed)" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(type, \"ex:ethernet\"))" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "applicationoperation-failederrorFailed WHEN condition of crc in module example (WHEN xpath is derived-from-or-self(ex:type,'ex:ethernet'))" new "Change type to ethernet (self)" expecteof_netconf "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO" "e0ethernet" "" ""