/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren Copyright (C) 2017-2019 Olof Hagsand Copyright (C) 2020-2022 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 ***** * * Translation / mapping code between formats */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clixon */ #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_string.h" #include "clixon_map.h" #include "clixon_yang.h" #include "clixon_xml.h" #include "clixon_log.h" #include "clixon_debug.h" #include "clixon_err.h" #include "clixon_options.h" #include "clixon_data.h" #include "clixon_yang_module.h" #include "clixon_plugin.h" #include "clixon_xml_nsctx.h" #include "clixon_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_netconf_lib.h" #include "clixon_xml_sort.h" #include "clixon_yang_type.h" #include "clixon_text_syntax.h" #include "clixon_xml_io.h" #include "clixon_xml_map.h" /* Local types */ /* Merge code needs a two-phase pass where objects subject to merge are first checked for, * the actually inserted. * This is to mitigate a search problem where objects inserted are among the ones checked for */ typedef struct { cxobj *mt_x0c; cxobj *mt_x1c; yang_stmt *mt_yc; } merge_twophase; /* Forward declaration */ static int xml_diff1(cxobj *x0, cxobj *x1, cxobj ***x0vec, int *x0veclen, cxobj ***x1vec, int *x1veclen, cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen); /*! Is attribute and is either of form xmlns="", or xmlns:x="" */ int isxmlns(cxobj *x) { char *prefix = NULL; if (xml_type(x) != CX_ATTR) return 0; if (strcmp(xml_name(x), "xmlns")==0) return 1; if ((prefix = xml_prefix(x)) != NULL && strcmp(xml_prefix(x), "xmlns")==0) return 1; return 0; } /*! Translate a single xml node to a cligen variable vector. Note not recursive * * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt * @param[out] cvv CLIgen variable vector. Should be freed by cvec_free() * @retval 0 Everything OK, cvv allocated and set * @retval -1 Something wrong, clixon_err() called to set error. No cvv returned * @note cvv Should be freed by cvec_free() after use. * 'Not recursive' means that only one level of XML bodies is translated to cvec:s. * If range is wriong (eg 1000 for uint8) a warning is logged, the value is * skipped, and continues. * yang is needed to know which type an xml element has. * Example: 23 88 99 --> b:23, c:88 * @see cvec2xml */ int xml2cvec(cxobj *xt, yang_stmt *yt, cvec **cvv0) { int retval = -1; cvec *cvv = NULL; cxobj *xc; /* xml iteration variable */ yang_stmt *ys; /* yang spec */ cg_var *cv; cg_var *ycv; char *body; char *reason = NULL; int ret; char *name; xc = NULL; /* Tried to allocate whole cvv here, but some cg_vars may be invalid */ if ((cvv = cvec_new(0)) == NULL){ clixon_err(OE_UNIX, errno, "cvec_new"); goto err; } xc = NULL; /* Go through all children of the xml tree */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){ name = xml_name(xc); if ((ys = yang_find_datanode(yt, name)) == NULL){ clixon_debug(CLIXON_DBG_ALWAYS, "yang sanity problem: %s in xml but not present in yang under %s", name, yang_argument_get(yt)); if ((body = xml_body(xc)) != NULL){ if ((cv = cv_new(CGV_STRING)) == NULL){ clixon_err(OE_PLUGIN, errno, "cv_new"); goto err; } cv_name_set(cv, name); if ((ret = cv_parse1(body, cv, &reason)) < 0){ clixon_err(OE_PLUGIN, errno, "cv_parse %s",name); goto err; } /* If value is out-of-range, log and skip value, and continue */ if (ret == 0){ clixon_log(NULL, LOG_WARNING, "cv_parse %s: %s", name, reason); if (reason) free(reason); } else cvec_append_var(cvv, cv); /* Add to variable vector */ cv_free(cv); } } else if ((ycv = yang_cv_get(ys)) != NULL){ if ((body = xml_body(xc)) != NULL){ if ((cv = cv_new(CGV_STRING)) == NULL){ clixon_err(OE_PLUGIN, errno, "cv_new"); goto err; } if (cv_cp(cv, ycv) < 0){ clixon_err(OE_PLUGIN, errno, "cv_cp"); goto err; } if ((ret = cv_parse1(body, cv, &reason)) < 0){ clixon_err(OE_PLUGIN, errno, "cv_parse: %s", name); goto err; } if (ret == 0){ clixon_log(NULL, LOG_WARNING, "cv_parse %s: %s", name, reason); if (reason) free(reason); } else cvec_append_var(cvv, cv); /* Add to variable vector */ cv_free(cv); } } } if (clixon_debug_isset(CLIXON_DBG_XML | CLIXON_DBG_DETAIL)){ clixon_debug(CLIXON_DBG_ALWAYS, "cvv:"); cvec_print(stderr, cvv); } *cvv0 = cvv; return 0; err: if (cvv) cvec_free(cvv); return retval; } /*! Translate a cligen variable vector to an XML tree with depth one * * @param[in] cvv CLIgen variable vector. Should be freed by cvec_free() * @param[in] toptag The XML tree in xt will have this XML tag * @param[in] xt Parent, or NULL * @param[out] xt Pointer to XML tree containing one top node. Should be freed with xml_free * @retval 0 Everything OK, cvv allocated and set * @retval -1 Something wrong, clixon_err() called to set error. No xt returned * @see xml2cvec * @see cvec2xml This does more but has an internal xml2cvec translation */ int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0) { int retval = -1; cxobj *xt = NULL; cxobj *xn; cxobj *xb; cg_var *cv; char *val; int len=0; int i; cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) len++; if ((xt = xml_new(toptag, xp, CX_ELMNT)) == NULL) goto err; if (xml_childvec_set(xt, len) < 0) goto err; cv = NULL; i = 0; while ((cv = cvec_each(cvv, cv)) != NULL) { if (cv_type_get(cv)==CGV_ERR || cv_name_get(cv) == NULL) continue; if ((xn = xml_new(cv_name_get(cv), NULL, CX_ELMNT)) == NULL) /* this leaks */ goto err; xml_parent_set(xn, xt); xml_child_i_set(xt, i++, xn); if ((xb = xml_new("body", xn, CX_BODY)) == NULL) /* this leaks */ goto err; val = cv2str_dup(cv); xml_value_set(xb, val); /* this leaks */ if (val) free(val); } *xt0 = xt; return 0; err: if (xt) xml_free(xt); return retval; } /*! Handle order-by user(leaf)list for xml_diff * * Loop over sublists started by x0c and x1c respectively until end or yang is no longer yc * Just mark all nodes from x0c to end as DEL and all nodes from x1c as ADD * @param[in] x0 First XML tree * @param[in] x1 Second XML tree * @param[in] x0c Start of sublist in first XML tree * @param[in] x1c Start of sublist in second XML tree * @param[in] yc Yang of ordered-by user (leaf)list * @retval 0 Ok * @retval -1 rror */ static int xml_diff_ordered_by_user(cxobj *x0, cxobj *x1, cxobj *x0c, cxobj *x1c, yang_stmt *yc) { int retval = -1; cxobj *xi; cxobj *xj; /* Simpler algoithm: Just delete whole old list and add new list if ANY changes */ xi = x0c; do { xml_flag_set(xi, XML_FLAG_DEL); } while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL && xml_spec(xi) == yc); xj = x1c; do { xml_flag_set(xj, XML_FLAG_ADD); } while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL && xml_spec(xj) == yc); retval = 0; // done: return retval; } /*! Recursive help function to compute differences between two xml trees * * @param[in] x0 First XML tree * @param[in] x1 Second XML tree * @param[out] x0vec Pointervector to XML nodes existing in only first tree * @param[out] x0veclen Length of first vector * @param[out] x1vec Pointervector to XML nodes existing in only second tree * @param[out] x1veclen Length of x1vec vector * @param[out] changed_x0 Pointervector to XML nodes changed orig value * @param[out] changed_x1 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector * @retval 0 Ok * @retval -1 Error * Algorithm to compare two sorted lists A, B: * A 0 1 2 3 5 6 * B 0 2 4 5 6 * Let (a, b) be first elements of (A, B) respectively(*) * a = b : EITHER leafs: a!=b : add a in changed_x0, b in changed_x1, * OR: Set (A,B) to children of (a,b) and call algorithm recursively * , get next (a,b) * a < b : add a in x0, get next a * a > b : add b in x1, get next b * (*) "comparing" a&b here is made by xml_cmp() which judges equality from a structural * perspective, ie both have the same yang spec, if they are lists, they have the * the same keys. NOT that the values are equal! * @see xml_diff2cbuf, clixon_text_diff2cbuf for +/- diff for XML and TEXT formats * @see text_diff2cbuf for curly * @see xml_tree_equal Equal or not * @note reordering in ordered-by user is NOT supported */ static int xml_diff1(cxobj *x0, cxobj *x1, cxobj ***x0vec, int *x0veclen, cxobj ***x1vec, int *x1veclen, cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen) { int retval = -1; cxobj *x0c = NULL; /* x0 child */ cxobj *x1c = NULL; /* x1 child */ yang_stmt *y0c; yang_stmt *y1c; char *b0; char *b1; int eq; cxobj *xi; cxobj *xj; int extflag; /* Traverse x0 and x1 in lock-step */ x0c = x1c = NULL; x0c = xml_child_each(x0, x0c, CX_ELMNT); x1c = xml_child_each(x1, x1c, CX_ELMNT); for (;;){ if (x0c == NULL && x1c == NULL) goto ok; y0c = NULL; y1c = NULL; /* If cl:ignore-compare extension, return equal */ if (x0c && (y0c = xml_spec(x0c)) != NULL){ if (yang_extension_value(y0c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) goto done; if (extflag){ /* skip */ if (x1c) { x0c = xml_child_each(x0, x0c, CX_ELMNT); continue; } else goto ok; } } if (x1c && (y1c = xml_spec(x1c)) != NULL){ if (yang_extension_value(y1c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) goto done; if (extflag){ /* skip */ if (x1c) { x1c = xml_child_each(x1, x1c, CX_ELMNT); continue; } else goto ok; } } if (x0c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; x1c = xml_child_each(x1, x1c, CX_ELMNT); continue; } else if (x1c == NULL){ if (cxvec_append(x0c, x0vec, x0veclen) < 0) goto done; x0c = xml_child_each(x0, x0c, CX_ELMNT); continue; } /* Both x0c and x1c exists, check if they are yang-equal. */ eq = xml_cmp(x0c, x1c, 0, 0, NULL); /* override ordered-by user with special look-ahead checks */ if (eq && y0c && y1c && y0c == y1c && yang_find(y0c, Y_ORDERED_BY, "user")){ if (xml_diff_ordered_by_user(x0, x1, x0c, x1c, y0c) < 0) goto done; /* Add all in x0 marked as DELETE in x0vec * Flags can remain: XXX should apply to all */ xi = x0c; do { if (xml_flag(xi, XML_FLAG_DEL)){ if (cxvec_append(xi, x0vec, x0veclen) < 0) goto done; } } while ((xi = xml_child_each(x0, xi, CX_ELMNT)) != NULL && xml_spec(xi) == y0c); x0c = xi; /* Add all in x1 marked as ADD in x1vec */ xj = x1c; do { if (xml_flag(xj, XML_FLAG_ADD)) if (cxvec_append(xj, x1vec, x1veclen) < 0) goto done; } while ((xj = xml_child_each(x1, xj, CX_ELMNT)) != NULL && xml_spec(xj) == y1c); x1c = xj; continue; } else if (eq < 0){ if (cxvec_append(x0c, x0vec, x0veclen) < 0) goto done; x0c = xml_child_each(x0, x0c, CX_ELMNT); continue; } else if (eq > 0){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; x1c = xml_child_each(x1, x1c, CX_ELMNT); continue; } else{ /* equal */ /* xml-spec NULL could happen with anydata children for example, * if so, continute compare children but without yang */ if (y0c && y1c && y0c != y1c){ /* choice */ if (cxvec_append(x0c, x0vec, x0veclen) < 0) goto done; if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; } else if (y0c && yang_keyword_get(y0c) == Y_LEAF){ /* if x0c and x1c are leafs w bodies, then they may be changed */ b0 = xml_body(x0c); b1 = xml_body(x1c); if (b0 == NULL && b1 == NULL) ; else if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0 ){ if (cxvec_append(x0c, changed_x0, changedlen) < 0) goto done; (*changedlen)--; /* append two vectors */ if (cxvec_append(x1c, changed_x1, changedlen) < 0) goto done; } } else if (xml_diff1(x0c, x1c, x0vec, x0veclen, x1vec, x1veclen, changed_x0, changed_x1, changedlen)< 0) goto done; } x0c = xml_child_each(x0, x0c, CX_ELMNT); x1c = xml_child_each(x1, x1c, CX_ELMNT); } ok: retval = 0; done: return retval; } /*! Compute differences between two xml trees * * @param[in] x0 First XML tree * @param[in] x1 Second XML tree * @param[out] first Pointervector to XML nodes existing in only first tree * @param[out] firstlen Length of first vector * @param[out] second Pointervector to XML nodes existing in only second tree * @param[out] secondlen Length of second vector * @param[out] changed_x0 Pointervector to XML nodes changed orig value * @param[out] changed_x1 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector * @retval 0 OK * @retval -1 Error * All xml vectors should be freed after use. * @see xml_tree_equal same algorithm but do not bother with what has changed * @see clixon_xml_diff_print same algorithm but print in +/- diff format */ int xml_diff(cxobj *x0, cxobj *x1, cxobj ***first, int *firstlen, cxobj ***second, int *secondlen, cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen) { int retval = -1; *firstlen = 0; *secondlen = 0; *changedlen = 0; if (x0 == NULL && x1 == NULL) return 0; if (x1 == NULL){ if (cxvec_append(x0, first, firstlen) < 0) goto done; goto ok; } if (x0 == NULL){ if (cxvec_append(x0, second, secondlen) < 0) goto done; goto ok; } if (xml_diff1(x0, x1, first, firstlen, second, secondlen, changed_x0, changed_x1, changedlen) < 0) goto done; ok: retval = 0; done: return retval; } /*! Compute if two XML trees are equal or not * * @param[in] x0 First XML tree * @param[in] x1 Second XML tree * @retval 1 Not equal * @retval 0 Equal * @see xml_diff which returns diff sets * @see xml_diff2cbuf Diff buffer in XML * @see text_diff2cbuf Diff buffer in curly */ int xml_tree_equal(cxobj *x0, cxobj *x1) { int retval = 1; /* Not equal */ int eq; yang_stmt *y0c; yang_stmt *y1c; char *b0; char *b1; cxobj *x0c; /* x0 child */ cxobj *x1c; /* x1 child */ int extflag = 0; /* Traverse x0 and x1 in lock-step */ x0c = x1c = NULL; x0c = xml_child_each(x0, x0c, CX_ELMNT); x1c = xml_child_each(x1, x1c, CX_ELMNT); for (;;){ if (x0c == NULL && x1c == NULL) goto ok; y0c = NULL; y1c = NULL; /* If cl:ignore-compare extension, return equal */ if (x0c && (y0c = xml_spec(x0c)) != NULL){ if (yang_extension_value(y0c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) goto done; if (extflag){ /* skip */ if (x1c) { x0c = xml_child_each(x0, x0c, CX_ELMNT); continue; } else goto ok; } } if (x1c && (y1c = xml_spec(x1c)) != NULL){ if (yang_extension_value(y1c, "ignore-compare", CLIXON_LIB_NS, &extflag, NULL) < 0) goto done; if (extflag){ /* skip */ if (x1c) { x1c = xml_child_each(x1, x1c, CX_ELMNT); continue; } else goto ok; } } if (x0c == NULL) goto done; else if (x1c == NULL) goto done; /* Both x0c and x1c exists, check if they are yang-equal. */ if ((eq = xml_cmp(x0c, x1c, 0, 0, NULL)) != 0){ goto done; } else{ /* equal */ /* xml-spec NULL could happen with anydata children for example, * if so, continue compare children but without yang */ if (y0c && y1c && y0c != y1c){ /* choice */ goto done; } else if (y0c && yang_keyword_get(y0c) == Y_LEAF){ /* if x0c and x1c are leafs w bodies, then they may be changed */ b0 = xml_body(x0c); b1 = xml_body(x1c); if (b0 == NULL && b1 == NULL) ; else if (b0 == NULL || b1 == NULL || strcmp(b0, b1) != 0 ){ goto done; } } else { eq = xml_tree_equal(x0c, x1c); if (eq) goto done; } } x0c = xml_child_each(x0, x0c, CX_ELMNT); x1c = xml_child_each(x1, x1c, CX_ELMNT); } ok: retval = 0; done: return retval; } /*! Prune everything that does not pass test or have at least a child* does not * * @param[in] xt XML tree with some node marked * @param[in] flag Which flag to test for * @param[in] test 1: test that flag is set, 0: test that flag is not set * @param[out] upmark Set if a child (recursively) has marked set. * @retval 0 OK * @retval -1 Error * The function removes all branches that does not pass the test * Purge all nodes that dont have MARK flag set recursively. * Save all nodes that is MARK:ed or have at least one (grand*)child that is MARKed * @code * xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL); * @endcode * @note This function seems a little too complex semantics * @see xml_tree_prune_flags1 for a simpler variant */ int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark) { int retval = -1; int submark; int mark; cxobj *x; cxobj *xprev; int iskey; int anykey=0; yang_stmt *yt; mark = 0; yt = xml_spec(xt); /* can be null */ x = NULL; xprev = x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if (xml_flag(x, flag) == (test?flag:0)){ /* Pass test */ mark++; xprev = x; continue; /* mark and stop here */ } /* If it is key dont remove it yet (see second round) */ if (yt){ if ((iskey = yang_key_match(yt, xml_name(x), NULL)) < 0) goto done; if (iskey){ anykey++; xprev = x; /* skip if this is key */ continue; } } if (xml_tree_prune_flagged_sub(x, flag, test, &submark) < 0) goto done; /* if xt is list and submark anywhere, then key subs are also marked */ if (submark) mark++; else{ /* Safe with xml_child_each if last */ if (xml_purge(x) < 0) goto done; x = xprev; } xprev = x; } /* Second round: if any keys were found, and no marks detected, purge now */ if (anykey && !mark){ x = NULL; xprev = x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { /* If it is key remove it here */ if (yt){ if ((iskey = yang_key_match(yt, xml_name(x), NULL)) < 0) goto done; if (iskey && xml_purge(x) < 0) goto done; x = xprev; } xprev = x; } } retval = 0; done: if (upmark) *upmark = mark; return retval; } /*! Prune everything that passes test recursively * * @param[in] xt XML tree with some node marked * @param[in] flags Flags set * @param[in] mask Which flags to test for * @retval 0 OK * @retval -1 Error * The function removes all branches that does pass test * @code * xml_tree_prune_flags(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT); * @endcode * @see xml_tree_prune_flags1 */ int xml_tree_prune_flags(cxobj *xt, int flags, int mask) { return xml_tree_prune_flags1(xt, flags, mask, 1, NULL); } /*! Prune everything that passes test * * @param[in] xt XML tree with some node marked * @param[in] flags Flags set * @param[in] mask Which flags to test for * @param[in] recurse 0: one level only, 1: recursive * @param[out] removed Number of entities removed * @retval 0 OK * @retval -1 Error * The function removes all branches that does pass test * @code * xml_tree_prune_flags1(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT, 1); * @endcode */ int xml_tree_prune_flags1(cxobj *xt, int flags, int mask, int recursive, int *removed) { int retval = -1; cxobj *x; cxobj *xprev; x = NULL; xprev = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if (xml_flag(x, mask) == flags){ /* Pass test means purge */ if (xml_purge(x) < 0) goto done; x = xprev; if (removed) (*removed)++; continue; } if (recursive) if (xml_tree_prune_flags1(x, flags, mask, 1, removed) < 0) goto done; xprev = x; } retval = 0; done: return retval; } /*! Change namespace of XML node * * @param[in] x XML node * @param[in] ns Change to this namespace (if ns does not exist in tree) * @param[in] prefix If change, use this prefix * @param 0 OK * @param -1 Error */ int xml_namespace_change(cxobj *x, char *ns, char *prefix) { int retval = -1; char *ns0 = NULL; /* existing namespace */ char *prefix0 = NULL; /* existing prefix */ cxobj *xp; ns0 = NULL; if (xml2ns(x, xml_prefix(x), &ns0) < 0) goto done; if (ns0 && strcmp(ns0, ns) == 0) goto ok; /* Already has right namespace */ /* Is namespace already declared? */ if (xml2prefix(x, ns, &prefix0) == 1){ /* Yes it is declared and the prefix is prefix0 */ if (xml_prefix_set(x, prefix0) < 0) goto done; } else{ /* Namespace does not exist, add it */ /* Clear old prefix if any */ if (xml_prefix_set(x, NULL) < 0) goto done; if (xml_type(x) == CX_ELMNT) /* If not element, do the namespace addition to the element */ xp = x; else xp = xml_parent(x); if (xml_add_namespace(x, xp, prefix, ns) < 0) goto done; /* Add prefix to x, if any */ if (prefix && xml_prefix_set(x, prefix) < 0) goto done; } ok: retval = 0; done: return retval; } /*! Sanitize an xml tree: xml node has matching yang_stmt pointer * * @param[in] xt XML top of tree * @param[in] arg Not used * @retval 0 OK * @retval -1 Error */ int xml_sanity(cxobj *xt, void *arg) { int retval = -1; yang_stmt *ys; char *name; if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ retval = 0; goto done; } name = xml_name(xt); if (strstr(yang_argument_get(ys), name)==NULL){ clixon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'", name, yang_argument_get(ys)); goto done; } retval = 0; done: return retval; } /*! Detect state data: Either mark or break on first occurence and return xerror * * @param[in] xt XML tree * @param[out] xerr If set return netconf error, abort and return if a state variable found * @retval 1 OK * @retval 0 Status node found and return xerror * @retval -1 Error * Note that the behaviour is quite different if xerr is set or not,... */ int xml_non_config_data(cxobj *xt, cxobj **xerr) { int retval = -1; cxobj *x; yang_stmt *y; int ret; cbuf *cb = NULL; x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if ((y = (yang_stmt*)xml_spec(x)) == NULL) goto ok; if (!yang_config(y)){ /* config == false means state data */ if (xerr){ /* behaviour 1: return on error */ if ((cb = cbuf_new()) == NULL){ clixon_err(OE_UNIX, errno, "cbuf_new"); goto done; } cprintf(cb, "module %s: state data node unexpected", yang_argument_get(ys_module(y))); if (netconf_bad_element_xml(xerr, "application", yang_argument_get(y), cbuf_get(cb)) < 0) goto done; retval = 0; goto done; } xml_flag_set(x, XML_FLAG_MARK); /* behaviour 2: mark and continue */ } if ((ret = xml_non_config_data(x, xerr)) < 0) goto done; if (ret == 0){ retval = 0; goto done; } } ok: retval = 1; done: if (cb) cbuf_free(cb); return retval; } /*! Check if the module tree x is in is assigned right XML namespace, assign if not * * @param[in] x XML node * @retval 0 OK * @retval -1 Error *(0. You should probably find the XML root and apply this function to that.) * 1. Check which namespace x should have (via yang). This is correct namespace. * 2. Check which namespace x has via its XML tree * 3. If equal, OK * 4. Assign the correct namespace to the XML node * Assign default namespace to x. Create an "xmlns" * @code * xml_yang_root(x, &xroot); * xmlns_assign(xroot); * @endcode */ int xmlns_assign(cxobj *x) { int retval = -1; yang_stmt *y; char *ns_correct; /* correct uri */ char *ns_xml; /* may be null or incorrect */ if ((y = xml_spec(x)) == NULL){ clixon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x)); goto done; } /* 1. Check which namespace x should have (via yang). This is correct namespace. */ if ((ns_correct = yang_find_mynamespace(y)) == NULL){ clixon_err(OE_YANG, ENOENT, "yang node %s does not have namespace", yang_argument_get(y)); goto done; } /* 2. Check which namespace x has via its XML tree */ if (xml2ns(x, NULL, &ns_xml) < 0) goto done; /* 3. If equal, OK, 4. Else, find root of XML tree */ if (ns_xml && strcmp(ns_xml, ns_correct)==0) goto ok; /* 4. Assign the correct namespace */ if (xmlns_set(x, NULL, ns_correct) < 0) goto done; ok: retval = 0; done: return retval; } /*! Given a src element node x0 and a target node x1, assign (optional) prefix and namespace * * @param[in] x1 XML tree * @param[in] x1p XML tree parent * @retval 0 OK * @retval -1 Error * @see assign_namespace_element this is a subroutine */ static int assign_namespace(cxobj *x1, /* target */ cxobj *x1p, int isroot, char *ns, char *prefix0) { int retval = -1; char *prefix1 = NULL; char *pexist = NULL; cvec *nsc0 = NULL; cvec *nsc = NULL; /* 2a. Detect if namespace is declared in x1 target parent */ if (xml2prefix(x1p, ns, &pexist) == 1){ /* Yes, and it has prefix pexist */ if (pexist){ if ((prefix1 = strdup(pexist)) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } } else prefix1 = NULL; /* 3. If yes, assign prefix to x1 */ if (prefix1 && xml_prefix_set(x1, prefix1) < 0) goto done; /* And copy namespace context from parent to child */ if ((nsc0 = nscache_get_all(x1p)) != NULL){ if ((nsc = cvec_dup(nsc0)) == NULL){ clixon_err(OE_UNIX, errno, "cvec_dup"); goto done; } nscache_replace(x1, nsc); } /* Just in case */ if (nscache_set(x1, prefix1, ns) < 0) goto done; } else{ /* No, namespace does not exist in x1 _parent_ * Check if it is exists in x1 itself */ if (xml2prefix(x1, ns, &pexist) == 1){ /* Yes it exists, but is it equal? */ if (clicon_strcmp(pexist, prefix0) == 0) ; /* Equal, reuse */ else{ /* namespace exist, but not equal, use existing */ /* Add prefix to x1, if any */ if (pexist && xml_prefix_set(x1, pexist) < 0) goto done; } goto ok; /* skip */ } else { /* namespace does not exist in target x1, */ if (isroot){ if (prefix0 && (prefix1 = strdup(prefix0)) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } } else{ if (prefix0 == NULL){ /* Use default namespace, may break use of previous default * somewhere in x1 */ prefix1 = NULL; } } } if (xml_add_namespace(x1, x1, prefix1, ns) < 0) goto done; if (prefix1 && xml_prefix_set(x1, prefix1) < 0) goto done; } ok: /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ retval = 0; done: if (prefix1) free(prefix1); return retval; } /*! Given a src element node x0 and a target node x1, assign (optional) prefix and namespace * * @param[in] x0 Source XML tree * @param[in] x1 Target XML tree * @param[in] x1p Target XML tree parent * @retval 0 OK * @retval -1 Error * 1. Find N=namespace(x0) * 2. Detect if N is declared in x1 parent * 3. If yes, assign prefix to x1 * 4. If no, if default namespace use that, otherwise create new prefix/namespace binding and assign * that to x1p * 5. Add prefix to x1, if any * 6. Ensure x1 cache is updated * @note switch use of x0 and x1 compared to datastore text_modify * @see xml2ns */ int assign_namespace_element(cxobj *x0, /* source */ cxobj *x1, /* target */ cxobj *x1p) { int retval = -1; char *namespace = NULL; char *prefix0 = NULL;; int isroot; isroot = xml_parent(x1p)==NULL && xml_flag(x1p, XML_FLAG_TOP) && xml_prefix(x1p)==NULL; /* 1. Find N=namespace(x0) in x0 element */ prefix0 = xml_prefix(x0); if (xml2ns(x0, prefix0, &namespace) < 0) goto done; if (namespace == NULL){ clixon_err(OE_XML, ENOENT, "No namespace found for prefix:%s", prefix0?prefix0:"NULL"); goto done; } if (assign_namespace(x1, x1p, isroot, namespace, prefix0) < 0) goto done; /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ retval = 0; done: return retval; } /*! Copy namespace declarations from source to target * * If origin body has namespace definitions, copy them. The reason is that * some bodies rely on namespace prefixes, such as NACM path, but there is * no way we can know this here. * However, this may lead to namespace collisions if these prefixes are not * canonical, and may collide with the assign_namespace_element() above (but that * is for element symbols) * * @param[in] x0 Source XML * @param[in] x1 Destination XML * @retval 0 OK * @retval -1 Error * @note "standard" namespaces, including clixon internal namespaces are removed */ int assign_namespace_body(cxobj *x0, /* source */ cxobj *x1) /* target */ { int retval = -1; char *namespace = NULL; char *namespace1; char *name; char *prefix0; char *prefix1 = NULL; cxobj *xa; xa = NULL; while ((xa = xml_child_each_attr(x0, xa)) != NULL) { prefix0 = xml_prefix(xa); name = xml_name(xa); namespace = xml_value(xa); if ((strcmp(name, "xmlns")==0 && prefix0==NULL) || (prefix0 != NULL && strcmp(prefix0, "xmlns")==0)){ if (prefix0 == NULL) prefix1 = NULL; else prefix1 = name; /* prefix1 contains actual prefix or NULL, prefix0 can be xmlns */ if (strcmp(namespace, NETCONF_BASE_NAMESPACE) == 0 || strcmp(namespace, YANG_XML_NAMESPACE) == 0 || strcmp(namespace, CLIXON_CONF_NS) == 0 || strcmp(namespace, CLIXON_LIB_NS) == 0 ) continue; continue; // XXX /* Detect if prefix:namespace is declared already? */ if (xml2ns(x1, prefix1, &namespace1) == 1) continue; /* Does prefix already point at right namespace? */ if (namespace1 && strcmp(namespace, namespace1)==0) continue; /* No, add entry */ if (xml_add_namespace(x1, x1, prefix1, namespace) < 0) goto done; } } /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ retval = 0; done: return retval; } /*! Merge a base tree x0 with x1 with yang spec y * * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[out] reason If retval=0 a malloced string * @retval 1 OK * @retval 0 Yang error, reason is set * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec */ static int xml_merge1(cxobj *x0, /* the target */ yang_stmt *y0, cxobj *x0p, cxobj *x1, /* the source */ char **reason) { int retval = -1; char *x1cname; /* child name */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ int ret; int i; merge_twophase *twophase = NULL; int twophase_len; cvec *nsc = NULL; cg_var *cv; char *ns; char *px; char *pxe; if (x1 == NULL || xml_type(x1) != CX_ELMNT || y0 == NULL){ clixon_err(OE_XML, EINVAL, "x1 is NULL or not XML element, or lacks yang spec"); goto done; } if (x0 == NULL){ if (xml_nsctx_node(x1, &nsc) < 0) goto done; if (xml_rm(x1) < 0) goto done; /* This is to make the anydata case a little more robust, more could be done */ if (xml_spec(x1) == NULL){ if (xml_addsub(x0p, x1) < 0) goto done; } else if (xml_insert(x0p, x1, INS_LAST, NULL, NULL) < 0) goto done; cv = NULL; while ((cv = cvec_each(nsc, cv)) != NULL){ px = cv_name_get(cv); ns = cv_string_get(cv); /* Check if namespace exists */ if ((ret = xml2prefix(x1, ns, &pxe)) < 0) goto done; if (ret == 0 || /* Not exist */ clicon_strcmp(px, pxe) != 0){ /* Exists and not equal (can be NULL) */ if (xmlns_set(x1, px, ns) < 0) goto done; xml_sort(x1); } } goto ok; } if (yang_keyword_get(y0) == Y_LEAF_LIST || yang_keyword_get(y0) == Y_LEAF){ x1bstr = xml_body(x1); if (x1bstr){ if ((x0b = xml_body_get(x0)) == NULL){ if ((x0b = xml_new("body", x0, CX_BODY)) == NULL) goto done; } if (xml_value_set(x0b, x1bstr) < 0) goto done; } if (xml_parent(x0) == NULL && xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0) goto done; if (assign_namespace_element(x1, x0, x0p) < 0) goto done; } /* if LEAF|LEAF_LIST */ else { /* eg Y_CONTAINER, Y_LIST */ if (assign_namespace_element(x1, x0, x0p) < 0) goto done; twophase_len = xml_child_nr(x1); if ((twophase = calloc(twophase_len, sizeof(*twophase))) == NULL){ clixon_err(OE_UNIX, errno, "calloc"); goto done; } i = 0; /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ /* * Actually the CLICON_YANG_SCHEMA_MOUNT option should be checked below * to be consistent with what is done e.g. in * clixon_datastore_write.c::text_modify() when yang_find_datanode() * returns NULL. * However the clixon_handle needed to check this option is not * available here. * So check for the YANG_FLAG_MOUNTPOINT flag on y0 as an alternative. * It will only have been set if CLICON_YANG_SCHEMA_MOUNT is enabled * and it will be set for exactly those cases where the xml_spec() * call is needed. */ if (yang_flag_get(y0, YANG_FLAG_MOUNTPOINT)) yc = xml_spec(x1c); } if (yc == NULL) { if (reason){ if ((cbr = cbuf_new()) == NULL){ clixon_err(OE_XML, errno, "cbuf_new"); goto done; } cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); if ((*reason = strdup(cbuf_get(cbr))) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } } goto fail; } /* See if there is a corresponding node in the base tree */ x0c = NULL; if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) goto done; /* If x0 already has a value, do not replace it with a default value in x1 */ if (x0c && xml_flag(x1c, XML_FLAG_DEFAULT)) continue; /* Save x0c, x1c, yc and merge in second wave, so that x1c entries dont "interfer" * with itself, ie that later searches are among earlier objects already added * to x0 */ twophase[i].mt_x0c = x0c; twophase[i].mt_x1c = x1c; twophase[i].mt_yc = yc; i++; } /* while */ twophase_len = i; /* Inital length included non-elements */ /* Second run where actual merging is done * Loop through children of the modification tree */ for (i=0; i 0) { if ((ret = yang_bits_pos(ytype, v, &bitpos)) < 0) goto done; if (ret == 0) goto fail; /* Set bit at correct byte and bit position */ byte = bitpos / 8; buffer[byte] = buffer[byte] | (1 << (7 - (bitpos % 8))); *outlen = byte + 1; if (*outlen >= CLIXON_BITS_POS_MAX) { clixon_err(OE_UNIX, EINVAL, "bit position %zu out of range. (max. allowed %d)", *outlen, CLIXON_BITS_POS_MAX); goto done; } } } if ((*outval = malloc(*outlen)) == NULL){ clixon_err(OE_UNIX, errno, "calloc"); goto done; } memcpy(*outval, buffer, *outlen); retval = 1; done: if (buffer) free(buffer); if (vec) free(vec); return retval; fail: retval = 0; goto done; } /*! Given a YANG (bits) type node and string value, return bit values in a uint64 * * @param[in] ytype YANG type noden * @param[in] bitsstr Value of bits as space separated string * @param[out] flags Pointer to integer with bit values set according to C type * @retval 1 OK, result in u64 * @retval 0 Invalid, not found * @retval -1 Error * @see yang_bitsstr2val for bit vector (snmp-like) */ int yang_bitsstr2flags(yang_stmt *ytype, char *bitsstr, uint32_t *flags) { int retval = -1; int i = 0; char **vec = NULL; char *v; int nvec; int ret = 0; uint32_t bitpos; if (flags == NULL){ clixon_err(OE_UNIX, EINVAL, "flags is NULL"); goto done; } if ((vec = clicon_strsep(bitsstr, " ", &nvec)) == NULL){ clixon_err(OE_UNIX, EINVAL, "split string failed"); goto done; } /* Go over all set flags in given bitstring */ for (i=0; i 0) { if ((ret = yang_bits_pos(ytype, v, &bitpos)) < 0) goto done; if (ret == 0) goto fail; if (bitpos >= 32) { clixon_err(OE_UNIX, EINVAL, "bit position %u out of range. (max. allowed %d)", bitpos, 32); goto done; } *flags |= (1 << bitpos); } } retval = 1; done: if (vec) free(vec); return retval; fail: retval = 0; goto done; } /*! Given a YANG (bits) type node and value, return the string value for all bits (flags) that are set. * * @param[in] h Clixon handle * @param[in] ytype YANG type noden * @param[in] inval Input string * @param[in] inlen Length of inval * @param[out] cb space separated string with bit labels for all bits that are set in inval * @retval 1 OK, result in cb * @retval 0 Invalid, not found * @retval -1 Error * @see yang_bitsstr2val * @note that the output is a vector of bits originally made for SNMP bitvectors (not integers) */ int yang_val2bitsstr(clixon_handle h, yang_stmt *ytype, unsigned char *inval, size_t inlen, cbuf *cb) { int retval = -1; int is_first = 1; int ret = 0; int byte = 0; char *reason = NULL; yang_stmt *yprev; yang_stmt *ypos; uint32_t bitpos = 0; int inext; if (cb == NULL){ clixon_err(OE_UNIX, EINVAL, "cb is NULL"); goto done; } /* Go over all defined bits and check if it is seet in intval */ inext = 0; while ((yprev = yn_iter(ytype, &inext)) != NULL && byte < inlen){ if (yang_keyword_get(yprev) == Y_BIT) { /* Use position from Y_POSITION statement if defined */ if ((ypos = yang_find(yprev, Y_POSITION, NULL)) != NULL){ if ((ret = parse_uint32(yang_argument_get(ypos), &bitpos, &reason)) < 0){ clixon_err(OE_UNIX, EINVAL, "cannot parse bit position val: %s", reason); goto done; } if (ret == 0) goto fail; } else { /* Position not defined. Use last known position + 1 (skip first node to start with 0) */ if (is_first == 0) bitpos++; } byte = bitpos / 8; if (inval[byte] & (1 << (7 - (bitpos % 8)))){ if (is_first == 0) cbuf_append_str(cb, " "); cbuf_append_str(cb, yang_argument_get(yprev)); } is_first = 0; } } /* append space if no flag is set to indicate that */ if (cbuf_len(cb) == 0) cbuf_append_str(cb, " "); retval = 1; done: if (reason) free(reason); return retval; fail: retval = 0; goto done; } /*! Map from bit string to integer bitfield given YANG mapping * * Given YANG node, schema-nodeid and a bits string, return a bitmap as u64 * Example: "default app2" --> CLIXON_DBG_DEFAULT | CLIXON_DBG_APP2 * @param[in] yt YANG node in tree (eg yspec) * @param[in] str String representation of Clixon debug bits, such as "msg app2" * @param[in] nodeid Absolute schema node identifier to leaf of option * @param[out] u64 Bit representation */ int yang_bits_map(yang_stmt *yt, char *str, char *nodeid, uint32_t *flags) { int retval = -1; yang_stmt *yn = NULL; yang_stmt *yrestype; int ret; if (yang_abs_schema_nodeid(yt, nodeid, &yn) < 0) goto done; if (yn == NULL){ clixon_err(OE_YANG, 0, "yang node not found: %s", nodeid); goto done; } if (yang_type_get(yn, NULL, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) goto done; if (yrestype != NULL) { if ((ret = yang_bitsstr2flags(yrestype, str, flags)) < 0) goto done; if (ret == 0){ clixon_err(OE_YANG, 0, "Bit string invalid: %s", str); goto done; } } retval = 0; done: return retval; } /*! Get integer value from xml node from yang enumeration * * @param[in] node XML node in a tree * @param[out] val Integer value returned * @retval 0 OK, value parsed * @retval -1 Error, value not obtained or parsed, no reason given * @note assume yang spec set * @note The function only returns assigned values, but according to RFC: If a value is not specified, then one will be automatically assigned. If the "enum" substatement is the first one defined, the assigned value is zero (0); otherwise, the assigned value is one greater than the current highest enum value (i.e., the highest enum value, * Thanks: Matthew Smith */ int yang_enum_int_value(cxobj *node, int32_t *val) { int retval = -1; yang_stmt *yspec; yang_stmt *ys; yang_stmt *ytype; yang_stmt *yrestype; /* resolved type */ if (node == NULL) goto done; if ((ys = (yang_stmt *) xml_spec(node)) == NULL) goto done; if ((yspec = ys_spec(ys)) == NULL) goto done; if ((ytype = yang_find(ys, Y_TYPE, NULL)) == NULL) goto done; if (yang_type_resolve(ys, ys, ytype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) goto done; if (yrestype == NULL){ clixon_err(OE_YANG, 0, "result-type should not be NULL"); goto done; } if (strcmp(yang_argument_get(yrestype), "enumeration")) goto done; if (yang_enum2int(yrestype, xml_body(node), val) < 0) goto done; retval = 0; done: return retval; } /*! Given XML tree x0 with marked nodes, copy marked nodes to new tree x1 * * Two marks are used: XML_FLAG_MARK and XML_FLAG_CHANGE * The algorithm works as following: * (1) Copy individual nodes marked with XML_FLAG_CHANGE * until nodes marked with XML_FLAG_MARK are reached, where * (2) the complete subtree of that node is copied. * (3) Special case: key nodes in lists are copied if any node in list is marked * @param[in] x0 * @param[in] x1 * @retval 0 OK * @retval -1 Error * @note you may want to check:!yang_config(ys) */ int xml_copy_marked(cxobj *x0, cxobj *x1) { int retval = -1; int mark; cxobj *x; cxobj *xcopy; int iskey; yang_stmt *yt; char *name; char *prefix; if (x0 == NULL || x1 == NULL){ clixon_err(OE_UNIX, EINVAL, "x0 or x1 is NULL"); goto done; } yt = xml_spec(x0); /* can be null */ xml_spec_set(x1, yt); /* Copy prefix*/ if ((prefix = xml_prefix(x0)) != NULL) if (xml_prefix_set(x1, prefix) < 0) goto done; /* Copy all attributes */ x = NULL; while ((x = xml_child_each_attr(x0, x)) != NULL) { name = xml_name(x); if ((xcopy = xml_new(name, x1, CX_ATTR)) == NULL) goto done; if (xml_copy(x, xcopy) < 0) goto done; } /* Go through children to detect any marked nodes: * (3) Special case: key nodes in lists are copied if any * node in list is marked */ mark = 0; x = NULL; while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) { if (xml_flag(x, XML_FLAG_MARK|XML_FLAG_CHANGE)){ mark++; break; } } x = NULL; while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) { name = xml_name(x); if (xml_flag(x, XML_FLAG_MARK)){ /* (2) the complete subtree of that node is copied. */ if ((xcopy = xml_new(name, x1, CX_ELMNT)) == NULL) goto done; if (xml_copy(x, xcopy) < 0) goto done; continue; } if (xml_flag(x, XML_FLAG_CHANGE)){ /* Copy individual nodes marked with XML_FLAG_CHANGE */ if ((xcopy = xml_new(name, x1, CX_ELMNT)) == NULL) goto done; if (xml_copy_marked(x, xcopy) < 0) /* */ goto done; } /* (3) Special case: key nodes in lists are copied if any * node in list is marked */ if (mark && yt && yang_keyword_get(yt) == Y_LIST){ /* XXX: I think yang_key_match is suboptimal here */ if ((iskey = yang_key_match(yt, name, NULL)) < 0) goto done; if (iskey){ if ((xcopy = xml_new(name, x1, CX_ELMNT)) == NULL) goto done; if (xml_copy(x, xcopy) < 0) goto done; } } } retval = 0; done: return retval; } /*! Check when condition * * @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp * @param[in] xp XML parent * @param[in] yn Yang node * @param[out] hit when statement found * @param[out] nrp 1: when stmt evaluates to true * @param[out] xpathp when stmts xpath, free after use * @retval 0 OK * @retval -1 Error * First variants of WHEN: Augmented and uses when using special info in node * Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ int yang_check_when_xpath(cxobj *xn, cxobj *xp, yang_stmt *yn, int *hit, int *nrp, char **xpathp) { int retval = 1; yang_stmt *yc; char *xpath = NULL; cxobj *x = NULL; int nr = 0; cvec *nsc = NULL; int variant = 0; /* ugly help variable to clean temporary object */ if (yang_when_canonical_xpath_get(yn, &xpath, &nsc) < 0) goto done; if (xpath != NULL){ x = xp; *hit = 1; } else if ((yc = yang_find(yn, Y_WHEN, NULL)) != NULL){ /* "when" has xpath argument */ if ((xpath = strdup(yang_argument_get(yc))) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } /* Create dummy */ if (xn == NULL){ if ((x = xml_new(yang_argument_get(yn), xp, CX_ELMNT)) == NULL) goto done; xml_spec_set(x, yn); variant = 1; } else x = xn; if (xml_nsctx_yang(yn, &nsc) < 0) goto done; *hit = 1; } else *hit = 0; if (x && xpath){ if ((nr = xpath_vec_bool(x, nsc, "%s", xpath)) < 0) goto done; } if (nrp) *nrp = nr; if (xpathp){ *xpathp = xpath; xpath = NULL; } retval = 0; done: if (variant) xml_purge(x); if (xpath) free(xpath); if (nsc) xml_nsctx_free(nsc); return retval; } /*! Check if node is (recursively) mandatory also checking when conditional * * @param[in] xn Optional XML node * @param[in] xp XML parent * @param[in] ys YANG node * @retval 1 Recursively contains a mandatory node * @retval 0 Does not contain a mandatory node * @retval -1 Error * @see RFC7950 Sec 3: * o mandatory node: A mandatory node is one of: * 1) A leaf, choice, anydata, or anyxml node with a "mandatory" * statement with the value "true". * 2) # see below * 3) A container node without a "presence" statement and that has at * least one mandatory node as a child. */ int yang_xml_mandatory(cxobj *xt, yang_stmt *ys) { int retval = -1; yang_stmt *ym; cg_var *cv; enum rfc_6020 keyw; cxobj *xs = NULL; int ret; yang_stmt *yc; int hit; int nr; int inext; /* Create dummy xs if not exist */ if ((xs = xml_new(yang_argument_get(ys), xt, CX_ELMNT)) == NULL) goto done; xml_spec_set(xs, ys); if (yang_check_when_xpath(xs, xt, ys, &hit, &nr, NULL) < 0) goto done; if (hit && !nr){ retval = 0; goto done; } keyw = yang_keyword_get(ys); if (keyw == Y_LEAF || keyw == Y_CHOICE || keyw == Y_ANYDATA || keyw == Y_ANYXML){ if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){ if ((cv = yang_cv_get(ym)) != NULL){ /* shouldnt happen */ retval = cv_bool_get(cv); goto done; } } } /* 3) A container node without a "presence" statement and that has at * least one mandatory node as a child. */ else if (keyw == Y_CONTAINER && yang_find(ys, Y_PRESENCE, NULL) == NULL){ inext = 0; while ((yc = yn_iter(ys, &inext)) != NULL) { if ((ret = yang_xml_mandatory(xs, yc)) < 0) goto done; if (ret == 1) goto mandatory; } } retval = 0; /* Does not contain mandatory node */ done: if (xs != NULL) xml_purge(xs); return retval; mandatory: retval = 1; goto done; } /*! Is XML node (ie under ) an action, ie name action and belong to YANG_XML_NAMESPACE? * * @param[in] xn XML node * @retval 1 Yes, an action * @retval 0 No, not an action * @retval -1 Error */ int xml_rpc_isaction(cxobj *xn) { int retval = -1; char *ns = NULL; if (strcmp(xml_name(xn), "action") != 0) goto fail; if (xml2ns(xn, xml_prefix(xn), &ns) < 0) goto done; if (strcmp(YANG_XML_NAMESPACE, ns) != 0) goto fail; retval = 1; // is action done: return retval; fail: retval = 0; goto done; } /*! Find innermost node under carrying the name of the defined action * * Find innermost container or list contains an XML element * that carries the name of the defined action. * @param[in] xn XML node * @param[in] top If set, dont look for action node since top-level * @param[out] xap Pointer to xml action node * @retval 0 OK * @retval -1 Error * XXX: does not look at list key */ int xml_find_action(cxobj *xn, int top, cxobj **xap) { int retval = -1; cxobj *xc = NULL; yang_stmt *yc; while ((xc = xml_child_each(xn, xc, CX_ELMNT)) != NULL) { if ((yc = xml_spec(xc)) == NULL) continue; if (!top && yang_keyword_get(yc) == Y_ACTION){ *xap = xc; break; } if (yang_keyword_get(yc) != Y_CONTAINER && yang_keyword_get(yc) != Y_LIST) continue; /* XXX check key */ if (xml_find_action(xc, 0, xap) < 0) goto done; break; } retval = 0; done: return retval; } /*! Utility function: recursive traverse an XML tree and remove nodes based on attribute value * * Conditionally remove attribute and node * @param[in] xn XML node * @param[in] ns Namespace of attribute * @param[in] name Attribute name * @param[in] value Attribute value * @param[in] keepnode 0: remove node associated with attribute; 1: keep node but remove attr * @retval 0 OK * @retval -1 Error */ int purge_tagged_nodes(cxobj *xn, char *ns, char *name, char *value, int keepnode) { int retval = -1; cxobj *x; cxobj *xa; cxobj *xprev; char *prefix; char *v; int ret; x = NULL; xprev = NULL; while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) { if ((ret = xml2prefix(x, ns, &prefix)) < 0) goto done; if (ret == 0) continue; if ((xa = xml_find_type(x, prefix, "default", CX_ATTR)) != NULL){ if (!keepnode && (v = xml_value(xa)) != NULL && strcmp(v, value) == 0){ xml_purge(x); x = xprev; continue; } xml_purge(xa); /* remove attribute regardless */ } if (purge_tagged_nodes(x, ns, name, value, keepnode) < 0) goto done; xprev = x; } retval = 0; done: return retval; } /*! Compare two dbs using XML. Write to file and run diff. Independent of YANG * * @param[in] xc1 XML tree 1 * @param[in] xc2 XML tree 2 * @param[in] format "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * @retval 0 OK * @retval -1 Error * @see clixon_xml_diff2cbuf with better XML in-mem comparison but is YANG dependent */ int clixon_compare_xmls(cxobj *xc1, cxobj *xc2, enum format_enum format) { int retval = -1; int fd; FILE *f; char filename1[MAXPATHLEN]; char filename2[MAXPATHLEN]; cbuf *cb = NULL; snprintf(filename1, sizeof(filename1), "/tmp/cliconXXXXXX"); snprintf(filename2, sizeof(filename2), "/tmp/cliconXXXXXX"); if ((fd = mkstemp(filename1)) < 0){ clixon_err(OE_UNDEF, errno, "tmpfile"); goto done; } if ((f = fdopen(fd, "w")) == NULL){ clixon_err(OE_XML, errno, "fdopen(%s)", filename1); goto done; } switch(format){ case FORMAT_TEXT: if (clixon_text2file(f, xc1, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_XML: default: if (clixon_xml2file(f, xc1, 0, 1, NULL, cligen_output, 1, 1) < 0) goto done; break; } fclose(f); close(fd); if ((fd = mkstemp(filename2)) < 0){ clixon_err(OE_UNDEF, errno, "mkstemp: %s", strerror(errno)); goto done; } if ((f = fdopen(fd, "w")) == NULL){ clixon_err(OE_XML, errno, "fdopen(%s)", filename2); goto done; } switch(format){ case FORMAT_TEXT: if (clixon_text2file(f, xc2, 0, cligen_output, 1, 1) < 0) goto done; break; case FORMAT_XML: default: if (clixon_xml2file(f, xc2, 0, 1, NULL, cligen_output, 1, 1) < 0) goto done; break; } fclose(f); close(fd); if ((cb = cbuf_new()) == NULL){ clixon_err(OE_CFG, errno, "cbuf_new"); goto done; } cprintf(cb, "diff -dU 1 %s %s | grep -v @@ | sed 1,2d", filename1, filename2); if (system(cbuf_get(cb)) < 0) goto done; retval = 0; done: if (cb) cbuf_free(cb); unlink(filename1); unlink(filename2); return retval; } /*! XML apply function: replace ${} variables with values from cligen variable vector * * Example: x=${name}, cvv=["name","bert"] --> bert * @param[in] x XML node * @param[in] arg cvv: vector of name/value pairs * @retval 2 Locally abort this subtree, continue with others * @retval 1 Abort, dont continue with others, return 1 to end user * @retval 0 OK, continue * @retval -1 Error, aborted at first error encounter, return -1 to end user * @code * xml_apply(xtmpl, CX_ELMNT, xml_template_apply, cvv); * @endcode */ int xml_template_apply(cxobj *x, void *arg) { int retval = -1; cvec *cvv = (cvec *)arg; cxobj *xb; char *b; cbuf *cb = NULL; if ((xb = xml_body_get(x)) != NULL && (b = xml_value(xb)) != NULL){ if ((cb = cbuf_new()) == NULL){ clixon_err(OE_UNIX, errno, "cbuf_new"); goto done; } if (clixon_str_subst(b, cvv, cb) < 0) goto done; xml_value_set(xb, cbuf_get(cb)); } retval = 0; done: if (cb) cbuf_free(cb); return retval; }