/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren Copyright (C) 2017-2019 Olof Hagsand Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC 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 ***** * XML sort and search functions when used with YANG */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clixon */ #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_xpath_ctx.h" #include "clixon_xpath.h" #include "clixon_options.h" #include "clixon_xml_map.h" #include "clixon_yang_type.h" #include "clixon_yang_module.h" #include "clixon_xml_sort.h" /*! Get xml body value as cligen variable * @param[in] x XML node (body and leaf/leaf-list) * @param[out] cvp Pointer to cligen variable containing value of x body * @retval 0 OK, cvp contains cv or NULL * @retval -1 Error * @note only applicable if x is body and has yang-spec and is leaf or leaf-list * Move to clixon_xml.c? */ static int xml_cv_cache(cxobj *x, cg_var **cvp) { int retval = -1; cg_var *cv = NULL; yang_stmt *y; yang_stmt *yrestype; enum cv_type cvtype; int ret; char *reason=NULL; int options = 0; uint8_t fraction = 0; char *body; if ((body = xml_body(x)) == NULL) body=""; if ((cv = xml_cv(x)) != NULL) goto ok; if ((y = xml_spec(x)) == NULL) goto ok; if (yang_type_get(y, NULL, &yrestype, &options, NULL, NULL, NULL, &fraction) < 0) goto done; yang2cv_type(yang_argument_get(yrestype), &cvtype); if (cvtype==CGV_ERR){ clicon_err(OE_YANG, errno, "yang->cligen type %s mapping failed", yang_argument_get(yrestype)); goto done; } if ((cv = cv_new(cvtype)) == NULL){ clicon_err(OE_YANG, errno, "cv_new"); goto done; } if (cvtype == CGV_DEC64) cv_dec64_n_set(cv, fraction); if ((ret = cv_parse1(body, cv, &reason)) < 0){ clicon_err(OE_YANG, errno, "cv_parse1"); goto done; } if (ret == 0){ clicon_err(OE_YANG, EINVAL, "cv parse error: %s\n", reason); goto done; } if (xml_cv_set(x, cv) < 0) goto done; ok: *cvp = cv; cv = NULL; retval = 0; done: if (reason) free(reason); if (cv) cv_free(cv); return retval; } /*! Help function to qsort for sorting entries in xml child vector same parent * @param[in] x1 object 1 * @param[in] x2 object 2 * @param[in] same If set, x1 and x2 are member of same parent & enumeration * is used (see explanation below) * @param[in] skip1 Key matching skipped for keys not in x1 (see explanation) * @retval 0 If equal * @retval <0 If x1 is less than x2 * @retval >0 If x1 is greater than x2 * * There are distinct calls for this function: * 1. For sorting in an existing list of XML children * 2. For searching of an existing element in a list * In the first case, there is a special case for "ordered-by-user", where * if they have the same yang-spec, the existing order is used as tie-breaker. * In other words, if order-by-system, or if the case (2) above, the existing * order is ignored and the actual xml element contents is examined. * * Also, if pattern is set, list matching is not made so that x1 and x2 must have same keys. * instead, x1 and x2 are considered equal if the keys that x1 have match. Keys that x2 but not * x1 has are ignored. * Example: x1: 1 and x2: 12 are considered equal. * This is useful in searching for indexes in trees, where x1 is a search index and not a * complete tree. * * @note empty value/NULL is smallest value * @note some error cases return as -1 (qsort cant handle errors) * @note some error cases return as -1 (qsort cant handle errors) * * @note "comparing" x1 and x2 here judges equality from a structural (model) * perspective, ie both have the same yang spec, if they are lists, they have the * the same keys. NOT that the values are equal! * In other words, if x is a leaf with the same yang spec, 1 and 2 are * "equal". * If x is a list element (with key "k"), * 42foo and 42bar are equal, * but is not equal to 71bar */ int xml_cmp(cxobj *x1, cxobj *x2, int same, int skip1) { yang_stmt *y1; yang_stmt *y2; int yi1 = 0; int yi2 = 0; cvec *cvk = NULL; /* vector of index keys */ cg_var *cvi; int equal = 0; char *b1; char *b2; char *keyname; cg_var *cv1; cg_var *cv2; int nr1 = 0; int nr2 = 0; cxobj *x1b; cxobj *x2b; if (x1==NULL || x2==NULL) goto done; /* shouldnt happen */ y1 = xml_spec(x1); y2 = xml_spec(x2); if (same){ nr1 = xml_enumerate_get(x1); nr2 = xml_enumerate_get(x2); } if (y1==NULL && y2==NULL){ if (same) equal = nr1-nr2; goto done; } if (y1==NULL){ equal = -1; goto done; } if (y2==NULL){ equal = 1; goto done; } if (y1 != y2){ yi1 = yang_order(y1); yi2 = yang_order(y2); if ((equal = yi1-yi2) != 0) goto done; } /* Now y1==y2, same Yang spec, can only be list or leaf-list, * But first check exceptions, eg config false or ordered-by user * otherwise sort according to key * If the two elements are in the same list, and they are ordered-by user * then do not look more into equivalence, use the enumeration in the * existing list. */ if (same && (yang_config(y1)==0 || yang_find(y1, Y_ORDERED_BY, "user") != NULL)){ equal = nr1-nr2; goto done; /* Ordered by user or state data : maintain existing order */ } switch (yang_keyword_get(y1)){ case Y_LEAF_LIST: /* Match with name and value */ b1 = xml_body(x1); b2 = xml_body(x2); if (b1 == NULL && b2 == NULL) ; else if (b1 == NULL) equal = -1; else if (b2 == NULL) equal = 1; else{ if (xml_cv_cache(x1, &cv1) < 0) /* error case */ goto done; if (xml_cv_cache(x2, &cv2) < 0) /* error case */ goto done; if (cv1 != NULL && cv2 != NULL) equal = cv_cmp(cv1, cv2); else if (cv1 == NULL && cv2 == NULL) equal = 0; else if (cv1 == NULL) equal = -1; else equal = 1; } break; case Y_LIST: /* Match with key values * Use Y_LIST cache (see struct yang_stmt) */ cvk = yang_cvec_get(y1); /* Use Y_LIST cache, see ys_populate_list() */ cvi = NULL; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); /* operational data may have NULL keys*/ x1b = xml_find(x1, keyname); /* match1: key matching skipped for keys not in x1 (see explanation) */ if (skip1 && x1b == NULL) continue; x2b = xml_find(x2, keyname); if (x1b == NULL && x2b == NULL) ; else if (x1b == NULL) equal = -1; else if (x2b == NULL) equal = 1; else{ b1 = xml_body(x1b); b2 = xml_body(x2b); if (b1 == NULL && b2 == NULL) ; else if (b1 == NULL) equal = -1; else if (b2 == NULL) equal = 1; else{ if (xml_cv_cache(x1b, &cv1) < 0) /* error case */ goto done; if (xml_cv_cache(x2b, &cv2) < 0) /* error case */ goto done; assert(cv1 && cv2); equal = cv_cmp(cv1, cv2); } } if (equal) break; } /* while cvi */ break; default: break; } /* switch */ done: clicon_debug(2, "%s %s %s %d nr: %d %d yi: %d %d", __FUNCTION__, xml_name(x1), xml_name(x2), equal, nr1, nr2, yi1, yi2); return equal; } /*! * @note args are pointer ot pointers, to fit into qsort cmp function */ static int xml_cmp_qsort(const void* arg1, const void* arg2) { return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1, 0); } /*! Sort children of an XML node * Assume populated by yang spec. * @param[in] x0 XML node * @param[in] arg Dummy so it can be called by xml_apply() * @retval -1 Error, aborted at first error encounter * @retval 0 OK, all nodes traversed (subparts may have been skipped) * @retval 1 OK, aborted on first fn returned 1 * @see xml_apply - typically called by recursive apply function */ int xml_sort(cxobj *x, void *arg) { yang_stmt *ys; /* Abort sort if non-config (=state) data */ if ((ys = xml_spec(x)) != 0 && yang_config(ys)==0) return 1; xml_enumerate_children(x); qsort(xml_childvec_get(x), xml_child_nr(x), sizeof(cxobj *), xml_cmp_qsort); return 0; } /*! Special case search for ordered-by user or state data where linear sort is used * * @param[in] xp Parent XML node (go through its childre) * @param[in] x1 XML node to match * @param[in] yangi Yang order number (according to spec) * @param[in] mid Where to start from (may be in middle of interval) * @param[out] xvec Vector of matching XML return objects (can be empty) * @param[out] xlen Length of xvec * @retval 0 OK, see xvec (may be empty) * @retval -1 Error * XXX: only first match */ static int xml_find_keys_notsorted(cxobj *xp, cxobj *x1, int yangi, int mid, int skip1, cxobj ***xvec, size_t *xlen) { int retval = -1; int i; cxobj *xc; yang_stmt *yc; for (i=mid+1; i=0; i--){ /* Then decrement */ xc = xml_child_i(xp, i); yc = xml_spec(xc); if (yangi != yang_order(yc)) /* wrong yang */ break; if (xml_cmp(xc, x1, 0, skip1) == 0){ if (cxvec_append(xc, xvec, xlen) < 0) goto done; goto ok; /* found */ } } ok: retval = 0; done: return retval; } /*! Find more equal objects in a vector up and down in the array of the present * @param[in] xp Parent XML node (go through its childre) * @param[in] x1 XML node to match * @param[in] yangi Yang order number (according to spec) * @param[in] mid Where to start from (may be in middle of interval) * @param[out] xvec Vector of matching XML return objects (can be empty) * @param[out] xlen Length of xvec * @retval 0 OK, see xvec (may be empty) * @retval -1 Error */ static int more_equals(cxobj *xp, cxobj *x1, int yangi, int mid, int skip1, cxobj ***xvec, size_t *xlen) { int retval = -1; int i; cxobj *xc; yang_stmt *yc; for (i=mid-1; i>=0; i--){ /* First decrement */ xc = xml_child_i(xp, i); yc = xml_spec(xc); if (yangi != yang_order(yc)) /* wrong yang */ break; if (xml_cmp(x1, xc, 0, skip1) != 0) break; if (cxvec_prepend(xc, xvec, xlen) < 0) goto done; } for (i=mid+1; i= xml_child_nr(xp)) /* beyond range */ goto ok; xc = xml_child_i(xp, mid); if ((y = xml_spec(xc)) == NULL) goto ok; cmp = yangi-yang_order(y); /* Here is right yang order == same yang? */ if (cmp == 0){ cmp = xml_cmp(x1, xc, 0, skip1); if (cmp && !sorted){ /* Ordered by user (if not equal) */ retval = xml_find_keys_notsorted(xp, x1, yangi, mid, skip1, xvec, xlen); goto done; } } if (cmp == 0){ if (cxvec_append(xc, xvec, xlen) < 0) goto done; /* there may be more? */ if (more_equals(xp, x1, yangi, mid, skip1, xvec, xlen) < 0) goto done; } else if (cmp < 0) xml_find_keys1(xp, x1, sorted, yangi, low, mid-1, skip1, xvec, xlen); else xml_find_keys1(xp, x1, sorted, yangi, mid+1, upper, skip1, xvec, xlen); ok: retval = 0; done: return retval; } /*! Find XML child under xp matching x1 using binary search for list/leaf-list keys * * Match is tried xp with x1 with either name only (container/leaf) or using keys (list/leaf-lists) * Any non-key leafs or other structure of x1 is not matched. * * @param[in] xp Parent xml node. * @param[in] x1 Find this object among xp:s children * @param[in] yc Yang spec of x1 * @param[in] skip1 Key matching skipped for keys not in x1 * @param[out] xvec Vector of matching XML return objects (can be empty) * @param[out] xlen Length of xvec * @retval 0 OK, see xvec (may be empty) * @retval -1 Error * @see xml_find_index for a generic search function */ static int xml_find_keys(cxobj *xp, cxobj *x1, yang_stmt *yc, int skip1, cxobj ***xvec, size_t *xlen) { cxobj *xa; int low = 0; int upper = xml_child_nr(xp); int sorted = 1; int yangi; /* Assume if there are any attributes, they are first in the list, mask them by raising low to skip them */ for (low=0; lowblowfish-cbc) */ static int xml_insert_userorder(cxobj *xp, cxobj *xn, yang_stmt *yn, int mid, enum insert_type ins, char *key_val, cvec *nsc_key) { int retval = -1; int i; cxobj *xc; yang_stmt *yc; switch (ins){ case INS_FIRST: for (i=mid-1; i>=0; i--){ /* decrement */ xc = xml_child_i(xp, i); yc = xml_spec(xc); if (yc != yn){ retval = i+1; goto done; } } retval = i+1; break; case INS_LAST: for (i=mid+1; i upper){ /* beyond range */ clicon_err(OE_XML, 0, "low>upper %d %d", low, upper); goto done; } if (low == upper){ retval = low; goto done; } mid = (low + upper) / 2; if (mid >= xml_child_nr(xp)){ /* beyond range */ clicon_err(OE_XML, 0, "Beyond range %d %d %d", low, mid, upper); goto done; } xc = xml_child_i(xp, mid); if ((yc = xml_spec(xc)) == NULL){ if (xml_type(xc) != CX_ELMNT) clicon_err(OE_XML, 0, "No spec found %s (wrong xml type:%s)", xml_name(xc), xml_type2str(xml_type(xc))); else clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc)); goto done; } if (yc == yn){ /* Same yang */ if (userorder){ /* append: increment linearly until no longer equal */ retval = xml_insert_userorder(xp, xn, yn, mid, ins, key_val, nsc_key); goto done; } else /* Ordered by system */ cmp = xml_cmp(xn, xc, 0, 0); } else{ /* Not equal yang - compute diff */ cmp = yni - yang_order(yc); /* One case is a choice where * xc = , xn = * same order but different yang spec */ } if (low +1 == upper){ /* termination criterium */ if (cmp<0) { retval = mid; goto done; } retval = mid+1; goto done; } if (cmp == 0){ retval = mid; goto done; } else if (cmp < 0) return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, nsc_key, low, mid); else return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, nsc_key, mid+1, upper); done: return retval; } /*! Insert xc as child to xp in sorted place. Remove xc from previous parent. * @param[in] xp Parent xml node. If NULL just remove from old parent. * @param[in] x Child xml node to insert under xp * @param[in] ins Insert operation (if ordered-by user) * @param[in] key_val Key if x is LIST and ins is before/after, val if LEAF_LIST * @param[in] nsc_key Network namespace for key * @retval 0 OK * @retval -1 Error * @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort() */ int xml_insert(cxobj *xp, cxobj *xi, enum insert_type ins, char *key_val, cvec *nsc_key) { int retval = -1; cxobj *xa; int low = 0; int upper; yang_stmt *y; int userorder= 0; int yi; /* Global yang-stmt order */ int i; /* Ensure the intermediate state that xp is parent of x but has not yet been * added as a child */ if (xml_parent(xi) != NULL){ clicon_err(OE_XML, 0, "XML node %s should not have parent", xml_name(xi)); goto done; } if ((y = xml_spec(xi)) == NULL){ clicon_err(OE_XML, 0, "No spec found %s", xml_name(xi)); goto done; } upper = xml_child_nr(xp); /* Assume if there are any attributes, they are first in the list, mask them by raising low to skip them */ for (low=0; low 0) goto done; } xprev = x; } retval = 0; done: return retval; } /*! Given child tree x1c, find (first) matching child in base tree x0 and return as x0cp * @param[in] x0 Base tree node * @param[in] x1c Modification tree child * @param[in] yc Yang spec of tree child * @param[out] x0cp Matching base tree child (if any) * @retval 0 OK * @retval -1 Error * XXX: only handles first match */ int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp) { int retval = -1; cvec *cvk = NULL; /* vector of index keys */ cg_var *cvi; cxobj *xb; char *keyname; cxobj *x0c = NULL; yang_stmt *y0c; yang_stmt *y0p; yang_stmt *yp; /* yang parent */ cxobj **xvec = NULL; size_t xlen = 0; *x0cp = NULL; /* init return value */ /* Special case is if yc parent (yp) is choice/case * then find x0 child with same yc even though it does not match lexically * However this will give another y0c != yc */ if ((yp = yang_choice(yc)) != NULL){ x0c = NULL; while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { if ((y0c = xml_spec(x0c)) != NULL && (y0p = yang_choice(y0c)) != NULL && y0p == yp) break; /* x0c will have a value */ } *x0cp = x0c; goto ok; /* What to do if not found? */ } switch (yang_keyword_get(yc)){ case Y_CONTAINER: /* Equal regardless */ case Y_LEAF: /* Equal regardless */ break; case Y_LEAF_LIST: /* Match with name and value */ if (xml_body(x1c) == NULL) /* Treat as empty string */ goto ok; break; case Y_LIST: /* Match with key values */ cvk = yang_cvec_get(yc); /* Use Y_LIST cache, see ys_populate_list() */ cvi = NULL; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if ((xb = xml_find(x1c, keyname)) == NULL) goto ok; } default: break; } /* Get match. */ if (xml_find_keys(x0, x1c, yc, 0, &xvec, &xlen) < 0) goto done; if (xlen) *x0cp = xvec[0]; ok: retval = 0; done: if (xvec) free(xvec); return retval; } /*! API for search in XML child list with non-indexed variables */ static int xml_find_noyang_cvk(char *ns0, cxobj *xc, cvec *cvk, cxobj ***xvec, size_t *xlen) { int retval = -1; cg_var *cvi; char *ns; cxobj *xcc; char *keyname; char *keyval; char *body; cvi = NULL; /* Loop through index variables. xc should match all, on exit if cvi=NULL it macthes */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_name_get(cvi); keyval = cv_string_get(cvi); if (keyname==NULL || strcmp(keyname, ".")==0){ /* Index variable on form .= (self) */ body = xml_body(xc); if ((body==NULL && (keyval==NULL || strlen(keyval) == 0)) || /* both null, break */ (body != NULL && strcmp(body, keyval) == 0)) /* Name match, break */ continue; /* match */ break; /* nomatch */ } else{ /* Index variable on form = * Loop through children of the matched x (to match keyname and value) */ xcc = NULL; while ((xcc = xml_child_each(xc, xcc, CX_ELMNT)) != NULL) { if (xml2ns(xcc, xml_prefix(xcc), &ns) < 0) goto done; if (strcmp(ns0, ns) != 0) /* Namespace does not match, skip */ continue; if (strcmp(keyname, xml_name(xcc)) != 0) /* Name does not match, skip */ continue; body = xml_body(xcc); if (body==NULL && (keyval==NULL || strlen(keyval) == 0)) /* both null, break */ break; if (body != NULL && strcmp(body, keyval) == 0) /* Name match, break */ break; } if (xcc == NULL) break; /* No match found */ } } if (cvi == NULL) /* means we iterated through all indexes without breaks */ if (cxvec_append(xc, xvec, xlen) < 0) goto done; retval = 0; done: return retval; } /*! API for search in XML child list with no yang available * Fallback if no yang available. Only linear search for matching name, and eventual index match */ static int xml_find_noyang_name(cxobj *xp, char *ns0, char *name, cvec *cvk, cxobj ***xvec, size_t *xlen) { int retval = -1; cxobj *xc; char *ns; if (name == NULL || ns0 == NULL){ clicon_err(OE_XML, EINVAL, "name and namespace required"); goto done; } /* Go through children linearly */ xc = NULL; while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) { ns = NULL; if (xml2ns(xc, xml_prefix(xc), &ns) < 0) goto done; if (ns == NULL) continue; if (strcmp(ns0, ns) != 0) /* Namespace does not match, skip */ continue; if (strcmp(name, xml_name(xc)) != 0) /* Namespace does not match, skip */ continue; if (cvk){ /* Check indexes */ if (xml_find_noyang_cvk(ns0, xc, cvk, xvec, xlen) < 0) goto done; } else /* No index variables: just add node to found list */ if (cxvec_append(xc, xvec, xlen) < 0) goto done; } retval = 0; done: return retval; } /*! API for search in XML child list with yang available * @retval 1 OK * @retval 0 Revert, try again with no-yang search * @retval -1 Error */ static int xml_find_index_yang(cxobj *xp, yang_stmt *yc, cvec *cvk, cxobj ***xvec, size_t *xlen) { int retval = -1; cxobj *xc = NULL; cxobj *xk; cg_var *cvi = NULL; cbuf *cb = NULL; yang_stmt *yk; char *kname; cvec *ycvk; cg_var *ycv = NULL; int i; char *name; name = yang_argument_get(yc); if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } switch (yang_keyword_get(yc)){ case Y_LIST: cprintf(cb, "<%s>", name); ycvk = yang_cvec_get(yc); /* Check that those are proper index keys */ cvi = NULL; i = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { if ((kname = cv_name_get(cvi)) == NULL){ clicon_err(OE_YANG, ENOENT, "missing yang key name in cvk"); goto done; } /* Parameter in cvk is not key or not in right key order, then we cannot call * xml_find_keys and we need to revert to noyang */ if ((ycv = cvec_i(ycvk, i)) == NULL || strcmp(kname, cv_string_get(ycv))){ goto revert; break; } cprintf(cb, "<%s>%s", kname, cv_string_get(cvi), kname); i++; } cprintf(cb, "", name); break; case Y_LEAF_LIST: if (cvec_len(cvk) != 1){ clicon_err(OE_YANG, ENOENT, "expected exactly one leaf-list key"); goto done; } cvi = cvec_i(cvk, 0); cprintf(cb, "<%s>%s", name, cv_string_get(cvi), name); break; default: cprintf(cb, "<%s/>", name); break; } clicon_debug(1, "%s XML:%s", __FUNCTION__, cbuf_get(cb)); if (xml_parse_string(cbuf_get(cb), yc, &xc) < 0) goto done; if (xml_rootchild(xc, 0, &xc) < 0) goto done; /* Populate created XML tree with yang specs */ if (xml_spec_set(xc, yc) < 0) goto done; xk = NULL; while ((xk = xml_child_each(xc, xk, CX_ELMNT)) != NULL) { if ((yk = yang_find(yc, Y_LEAF, xml_name(xk))) == NULL){ clicon_err(OE_YANG, ENOENT, "yang spec of key %s not found", xml_name(xk)); goto done; } if (xml_spec_set(xk, yk) < 0) goto done; } if (xml_find_keys(xp, xc, yc, 1, xvec, xlen) < 0) goto done; retval = 1; /* OK */ done: if (cb) cbuf_free(cb); if (xc) xml_free(xc); return retval; revert: /* means try name-only*/ retval = 0; goto done; } #ifdef XML_EXPLICIT_INDEX /*! API for search in XML child list with yang available * @retval 1 OK * @retval 0 Revert, try again with no-yang search (or explicit index) * @retval -1 Error * XXX: can merge with xml_find_index_yang in some way, similar code */ static int xml_find_index_explicit(cxobj *xp, yang_stmt *yc, cvec *cvk, cxobj ***xvec, size_t *xlen) { int retval = -1; cg_var *cvi; if (yang_keyword_get(yc) == Y_LIST && cvec_len(cvk) == 1){ cvi = cvec_i(cvk, 0); goto revert; } retval = 1; /* OK */ done: return retval; revert: /* means try name-only*/ retval = 0; goto done; } #endif /* XML_EXPLICIT_INDEX */ /*! API for search in XML child list using indexes and binary search if applicable * * Generic search function as alternative to xml_find() and others that finds YANG associated * children of an XML tree node using indexes and optimized lookup. Optimized lookup is used only * if an associated YANG exists. The optimized lookup is based on binary trees but is used * The function takes as input: * 1) An XML tree (parent) node (cxobj). See note(1) below * 2) A set of parameters to define wanted children by name or yang-spec using yc, name, ns, yp: * a. yp and ns:name given: YANG spec of parent + ns:name derives yc. Not found: only ns:name * b. name given: yp is derived from xp spec, then goto 2b above. * 3) A vector of index expressions on the form = refining the child set: * a. cvk is NULL, return all children matching according to (B) * b. cvk contains = entries, return children with leafs matching . * See "Optimized" section below. * 4) A return vector of XML nodes matching name and index variables * * # Optimized * The function makes the index matching = above optimized using binary search if: * - yc is defined AND one of the following * - if xp is leaf-list and "id" is "." * - if xp is a yang list and "id" is a registered index key * - if xp is a yang list and first "id" is first leaf key, second "id" is second leaf key, etc. * - Otherwise search is made using linear search * * @param[in] xp Parent xml node. * @param[in] yc Yang spec of list child (preferred) See rule (2) above * @param[in] yp Yang spec of parent node or yang-spec/yang (Alternative if yc not given). * @param[in] ns Namespace (needed only if name is derived from top-symbol) * @param[in] name Name of child (not required if yc given) * @param[in] cvk List of keys and values as CLIgen vector on the form k1=foo, k2=bar * @param[out] xvec Array of found nodes * @param[out] xlen Len of xvec * @retval 0 OK, see xret * @retval -1 Error * @code * cxobj **xvec = NULL; * size_t xlen = 0; * cvec *cvk = NULL; vector of index keys * ... Populate cvk with key/values eg a:5 b:6 * if (clixon_xml_find_index(xp, yp, "a", ns, cvk, &xvec, &xlen) < 0) * err; * @endcode * Discussion: * (1) Rule 2 on how to get the child name election seems unecessary complex. First, it would be * nice to just state name and parent 2c. But xp as top-objects may sometimes be dummies, for * parsing, copy-buffers, or simply for XML nodes that do not have YANG. * (2) Short form could be: "first" only returning first object to get rid of vectors. * (3) Another short form could be: keyname,keyval instead of cvk for single keys. */ int clixon_xml_find_index(cxobj *xp, yang_stmt *yp, char *namespace, char *name, cvec *cvk, cxobj ***xvec, size_t *xlen) { int retval = -1; int ret; yang_stmt *yc = NULL; if (name == NULL){ clicon_err(OE_YANG, ENOENT, "name"); goto done; } if (yp == NULL) yp = xml_spec(xp); if (yp){/* 2. YANG spec of parent + name derives yc. If not found use just name. */ if (yang_keyword_get(yp) == Y_SPEC) yp = yang_find_module_by_namespace(yp, namespace); if (yp) yc = yang_find_datanode(yp, name); } if (yc){ if ((ret = xml_find_index_yang(xp, yc, cvk, xvec, xlen)) < 0) goto done; if (ret == 0){ /* This means yang method did not work for some reason * such as not being list key indexes in cvk, for example */ #ifdef XML_EXPLICIT_INDEX /* Check if (exactly one) explicit indexes in cvk */ if ((ret = xml_find_index_explicit(xp, yc, cvk, xvec, xlen)) < 0) goto done; if (ret == 0) #endif if (xml_find_noyang_name(xp, namespace, name, cvk, xvec, xlen) < 0) goto done; } } else if (xml_find_noyang_name(xp, namespace, name, cvk, xvec, xlen) < 0) goto done; retval = 0; done: return retval; } /*! Find positional parameter in xml child list, eg x/y[42]. Note, not optimized * * Create a temporary search object: a list (xc) with a key (xk) and call the binary search. * @param[in] xp Parent xml node. * @param[in] yc Yang spec of list child * @param[in] pos Position * @param[out] xvec Array of found nodes * @param[out] xlen Len of xvec * @retval 0 OK, see xret * @retval -1 Error */ int clixon_xml_find_pos(cxobj *xp, yang_stmt *yc, uint32_t pos, cxobj ***xvec, size_t *xlen) { int retval = -1; cxobj *xc = NULL; char *name; uint32_t u; if (yc == NULL){ clicon_err(OE_YANG, ENOENT, "yang spec not found"); goto done; } name = yang_argument_get(yc); u = 0; xc = NULL; while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) { if (strcmp(name, xml_name(xc))) continue; if (pos == u++){ /* Found */ if (cxvec_append(xc, xvec, xlen) < 0) goto done; break; } } retval = 0; done: return retval; }