1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2,
29 indicate your decision by deleting the provisions above and replace them with
30 the notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35
36 * XML sort and search functions when used with YANG
37 */
38
39 #ifdef HAVE_CONFIG_H
40 #include "clixon_config.h" /* generated by config & autoconf */
41 #endif
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <errno.h>
47 #include <string.h>
48 #include <limits.h>
49 #include <stdint.h>
50 #include <assert.h>
51 #include <syslog.h>
52
53 /* cligen */
54 #include <cligen/cligen.h>
55
56 /* clixon */
57 #include "clixon_err.h"
58 #include "clixon_log.h"
59 #include "clixon_string.h"
60 #include "clixon_queue.h"
61 #include "clixon_hash.h"
62 #include "clixon_handle.h"
63 #include "clixon_yang.h"
64 #include "clixon_xml.h"
65 #include "clixon_xml_nsctx.h"
66 #include "clixon_xml_io.h"
67 #include "clixon_xpath_ctx.h"
68 #include "clixon_xpath.h"
69 #include "clixon_options.h"
70 #include "clixon_xml_map.h"
71 #include "clixon_yang_type.h"
72 #include "clixon_yang_module.h"
73 #include "clixon_xml_vec.h"
74 #include "clixon_xml_sort.h"
75
76 /*! Get xml body value as cligen variable
77 * @param[in] x XML node (body and leaf/leaf-list)
78 * @param[out] cvp Pointer to cligen variable containing value of x body
79 * @retval 0 OK, cvp contains cv or NULL
80 * @retval -1 Error
81 * @note only applicable if x is body and has yang-spec and is leaf or leaf-list
82 * Move to clixon_xml.c?
83 * As a side-effect sets the cache.
84 * Clear cache with xml_cv_set(x, NULL)
85 */
86 static int
xml_cv_cache(cxobj * x,cg_var ** cvp)87 xml_cv_cache(cxobj *x,
88 cg_var **cvp)
89 {
90 int retval = -1;
91 cg_var *cv = NULL;
92 yang_stmt *y;
93 yang_stmt *yrestype;
94 enum cv_type cvtype;
95 int ret;
96 char *reason=NULL;
97 int options = 0;
98 uint8_t fraction = 0;
99 char *body;
100
101 if ((body = xml_body(x)) == NULL)
102 body="";
103 if ((cv = xml_cv(x)) != NULL)
104 goto ok;
105 if ((y = xml_spec(x)) == NULL){
106 clicon_err(OE_XML, EFAULT, "Yang binding missing for xml symbol %s, body:%s", xml_name(x), body);
107 goto done;
108 }
109 if (yang_type_get(y, NULL, &yrestype, &options, NULL, NULL, NULL, &fraction) < 0)
110 goto done;
111 yang2cv_type(yang_argument_get(yrestype), &cvtype);
112 if (cvtype==CGV_ERR){
113 clicon_err(OE_YANG, errno, "yang->cligen type %s mapping failed",
114 yang_argument_get(yrestype));
115 goto done;
116 }
117 if ((cv = cv_new(cvtype)) == NULL){
118 clicon_err(OE_YANG, errno, "cv_new");
119 goto done;
120 }
121 if (cvtype == CGV_DEC64)
122 cv_dec64_n_set(cv, fraction);
123
124 if ((ret = cv_parse1(body, cv, &reason)) < 0){
125 clicon_err(OE_YANG, errno, "cv_parse1");
126 goto done;
127 }
128 if (ret == 0){
129 clicon_err(OE_YANG, EINVAL, "cv parse error: %s\n", reason);
130 goto done;
131 }
132 if (xml_cv_set(x, cv) < 0)
133 goto done;
134 ok:
135 *cvp = cv;
136 cv = NULL;
137 retval = 0;
138 done:
139 if (reason)
140 free(reason);
141 if (cv)
142 cv_free(cv);
143 return retval;
144 }
145
146 static int
xml_cv_cache_clear(cxobj * xt)147 xml_cv_cache_clear(cxobj *xt)
148 {
149 int retval = -1;
150 cxobj *x = NULL;
151
152 while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL)
153 if (xml_cv_set(x, NULL) < 0)
154 goto done;
155 retval = 0;
156 done:
157 return retval;
158 }
159
160 /*! Help function to qsort for sorting entries in xml child vector same parent
161 * @param[in] x1 object 1
162 * @param[in] x2 object 2
163 * @param[in] same If set, x1 and x2 are member of same parent & enumeration
164 * is used (see explanation below)
165 * @param[in] skip1 Key matching skipped for keys not in x1 (see explanation)
166 * @param[in] explicit For list nodes, use explicit index variables, not keys
167 * @retval 0 If equal
168 * @retval <0 If x1 is less than x2
169 * @retval >0 If x1 is greater than x2
170 *
171 * There are distinct calls for this function:
172 * 1. For sorting in an existing list of XML children
173 * 2. For searching of an existing element in a list
174 * In the first case, there is a special case for "ordered-by-user", where
175 * if they have the same yang-spec, the existing order is used as tie-breaker.
176 * In other words, if order-by-system, or if the case (2) above, the existing
177 * order is ignored and the actual xml element contents is examined.
178 *
179 * Also, if pattern is set, list matching is not made so that x1 and x2 must have same keys.
180 * instead, x1 and x2 are considered equal if the keys that x1 have match. Keys that x2 but not
181 * x1 has are ignored.
182 * Example: x1: <x><k1>1</k1></x> and x2: <x><k1>1</k1><k2>2</k2></x> are considered equal.
183 * This is useful in searching for indexes in trees, where x1 is a search index and not a
184 * complete tree.
185 *
186 * @note empty value/NULL is smallest value
187 * @note some error cases return as -1 (qsort cant handle errors)
188 * @note some error cases return as -1 (qsort cant handle errors)
189 *
190 * @note "comparing" x1 and x2 here judges equality from a structural (model)
191 * perspective, ie both have the same yang spec, if they are lists, they have the
192 * the same keys. NOT that the values are equal!
193 * In other words, if x is a leaf with the same yang spec, <x>1</x> and <x>2</x> are
194 * "equal".
195 * If x is a list element (with key "k"),
196 * <x><k>42</42><y>foo</y></x> and <x><k>42</42><y>bar</y></x> are equal,
197 * but is not equal to <x><k>71</42><y>bar</y></x>
198 */
199 int
xml_cmp(cxobj * x1,cxobj * x2,int same,int skip1,char * indexvar)200 xml_cmp(cxobj *x1,
201 cxobj *x2,
202 int same,
203 int skip1,
204 char *indexvar)
205 {
206 yang_stmt *y1;
207 yang_stmt *y2;
208 int yi1 = 0;
209 int yi2 = 0;
210 cvec *cvk = NULL; /* vector of index keys */
211 cg_var *cvi;
212 int equal = 0;
213 char *b1;
214 char *b2;
215 char *keyname;
216 cg_var *cv1 = NULL;
217 cg_var *cv2 = NULL;
218 int nr1 = 0;
219 int nr2 = 0;
220 cxobj *x1b;
221 cxobj *x2b;
222 enum cxobj_type xt1;
223 enum cxobj_type xt2;
224
225 if (x1==NULL || x2==NULL)
226 goto done; /* shouldnt happen */
227 /* Sort according to attributes first */
228 if ((xt1 = xml_type(x1)) != (xt2 = xml_type(x2))){
229 if (xt1 == CX_ATTR){
230 equal = -1;
231 goto done;
232 }
233 else if (xt2 == CX_ATTR){
234 equal = 1;
235 goto done;
236 }
237 }
238 /* Here x1 and x2 are same type */
239 y1 = xml_spec(x1);
240 y2 = xml_spec(x2);
241 if (same){
242 nr1 = xml_enumerate_get(x1);
243 nr2 = xml_enumerate_get(x2);
244 }
245 if (y1==NULL && y2==NULL){
246 if (same)
247 equal = nr1-nr2;
248 goto done;
249 }
250 if (y1==NULL){
251 equal = -1;
252 goto done;
253 }
254 if (y2==NULL){
255 equal = 1;
256 goto done;
257 }
258 if (y1 != y2){
259 yi1 = yang_order(y1);
260 yi2 = yang_order(y2);
261 if ((equal = yi1-yi2) != 0)
262 goto done;
263 }
264 /* Now y1==y2, same Yang spec, can only be list or leaf-list,
265 * But first check exceptions, eg config false or ordered-by user
266 * otherwise sort according to key
267 * If the two elements are in the same list, and they are ordered-by user
268 * then do not look more into equivalence, use the enumeration in the
269 * existing list.
270 */
271 if (same &&
272 (
273 #ifndef STATE_ORDERED_BY_SYSTEM
274 yang_config(y1)==0 ||
275 #endif
276 yang_find(y1, Y_ORDERED_BY, "user") != NULL)){
277 equal = nr1-nr2;
278 goto done; /* Ordered by user or state data : maintain existing order */
279 }
280 switch (yang_keyword_get(y1)){
281 case Y_LEAF_LIST: /* Match with name and value */
282 b1 = xml_body(x1);
283 b2 = xml_body(x2);
284 if (b1 == NULL && b2 == NULL)
285 ;
286 else if (b1 == NULL)
287 equal = -1;
288 else if (b2 == NULL)
289 equal = 1;
290 else{
291 if (xml_cv_cache(x1, &cv1) < 0) /* error case */
292 goto done;
293 if (xml_cv_cache(x2, &cv2) < 0) /* error case */
294 goto done;
295 if (cv1 != NULL && cv2 != NULL)
296 equal = cv_cmp(cv1, cv2);
297 else if (cv1 == NULL && cv2 == NULL)
298 equal = 0;
299 else if (cv1 == NULL)
300 equal = -1;
301 else
302 equal = 1;
303 }
304 break;
305 case Y_LIST: /* Match with key values */
306 if (indexvar != NULL){
307 #ifdef XML_EXPLICIT_INDEX
308 x1b = xml_find(x1, indexvar);
309 x2b = xml_find(x2, indexvar);
310 if (x1b == NULL && x2b == NULL)
311 ;
312 else if (x1b == NULL)
313 equal = -1;
314 else if (x2b == NULL)
315 equal = 1;
316 else{
317 b1 = xml_body(x1b);
318 b2 = xml_body(x2b);
319 if (b1 == NULL && b2 == NULL)
320 ;
321 else if (b1 == NULL)
322 equal = -1;
323 else if (b2 == NULL)
324 equal = 1;
325 else{
326 if (xml_cv_cache(x1b, &cv1) < 0) /* error case */
327 goto done;
328 if (xml_cv_cache(x2b, &cv2) < 0) /* error case */
329 goto done;
330 assert(cv1 && cv2);
331 equal = cv_cmp(cv1, cv2);
332 }
333 }
334 if (equal)
335 break;
336 #endif /* XML_EXPLICIT_INDEX */
337 }
338 else {
339 /* Use Y_LIST cache (see struct yang_stmt) */
340 cvk = yang_cvec_get(y1); /* Use Y_LIST cache, see ys_populate_list() */
341 cvi = NULL;
342 while ((cvi = cvec_each(cvk, cvi)) != NULL) {
343 keyname = cv_string_get(cvi); /* operational data may have NULL keys*/
344 x1b = xml_find(x1, keyname);
345 /* match1: key matching skipped for keys not in x1 (see explanation) */
346 if (skip1 && x1b == NULL)
347 continue;
348 x2b = xml_find(x2, keyname);
349 if (x1b == NULL && x2b == NULL)
350 ;
351 else if (x1b == NULL)
352 equal = -1;
353 else if (x2b == NULL)
354 equal = 1;
355 else{
356 b1 = xml_body(x1b);
357 b2 = xml_body(x2b);
358 if (b1 == NULL && b2 == NULL)
359 ;
360 else if (b1 == NULL)
361 equal = -1;
362 else if (b2 == NULL)
363 equal = 1;
364 else{
365 if (xml_cv_cache(x1b, &cv1) < 0) /* error case */
366 goto done;
367 if (xml_cv_cache(x2b, &cv2) < 0) /* error case */
368 goto done;
369 assert(cv1 && cv2);
370 equal = cv_cmp(cv1, cv2);
371 }
372 }
373 if (equal)
374 break;
375 } /* while cvi */
376 }
377 break;
378 default:
379 /* This is a very special case such as for two choices - which is not validation correct, but we
380 should sort them according to nr1, nr2 since their yang is equal order */
381 if (same)
382 equal = nr1-nr2;
383 break;
384 } /* switch */
385 done:
386 clicon_debug(3, "%s %s %s eq:%d nr: %d %d yi: %d %d", __FUNCTION__, xml_name(x1), xml_name(x2), equal, nr1, nr2, yi1, yi2);
387 return equal;
388 }
389
390 /*!
391 * @note args are pointer ot pointers, to fit into qsort cmp function
392 */
393 static int
xml_cmp_qsort(const void * arg1,const void * arg2)394 xml_cmp_qsort(const void* arg1,
395 const void* arg2)
396 {
397 return xml_cmp(*(struct xml**)arg1, *(struct xml**)arg2, 1, 0, NULL);
398 }
399
400 /*! Sort children of an XML node
401 * Assume populated by yang spec.
402 * @param[in] x0 XML node
403 * @retval -1 Error, aborted at first error encounter
404 * @retval 0 OK, all nodes traversed (subparts may have been skipped)
405 * @retval 1 OK, aborted on first fn returned 1
406 * @see xml_apply - typically called by recursive apply function
407 * @see xml_sort_verify
408 */
409 int
xml_sort(cxobj * x)410 xml_sort(cxobj *x)
411 {
412 #ifndef STATE_ORDERED_BY_SYSTEM
413 yang_stmt *ys;
414
415 /* Abort sort if non-config (=state) data */
416 if ((ys = xml_spec(x)) != 0 && yang_config(ys)==0)
417 return 1;
418 #endif
419 xml_enumerate_children(x);
420 qsort(xml_childvec_get(x), xml_child_nr(x), sizeof(cxobj *), xml_cmp_qsort);
421 return 0;
422 }
423
424 /*! Recursively sort a tree
425 * Alt to use xml_apply
426 */
427 int
xml_sort_recurse(cxobj * xn)428 xml_sort_recurse(cxobj *xn)
429 {
430 int retval = -1;
431 cxobj *x;
432 int ret;
433
434 ret = xml_sort_verify(xn, NULL);
435 if (ret == 1) /* This node is not sortable */
436 goto ok;
437 if (ret == -1){ /* not sorted */
438 if ((ret = xml_sort(xn)) < 0)
439 goto done;
440 if (ret == 1) /* This node is not sortable */
441 goto ok;
442 }
443 if (xml_cv_cache_clear(xn) < 0)
444 goto done;
445 x = NULL;
446 while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
447 if (xml_sort_recurse(x) < 0)
448 goto done;
449 }
450 ok:
451 retval = 0;
452 done:
453 return retval;
454 }
455
456 /*! Special case search for ordered-by user or state data where linear sort is used
457 *
458 * @param[in] xp Parent XML node (go through its childre)
459 * @param[in] x1 XML node to match
460 * @param[in] yangi Yang order number (according to spec)
461 * @param[in] mid Where to start from (may be in middle of interval)
462 * @param[out] xvec Vector of matching XML return objects (can be empty)
463 * @retval 0 OK, see xvec (may be empty)
464 * @retval -1 Error
465 * XXX: only first match
466 */
467 static int
xml_find_keys_notsorted(cxobj * xp,cxobj * x1,int yangi,int mid,int skip1,clixon_xvec * xvec)468 xml_find_keys_notsorted(cxobj *xp,
469 cxobj *x1,
470 int yangi,
471 int mid,
472 int skip1,
473 clixon_xvec *xvec)
474 {
475 int retval = -1;
476 int i;
477 cxobj *xc;
478 yang_stmt *yc;
479
480 for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
481 xc = xml_child_i(xp, i);
482 yc = xml_spec(xc);
483 if (yangi != yang_order(yc)) /* wrong yang */
484 break;
485 if (xml_cmp(xc, x1, 0, skip1, NULL) == 0){
486 if (clixon_xvec_append(xvec, xc) < 0)
487 goto done;
488 goto ok; /* found */
489 }
490 }
491 for (i=mid-1; i>=0; i--){ /* Then decrement */
492 xc = xml_child_i(xp, i);
493 yc = xml_spec(xc);
494 if (yangi != yang_order(yc)) /* wrong yang */
495 break;
496 if (xml_cmp(xc, x1, 0, skip1, NULL) == 0){
497 if (clixon_xvec_append(xvec, xc) < 0)
498 goto done;
499 goto ok; /* found */
500 }
501 }
502 ok:
503 retval = 0;
504 done:
505 return retval;
506 }
507
508 /*! Find more equal objects in a vector up and down in the array of the present
509 * @param[in] childvec Vector of children of parent
510 * @param[in] childlen Length of child vector
511 * @param[in] x1 XML node to match
512 * @param[in] yangi Yang order number (according to spec)
513 * @param[in] mid Where to start from (may be in middle of interval)
514 * @param[out] xvec Vector of matching XML return objects (can be empty)
515 * @retval 0 OK, see xvec (may be empty)
516 * @retval -1 Error
517 */
518 static int
search_multi_equals(cxobj ** childvec,size_t childlen,cxobj * x1,int yangi,int mid,int skip1,clixon_xvec * xvec)519 search_multi_equals(cxobj **childvec,
520 size_t childlen,
521 cxobj *x1,
522 int yangi,
523 int mid,
524 int skip1,
525 clixon_xvec *xvec)
526 {
527 int retval = -1;
528 int i;
529 cxobj *xc;
530 yang_stmt *yc;
531
532 for (i=mid-1; i>=0; i--){ /* First decrement */
533 xc = childvec[i];
534 yc = xml_spec(xc);
535 if (yangi != yang_order(yc)) /* wrong yang */
536 break;
537 if (xml_cmp(x1, xc, 0, skip1, NULL) != 0)
538 break;
539 if (clixon_xvec_prepend(xvec, xc) < 0)
540 goto done;
541 }
542 for (i=mid+1; i<childlen; i++){ /* Then increment */
543 xc = childvec[i];
544 yc = xml_spec(xc);
545 if (yangi != yang_order(yc)) /* wrong yang */
546 break;
547 if (xml_cmp(x1, xc, 0, skip1, NULL) != 0)
548 break;
549 if (clixon_xvec_append(xvec, xc) < 0)
550 goto done;
551 }
552
553 retval = 0;
554 done:
555 return retval;
556 }
557
558 #ifdef XML_EXPLICIT_INDEX
559 /* XXX unify with search_multi_equals */
560 static int
search_multi_equals_xvec(clixon_xvec * childvec,cxobj * x1,int yangi,int mid,int skip1,clixon_xvec * xvec)561 search_multi_equals_xvec(clixon_xvec *childvec,
562 cxobj *x1,
563 int yangi,
564 int mid,
565 int skip1,
566 clixon_xvec *xvec)
567 {
568 int retval = -1;
569 int i;
570 cxobj *xc;
571 yang_stmt *yc;
572
573 for (i=mid-1; i>=0; i--){ /* First decrement */
574 xc = clixon_xvec_i(childvec, i);
575 yc = xml_spec(xc);
576 if (yangi != yang_order(yc)) /* wrong yang */
577 break;
578 if (xml_cmp(x1, xc, 0, skip1, NULL) != 0)
579 break;
580 if (clixon_xvec_prepend(xvec, xc) < 0)
581 goto done;
582 }
583 for (i=mid+1; i<clixon_xvec_len(childvec); i++){ /* Then increment */
584 xc = clixon_xvec_i(childvec, i);
585 yc = xml_spec(xc);
586 if (yangi != yang_order(yc)) /* wrong yang */
587 break;
588 if (xml_cmp(x1, xc, 0, skip1, NULL) != 0)
589 break;
590 if (clixon_xvec_append(xvec, xc) < 0)
591 goto done;
592 }
593 retval = 0;
594 done:
595 return retval;
596 }
597
598 /*! Insert xi in vector sorted according to index variable xi
599 * @param[in] x1 XML parent object (the list element)
600 * @param[in] ivec Sorted index vector
601 * @param[in] low Lower range limit
602 * @param[in] upper Upper range limit
603 * @param[in] max Upper range limit
604 * @param[out] eq If 0, not equal object found; if 1 pos is one equivalent object
605 * @retval 0 Position, where to insert xn
606 * @retval -1 Error
607 * @see xml_search_indexvar_binary which returns the found objects, this one just returns
608 * position of where to insert xn.
609 */
610 int
xml_search_indexvar_binary_pos(cxobj * x1,char * indexvar,clixon_xvec * ivec,int low,int upper,int max,int * eq)611 xml_search_indexvar_binary_pos(cxobj *x1,
612 char *indexvar,
613 clixon_xvec *ivec,
614 int low,
615 int upper,
616 int max,
617 int *eq)
618 {
619 int retval = -1;
620 int mid;
621 int cmp;
622 cxobj *xc;
623
624 if (upper < low){ /* beyond range */
625 clicon_err(OE_XML, 0, "low>upper %d %d", low, upper);
626 goto done;
627 }
628 if (low == upper){
629 retval = low;
630 goto done;
631 }
632 mid = (low + upper) / 2;
633 if (mid >= max){ /* beyond range */
634 clicon_err(OE_XML, 0, "Beyond range %d %d %d", low, mid, upper);
635 goto done;
636 }
637 xc = clixon_xvec_i(ivec, mid);
638 /* >0 means search upper interval, <0 lower interval, = 0 is equal */
639 cmp = xml_cmp(x1, xc, 0, 0, indexvar);
640 if (low +1 == upper){ /* termination criterium */
641 }
642 if (cmp == 0){
643 retval = mid; /* equal */
644 if (eq)
645 *eq = 1;
646 goto done;
647 }
648 else {
649 if (low +1 == upper){ /* termination criterium */
650 if (eq) /* No exact match */
651 *eq = 0;
652 if (cmp < 0) /* return either 0 if cmp<0 or +1 if cmp>0 */
653 retval = mid;
654 else
655 retval = mid+1;
656 goto done;
657 }
658 if (cmp < 0)
659 return xml_search_indexvar_binary_pos(x1, indexvar, ivec, low, mid, max, eq);
660 else
661 return xml_search_indexvar_binary_pos(x1, indexvar, ivec, mid+1, upper, max, eq);
662 }
663 done:
664 return retval;
665 }
666
667 static int
xml_search_indexvar(cxobj * xp,cxobj * x1,int yangi,int low,int upper,char * indexvar,clixon_xvec * xvec)668 xml_search_indexvar(cxobj *xp,
669 cxobj *x1,
670 int yangi,
671 int low,
672 int upper,
673 char *indexvar,
674 clixon_xvec *xvec)
675 {
676 int retval = -1;
677 clixon_xvec *ivec = NULL;
678 int ilen;
679 int pos;
680 int eq = 0;
681 cxobj *xc;
682
683 /* Check if (exactly one) explicit indexes in cvk */
684 if (xml_search_vector_get(xp, indexvar, &ivec) < 0)
685 goto done;
686 if (ivec){
687 ilen = clixon_xvec_len(ivec);
688 if ((pos = xml_search_indexvar_binary_pos(x1, indexvar,
689 ivec, 0,
690 ilen, ilen, &eq)) < 0)
691 goto done;
692 if (eq){ /* Found */
693 xc = clixon_xvec_i(ivec, pos);
694 if (clixon_xvec_append(xvec, xc) < 0)
695 goto done;
696 /* there may be more? */
697 if (search_multi_equals_xvec(ivec, x1, yangi, pos,
698 0, xvec) < 0)
699 goto done;
700 }
701 }
702 retval = 0;
703 done:
704 return retval;
705 }
706 #endif /* XML_EXPLICIT_INDEX */
707
708 /*! Find XML child under xp matching x1 using binary search
709 * @param[in] xp Parent xml node.
710 * @param[in] x1 Find this object among xp:s children
711 * @param[in] sorted If x1 is ordered by user or statedata, the list is not sorted
712 * @param[in] yangi Yang order
713 * @param[in] low Lower bound of childvec search interval
714 * @param[in] upper Lower bound of childvec search interval
715 * @param[in] skip1 Key matching skipped for keys not in x1
716 * @param[in] indexvar Override list key value search with explicit search index of x1
717 * @param[out] xvec Vector of matching XML return objects (can be empty)
718 * @retval 0 OK, see xvec (may be empty)
719 * @retval -1 Error
720 */
721 static int
xml_search_binary(cxobj * xp,cxobj * x1,int sorted,int yangi,int low,int upper,int skip1,char * indexvar,clixon_xvec * xvec)722 xml_search_binary(cxobj *xp,
723 cxobj *x1,
724 int sorted,
725 int yangi,
726 int low,
727 int upper,
728 int skip1,
729 char *indexvar,
730 clixon_xvec *xvec)
731 {
732 int retval = -1;
733 int mid;
734 int cmp;
735 cxobj *xc;
736 yang_stmt *y;
737
738 if (upper < low)
739 goto ok;
740 mid = (low + upper) / 2;
741 if (mid >= xml_child_nr(xp)) /* beyond range */
742 goto ok;
743 xc = xml_child_i(xp, mid);
744 if ((y = xml_spec(xc)) == NULL)
745 goto ok;
746 cmp = yangi-yang_order(y);
747 /* Here is right yang order == same yang? */
748 if (cmp == 0){
749 #ifdef XML_EXPLICIT_INDEX
750 if (indexvar){
751 if (xml_search_indexvar(xp, x1, yangi, low, upper, indexvar, xvec) < 0)
752 goto done;
753 goto ok;
754 }
755 #endif
756 /* >0 means search upper interval, <0 lower interval, = 0 is equal */
757 cmp = xml_cmp(x1, xc, 0, skip1, NULL);
758 if (cmp && !sorted){ /* Ordered by user (if not equal) */
759 retval = xml_find_keys_notsorted(xp, x1, yangi, mid, skip1, xvec);
760 goto done;
761 }
762 }
763 if (cmp == 0){
764 if (clixon_xvec_append(xvec, xc) < 0)
765 goto done;
766 /* there may be more? */
767 if (search_multi_equals(xml_childvec_get(xp), xml_child_nr(xp),
768 x1, yangi, mid, skip1, xvec) < 0)
769 goto done;
770 }
771 else if (cmp < 0)
772 xml_search_binary(xp, x1, sorted, yangi, low, mid-1, skip1, indexvar, xvec);
773 else
774 xml_search_binary(xp, x1, sorted, yangi, mid+1, upper, skip1, indexvar, xvec);
775 ok:
776 retval = 0;
777 done:
778 return retval;
779 }
780
781 /*! Search XML child under xp matching x1 using yang-based binary search for list/leaf-list keys
782 *
783 * Match is tried xp with x1 with either name only (container/leaf) or using keys (list/leaf-lists)
784 * Any non-key leafs or other structure of x1 is not matched (unless indexvar is set).
785 * If x1 is list or leaf-list, the function assumes key values / indexvar exists in x1.
786 *
787 * @param[in] xp Parent xml node.
788 * @param[in] x1 Find this object among xp:s children
789 * @param[in] yc Yang spec of x1
790 * @param[in] skip1 Key matching skipped for keys not in x1
791 * @param[in] indexvar Override list key value search with explicit search index var of x1
792 * @param[out] xvec Vector of matching XML return objects (can be empty)
793 * @retval 0 OK, see xvec (may be empty)
794 * @retval -1 Error
795 * @see xml_find_index for a generic search function
796 */
797 static int
xml_search_yang(cxobj * xp,cxobj * x1,yang_stmt * yc,int skip1,char * indexvar,clixon_xvec * xvec)798 xml_search_yang(cxobj *xp,
799 cxobj *x1,
800 yang_stmt *yc,
801 int skip1,
802 char *indexvar,
803 clixon_xvec *xvec)
804 {
805 int retval = -1;
806 cxobj *xa;
807 int low = 0;
808 int upper = xml_child_nr(xp);
809 int sorted = 1;
810 int yangi;
811
812 if (xp == NULL){
813 clicon_err(OE_XML, EINVAL, "xp is NULL");
814 goto done;
815 }
816 upper = xml_child_nr(xp);
817 /* Assume if there are any attributes, they are first in the list, mask
818 them by raising low to skip them */
819 for (low=0; low<upper; low++)
820 if ((xa = xml_child_i(xp, low)) == NULL || xml_type(xa) != CX_ATTR)
821 break;
822 #ifndef STATE_ORDERED_BY_SYSTEM
823 /* Find if non-config and if ordered-by-user */
824 if (yang_config_ancestor(yc)==0)
825 sorted = 0;
826 else
827 #endif
828 if (yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST)
829 sorted = (yang_find(yc, Y_ORDERED_BY, "user") == NULL);
830 yangi = yang_order(yc);
831
832 if (xml_search_binary(xp, x1, sorted, yangi, low, upper, skip1, indexvar, xvec) < 0)
833 goto done;
834 retval = 0;
835 done:
836 return retval;
837 }
838
839 /*! Insert xn in xp:s sorted child list (special case of ordered-by user)
840 * @param[in] xp Parent xml node. If NULL just remove from old parent.
841 * @param[in] xn Child xml node to insert under xp
842 * @param[in] yn Yang stmt of xml child node
843 * @param[in] ins Insert operation (if ordered-by user)
844 * @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
845 * @param[in] nsc_key Network namespace for key
846 * @retval i Order where xn should be inserted into xp:s children
847 * @retval -1 Error
848 * LIST: RFC 7950 7.8.6:
849 * The value of the "key" attribute is the key predicates of the
850 * full instance identifier (see Section 9.13) for the list entry.
851 * This means the value can be [x='a'] but the full instance-id should be prepended,
852 * such as /ex:system/ex:services[x='a']
853 *
854 * LEAF-LIST: RFC7950 7.7.9
855 * yang:insert="after"
856 * yang:value="3des-cbc">blowfish-cbc</cipher>)
857 */
858 static int
xml_insert_userorder(cxobj * xp,cxobj * xn,yang_stmt * yn,int mid,enum insert_type ins,char * key_val,cvec * nsc_key)859 xml_insert_userorder(cxobj *xp,
860 cxobj *xn,
861 yang_stmt *yn,
862 int mid,
863 enum insert_type ins,
864 char *key_val,
865 cvec *nsc_key)
866 {
867 int retval = -1;
868 int i;
869 cxobj *xc;
870 yang_stmt *yc;
871
872 switch (ins){
873 case INS_FIRST:
874 for (i=mid-1; i>=0; i--){ /* decrement */
875 xc = xml_child_i(xp, i);
876 yc = xml_spec(xc);
877 if (yc != yn){
878 retval = i+1;
879 goto done;
880 }
881 }
882 retval = i+1;
883 break;
884 case INS_LAST:
885 for (i=mid+1; i<xml_child_nr(xp); i++){ /* First increment */
886 xc = xml_child_i(xp, i);
887 yc = xml_spec(xc);
888 if (yc != yn){
889 retval = i;
890 goto done;
891 }
892 }
893 retval = i;
894 break;
895 case INS_BEFORE:
896 case INS_AFTER: /* see retval handling different between before and after */
897 if (key_val == NULL)
898 /* shouldnt happen */
899 clicon_err(OE_YANG, 0, "Missing key/value attribute when insert is before");
900 else{
901 switch (yang_keyword_get(yn)){
902 case Y_LEAF_LIST:
903 if ((xc = xpath_first(xp, nsc_key, "%s[.='%s']", xml_name(xn), key_val)) == NULL)
904 clicon_err(OE_YANG, 0, "bad-attribute: value, missing-instance: %s", key_val);
905 else {
906 if ((i = xml_child_order(xp, xc)) < 0)
907 clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");
908 else
909 retval = (ins==INS_BEFORE)?i:i+1;
910 }
911 break;
912 case Y_LIST:
913 if ((xc = xpath_first(xp, nsc_key, "%s%s", xml_name(xn), key_val)) == NULL)
914 clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s", key_val);
915 else {
916 if ((i = xml_child_order(xp, xc)) < 0)
917 clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");
918 else
919 retval = (ins==INS_BEFORE)?i:i+1;
920 }
921 break;
922 default:
923 clicon_err(OE_YANG, 0, "insert only for leaf or leaf-list");
924 break;
925 } /* switch */
926 }
927 }
928 done:
929 return retval;
930 }
931
932 /*! Insert xn in xp:s sorted child list
933 * Find a point in xp childvec with two adjacent nodes xi,xi+1 such that
934 * xi<=xn<=xi+1 or xn<=x0 or xmax<=xn
935 * @param[in] xp Parent xml node. If NULL just remove from old parent.
936 * @param[in] xn Child xml node to insert under xp
937 * @param[in] yn Yang stmt of xml child node
938 * @param[in] yni yang order
939 * @param[in] userorder Set if ordered-by user, otherwise 0
940 * @param[in] ins Insert operation (if ordered-by user)
941 * @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
942 * @param[in] nsc_key Network namespace for key
943 * @param[in] low Lower range limit
944 * @param[in] upper Upper range limit
945 * @retval i Order where xn should be inserted into xp:s children
946 * @retval -1 Error
947 */
948 static int
xml_insert2(cxobj * xp,cxobj * xn,yang_stmt * yn,int yni,int userorder,enum insert_type ins,char * key_val,cvec * nsc_key,int low,int upper)949 xml_insert2(cxobj *xp,
950 cxobj *xn,
951 yang_stmt *yn,
952 int yni,
953 int userorder,
954 enum insert_type ins,
955 char *key_val,
956 cvec *nsc_key,
957 int low,
958 int upper)
959 {
960 int retval = -1;
961 int mid;
962 int cmp;
963 cxobj *xc;
964 yang_stmt *yc;
965
966 if (low > upper){ /* beyond range */
967 clicon_err(OE_XML, 0, "low>upper %d %d", low, upper);
968 goto done;
969 }
970 if (low == upper){
971 retval = low;
972 goto done;
973 }
974 mid = (low + upper) / 2;
975 if (mid >= xml_child_nr(xp)){ /* beyond range */
976 clicon_err(OE_XML, 0, "Beyond range %d %d %d", low, mid, upper);
977 goto done;
978 }
979 xc = xml_child_i(xp, mid);
980 if ((yc = xml_spec(xc)) == NULL){
981 if (xml_type(xc) != CX_ELMNT)
982 clicon_err(OE_XML, 0, "No spec found %s (wrong xml type:%s)",
983 xml_name(xc), xml_type2str(xml_type(xc)));
984 else
985 clicon_err(OE_XML, 0, "No spec found %s", xml_name(xc));
986 goto done;
987 }
988 if (yc == yn){ /* Same yang */
989 if (userorder){ /* append: increment linearly until no longer equal */
990 retval = xml_insert_userorder(xp, xn, yn, mid, ins, key_val, nsc_key);
991 goto done;
992 }
993 else /* Ordered by system */
994 cmp = xml_cmp(xn, xc, 0, 0, NULL);
995 }
996 else{ /* Not equal yang - compute diff */
997 cmp = yni - yang_order(yc);
998 /* One case is a choice where
999 * xc = <tcp/>, xn = <udp/>
1000 * same order but different yang spec
1001 */
1002 }
1003 if (low +1 == upper){ /* termination criterium */
1004 if (cmp<0) {
1005 retval = mid;
1006 goto done;
1007 }
1008 retval = mid+1;
1009 goto done;
1010 }
1011 if (cmp == 0){
1012 retval = mid;
1013 goto done;
1014 }
1015 else if (cmp < 0)
1016 return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, nsc_key, low, mid);
1017 else
1018 return xml_insert2(xp, xn, yn, yni, userorder, ins, key_val, nsc_key, mid+1, upper);
1019 done:
1020 return retval;
1021 }
1022
1023 /*! Insert xc as child to xp in sorted place. Remove xc from previous parent.
1024 * @param[in] xp Parent xml node. If NULL just remove from old parent.
1025 * @param[in] x Child xml node to insert under xp
1026 * @param[in] ins Insert operation (if ordered-by user)
1027 * @param[in] key_val Key if x is LIST and ins is before/after, val if LEAF_LIST
1028 * @param[in] nsc_key Network namespace for key
1029 * @retval 0 OK
1030 * @retval -1 Error
1031 * @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
1032 * @note It is assumed that all siblings of xi are YANG bound
1033 */
1034 int
xml_insert(cxobj * xp,cxobj * xi,enum insert_type ins,char * key_val,cvec * nsc_key)1035 xml_insert(cxobj *xp,
1036 cxobj *xi,
1037 enum insert_type ins,
1038 char *key_val,
1039 cvec *nsc_key)
1040 {
1041 int retval = -1;
1042 cxobj *xa;
1043 int low = 0;
1044 int upper;
1045 yang_stmt *y;
1046 int userorder= 0;
1047 int yi; /* Global yang-stmt order */
1048 int i;
1049
1050 /* Ensure the intermediate state that xp is parent of x but has not yet been
1051 * added as a child
1052 */
1053 if (xml_parent(xi) != NULL){
1054 clicon_err(OE_XML, 0, "XML node %s should not have parent", xml_name(xi));
1055 goto done;
1056 }
1057 if ((y = xml_spec(xi)) == NULL){
1058 clicon_err(OE_XML, 0, "No spec found %s", xml_name(xi));
1059 goto done;
1060 }
1061 upper = xml_child_nr(xp);
1062 /* Assume if there are any attributes, they are first in the list, mask
1063 them by raising low to skip them */
1064 for (low=0; low<upper; low++)
1065 if ((xa = xml_child_i(xp, low)) == NULL || xml_type(xa)!=CX_ATTR)
1066 break;
1067 /* Find if non-config and if ordered-by-user */
1068 #ifndef STATE_ORDERED_BY_SYSTEM
1069 if (yang_config_ancestor(y)==0)
1070 userorder = 1;
1071 else
1072 #endif
1073 if (yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST)
1074 userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL);
1075 yi = yang_order(y);
1076 if ((i = xml_insert2(xp, xi, y, yi,
1077 userorder, ins, key_val, nsc_key,
1078 low, upper)) < 0)
1079 goto done;
1080 if (xml_child_insert_pos(xp, xi, i) < 0)
1081 goto done;
1082 xml_parent_set(xi, xp);
1083 /* clear namespace context cache of child */
1084 nscache_clear(xi);
1085
1086 retval = 0;
1087 done:
1088 return retval;
1089 }
1090
1091 /*! Verify all children of XML node are sorted according to xml_sort()
1092 * @param[in] x XML node. Check its children
1093 * @param[in] arg Dummy. Ensures xml_apply can be used with this fn
1094 * @retval 1 Not sortable
1095 * @retval 0 Sorted
1096 * @retval -1 Not sorted
1097 * @see xml_apply
1098 */
1099 int
xml_sort_verify(cxobj * x0,void * arg)1100 xml_sort_verify(cxobj *x0,
1101 void *arg)
1102 {
1103 int retval = -1;
1104 cxobj *x = NULL;
1105 cxobj *xprev = NULL;
1106 #ifndef STATE_ORDERED_BY_SYSTEM
1107 yang_stmt *ys;
1108
1109 /* Abort sort if non-config (=state) data */
1110 if ((ys = xml_spec(x0)) != 0 && yang_config_ancestor(ys)==0){
1111 retval = 1;
1112 goto done;
1113 }
1114 #endif
1115 if (xml_type(x0) == CX_ELMNT){
1116 xml_enumerate_children(x0);
1117 while ((x = xml_child_each(x0, x, -1)) != NULL) {
1118 if (xprev != NULL){ /* Check xprev <= x */
1119 if (xml_cmp(xprev, x, 1, 0, NULL) > 0)
1120 goto done;
1121 }
1122 xprev = x;
1123 }
1124 }
1125 retval = 0;
1126 done:
1127 return retval;
1128 }
1129
1130 /*! Given child tree x1c, find (first) matching child in base tree x0 and return as x0cp
1131 * @param[in] x0 Base tree node
1132 * @param[in] x1c Modification tree child
1133 * @param[in] yc Yang spec of tree child. If null revert to linear search.
1134 * @param[out] x0cp Matching base tree child (if any)
1135 * @retval 0 OK
1136 * @retval -1 Error
1137 * @note only handles first match
1138 */
1139 int
match_base_child(cxobj * x0,cxobj * x1c,yang_stmt * yc,cxobj ** x0cp)1140 match_base_child(cxobj *x0,
1141 cxobj *x1c,
1142 yang_stmt *yc,
1143 cxobj **x0cp)
1144 {
1145 int retval = -1;
1146 cvec *cvk = NULL; /* vector of index keys */
1147 cg_var *cvi;
1148 cxobj *xb;
1149 char *keyname;
1150 cxobj *x0c = NULL;
1151 yang_stmt *y0c;
1152 yang_stmt *y0p;
1153 yang_stmt *yp; /* yang parent */
1154 clixon_xvec *xvec = NULL;
1155
1156 *x0cp = NULL; /* init return value */
1157 /* Revert to simple xml lookup if no yang */
1158 if (yc == NULL){
1159 *x0cp = xml_find(x0, xml_name(x1c));
1160 goto ok;
1161 }
1162 /* Special case is if yc parent (yp) is choice/case
1163 * then find x0 child with same yc even though it does not match lexically
1164 * However this will give another y0c != yc
1165 */
1166 if ((yp = yang_choice(yc)) != NULL){
1167 x0c = NULL;
1168 while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
1169 if ((y0c = xml_spec(x0c)) != NULL &&
1170 (y0p = yang_choice(y0c)) != NULL &&
1171 y0p == yp)
1172 break; /* x0c will have a value */
1173 }
1174 *x0cp = x0c;
1175 goto ok; /* What to do if not found? */
1176 }
1177 switch (yang_keyword_get(yc)){
1178 case Y_CONTAINER: /* Equal regardless */
1179 case Y_LEAF: /* Equal regardless */
1180 break;
1181 case Y_LEAF_LIST: /* Match with name and value */
1182 if (xml_body(x1c) == NULL) /* Treat as empty string */
1183 goto ok;
1184 break;
1185 case Y_LIST: /* Match with key values */
1186 cvk = yang_cvec_get(yc); /* Use Y_LIST cache, see ys_populate_list() */
1187 cvi = NULL;
1188 while ((cvi = cvec_each(cvk, cvi)) != NULL) {
1189 keyname = cv_string_get(cvi);
1190 if ((xb = xml_find(x1c, keyname)) == NULL)
1191 goto ok;
1192 }
1193 default:
1194 break;
1195 }
1196 if ((xvec = clixon_xvec_new()) == NULL)
1197 goto done;
1198 /* Get match. */
1199 if (xml_search_yang(x0, x1c, yc, 0, 0, xvec) < 0)
1200 goto done;
1201 if (clixon_xvec_len(xvec))
1202 *x0cp = clixon_xvec_i(xvec, 0);
1203 ok:
1204 retval = 0;
1205 done:
1206 if (xvec)
1207 clixon_xvec_free(xvec);
1208 return retval;
1209 }
1210
1211 /*! API for search in XML child list with non-indexed variables
1212 */
1213 static int
xml_find_noyang_cvk(char * ns0,cxobj * xc,cvec * cvk,clixon_xvec * xvec)1214 xml_find_noyang_cvk(char *ns0,
1215 cxobj *xc,
1216 cvec *cvk,
1217 clixon_xvec *xvec)
1218 {
1219 int retval = -1;
1220 cg_var *cvi;
1221 char *ns;
1222 cxobj *xcc;
1223 char *keyname;
1224 char *keyval;
1225 char *body;
1226
1227 cvi = NULL;
1228 /* Loop through index variables. xc should match all, on exit if cvi=NULL it macthes */
1229 while ((cvi = cvec_each(cvk, cvi)) != NULL) {
1230 keyname = cv_name_get(cvi);
1231 keyval = cv_string_get(cvi);
1232 if (keyname==NULL || strcmp(keyname, ".")==0){ /* Index variable on form .=<val> (self) */
1233 body = xml_body(xc);
1234 if ((body==NULL && (keyval==NULL || strlen(keyval) == 0)) || /* both null, break */
1235 (body != NULL && strcmp(body, keyval) == 0)) /* Name match, break */
1236 continue; /* match */
1237 break; /* nomatch */
1238 }
1239 else{
1240 /* Index variable on form <id>=<val>
1241 * Loop through children of the matched x (to match keyname and value) */
1242 xcc = NULL;
1243 while ((xcc = xml_child_each(xc, xcc, CX_ELMNT)) != NULL) {
1244 if (xml2ns(xcc, xml_prefix(xcc), &ns) < 0)
1245 goto done;
1246 if (strcmp(ns0, ns) != 0) /* Namespace does not match, skip */
1247 continue;
1248 if (strcmp(keyname, xml_name(xcc)) != 0) /* Name does not match, skip */
1249 continue;
1250 body = xml_body(xcc);
1251 if (body==NULL && (keyval==NULL || strlen(keyval) == 0)) /* both null, break */
1252 break;
1253 if (body != NULL && strcmp(body, keyval) == 0) /* Name match, break */
1254 break;
1255 }
1256 if (xcc == NULL)
1257 break; /* No match found */
1258 }
1259 }
1260 if (cvi == NULL) /* means we iterated through all indexes without breaks */
1261 if (clixon_xvec_append(xvec, xc) < 0)
1262 goto done;
1263 retval = 0;
1264 done:
1265 return retval;
1266 }
1267
1268 /*! API for search in XML child list with no yang available
1269 * Fallback if no yang available. Only linear search for matching name, and eventual index match
1270 */
1271 static int
xml_find_noyang_name(cxobj * xp,char * ns0,char * name,cvec * cvk,clixon_xvec * xvec)1272 xml_find_noyang_name(cxobj *xp,
1273 char *ns0,
1274 char *name,
1275 cvec *cvk,
1276 clixon_xvec *xvec)
1277
1278 {
1279 int retval = -1;
1280 cxobj *xc;
1281 char *ns;
1282
1283 if (name == NULL || ns0 == NULL){
1284 clicon_err(OE_XML, EINVAL, "name and namespace required");
1285 goto done;
1286 }
1287 /* Go through children linearly */
1288 xc = NULL;
1289 while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) {
1290 ns = NULL;
1291 if (xml2ns(xc, xml_prefix(xc), &ns) < 0)
1292 goto done;
1293 if (ns == NULL)
1294 continue;
1295 if (strcmp(ns0, ns) != 0) /* Namespace does not match, skip */
1296 continue;
1297 if (strcmp(name, xml_name(xc)) != 0) /* Namespace does not match, skip */
1298 continue;
1299 if (cvk){ /* Check indexes */
1300 if (xml_find_noyang_cvk(ns0, xc, cvk, xvec) < 0)
1301 goto done;
1302 }
1303 else /* No index variables: just add node to found list */
1304 if (clixon_xvec_append(xvec, xc) < 0)
1305 goto done;
1306 }
1307 retval = 0;
1308 done:
1309 return retval;
1310 }
1311
1312 /*! Try to find an XML child from parent with yang available using list keys and leaf-lists
1313 *
1314 * Must be populated with Yang specs, parent must be list or leaf-list, and (for list) search
1315 * index MUST be keys in the order they are declared.
1316 * First identify that this search qualifies for yang-based list/leaf-list optimized search,
1317 * - if no, revert (return 0) so that the overlying algorithm can try next or fallback to
1318 * linear seacrh
1319 * - if yes, then construct a dummy search object and find it in the list of xp:s children
1320 * using binary search
1321 * @param[in] xp Parent xml node.
1322 * @param[in] yc Yang spec of list child (preferred) See rule (2) above
1323 * @param[in] cvk List of keys and values as CLIgen vector on the form k1=foo, k2=bar
1324 * @param[out] xvec Array of found nodes
1325 * @retval 1 OK
1326 * @retval 0 Revert, try again with no-yang search
1327 * @retval -1 Error
1328 */
1329 static int
xml_find_index_yang(cxobj * xp,yang_stmt * yc,cvec * cvk,clixon_xvec * xvec)1330 xml_find_index_yang(cxobj *xp,
1331 yang_stmt *yc,
1332 cvec *cvk,
1333 clixon_xvec *xvec)
1334 {
1335 int retval = -1;
1336 cxobj *xc = NULL;
1337 cxobj *xk;
1338 cg_var *cvi = NULL;
1339 cbuf *cb = NULL;
1340 yang_stmt *yk;
1341 char *kname;
1342 cvec *ycvk;
1343 cg_var *ycv = NULL;
1344 int i;
1345 char *name;
1346 int revert = 0;
1347 char *indexvar = NULL;
1348
1349 if (xp == NULL){
1350 clicon_err(OE_XML, EINVAL, "xp is NULL");
1351 goto done;
1352 }
1353 name = yang_argument_get(yc);
1354 if ((cb = cbuf_new()) == NULL){
1355 clicon_err(OE_UNIX, errno, "cbuf_new");
1356 goto done;
1357 }
1358 switch (yang_keyword_get(yc)){
1359 case Y_LIST:
1360 cprintf(cb, "<%s>", name);
1361 ycvk = yang_cvec_get(yc); /* Check that those are proper index keys */
1362 cvi = NULL;
1363 if (cvk == NULL){ /* If list and no keys, all should match */
1364 revert++;
1365 break;
1366 }
1367 i = 0;
1368 while ((cvi = cvec_each(cvk, cvi)) != NULL) {
1369 if ((kname = cv_name_get(cvi)) == NULL){
1370 clicon_err(OE_YANG, ENOENT, "missing yang key name in cvk");
1371 goto done;
1372 }
1373 /* Parameter in cvk is not key or not in right key order, then we cannot call
1374 * xml_find_keys and we need to revert to noyang
1375 */
1376 if ((ycv = cvec_i(ycvk, i)) == NULL || strcmp(kname, cv_string_get(ycv))){
1377 revert++;
1378 break;
1379 }
1380 cprintf(cb, "<%s>%s</%s>", kname, cv_string_get(cvi), kname);
1381 i++;
1382 }
1383 if (revert)
1384 break;
1385 cprintf(cb, "</%s>", name);
1386 break;
1387 case Y_LEAF_LIST:
1388 if (cvec_len(cvk) != 1){
1389 clicon_err(OE_YANG, ENOENT, "expected exactly one leaf-list key");
1390 goto done;
1391 }
1392 cvi = cvec_i(cvk, 0);
1393 cprintf(cb, "<%s>%s</%s>", name, cv_string_get(cvi), name);
1394 break;
1395 default:
1396 cprintf(cb, "<%s/>", name);
1397 break;
1398 }
1399 #ifdef XML_EXPLICIT_INDEX
1400 if (revert){
1401 char *iname;
1402 yang_stmt *yi;
1403 if (cvec_len(cvk) != 1 ||
1404 (cvi = cvec_i(cvk, 0)) == NULL ||
1405 (iname = cv_name_get(cvi)) == NULL ||
1406 (yi = yang_find_datanode(yc, iname)) == NULL ||
1407 yang_flag_get(yi, YANG_FLAG_INDEX) == 0)
1408 goto revert;
1409 cbuf_reset(cb);
1410 cprintf(cb, "<%s><%s>%s</%s></%s>", name, iname, cv_string_get(cvi), iname, name);
1411 indexvar = iname;
1412 }
1413 #else
1414 if (revert)
1415 goto revert;
1416 #endif
1417 if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, &xc, NULL) < 0)
1418 goto done;
1419 if (xml_rootchild(xc, 0, &xc) < 0)
1420 goto done;
1421 /* Populate created XML tree with yang specs */
1422 if (xml_spec_set(xc, yc) < 0)
1423 goto done;
1424 xk = NULL;
1425 while ((xk = xml_child_each(xc, xk, CX_ELMNT)) != NULL) {
1426 if ((yk = yang_find(yc, Y_LEAF, xml_name(xk))) == NULL){
1427 clicon_err(OE_YANG, ENOENT, "yang spec of key %s not found", xml_name(xk));
1428 goto done;
1429 }
1430 if (xml_spec_set(xk, yk) < 0)
1431 goto done;
1432 }
1433 if (xml_search_yang(xp, xc, yc, 1, indexvar, xvec) < 0)
1434 goto done;
1435 retval = 1; /* OK */
1436 done:
1437 if (cb)
1438 cbuf_free(cb);
1439 if (xc)
1440 xml_free(xc);
1441 return retval;
1442 revert: /* means give up yang/key search, try next (eg explicit/noyang) */
1443 retval = 0;
1444 goto done;
1445 }
1446
1447 /*! API for search in XML child list using indexes and binary search if applicable
1448 *
1449 * Generic search function as alternative to xml_find() and others that finds YANG associated
1450 * children of an XML tree node using indexes and optimized lookup. Optimized lookup is used only
1451 * if an associated YANG exists. The optimized lookup is based on binary trees but is used
1452 * The function takes as input:
1453 * 1) An XML tree (parent) node (cxobj). See note(1) below
1454 * 2) A set of parameters to define wanted children by name or yang-spec using yc, name, ns, yp:
1455 * a. yp and ns:name given: YANG spec of parent + ns:name derives yc. Not found: only ns:name
1456 * b. name given: yp is derived from xp spec, then goto 2b above.
1457 * 3) A vector of index expressions on the form <id>=<val> refining the child set:
1458 * a. cvk is NULL, return all children matching according to (B)
1459 * b. cvk contains <id>=<val> entries, return children with <id> leafs matching <val>.
1460 * See "Optimized" section below.
1461 * 4) A return vector of XML nodes matching name and index variables
1462 *
1463 * # Optimized
1464 * The function makes the index matching <id>=<val> above optimized using binary search if:
1465 * - yc is defined AND one of the following
1466 * - if xp is leaf-list and "id" is "."
1467 * - if xp is a yang list and "id" is a registered index key
1468 * - if xp is a yang list and first "id" is first leaf key, second "id" is second leaf key, etc.
1469 * - Otherwise search is made using linear search
1470 *
1471 * @param[in] xp Parent xml node.
1472 * @param[in] yp Yang spec of parent node or yang-spec/yang (Alternative if yc not given).
1473 * @param[in] ns Namespace (needed only if name is derived from top-symbol)
1474 * @param[in] name Name of child
1475 * @param[in] cvk List of keys and values as CLIgen vector on the form k1=foo, k2=bar
1476 * @param[out] xvec Array of result nodes. Must be initialized on entry
1477 * @retval 0 OK, see xret
1478 * @retval -1 Error
1479 * @code
1480 * clixon_xvec *xv = NULL;
1481 * cvec *cvk = NULL; vector of index keys
1482 * cxobj *x;
1483 * ... Populate cvk with key/values eg a:5 b:6
1484 * if ((xv = clixon_xvec_new()) == NULL)
1485 * err;
1486 * if (clixon_xml_find_index(xp, yp, NULL, "a", ns, cvk, xv) < 0)
1487 * err;
1488 * for (i=0; i<clixon_xvec_len(xv); i++){
1489 * x = clixon_xpath_i(xv, i);
1490 * ...
1491 * }
1492 * clixon_xvec_free(xvec);
1493 * @endcode
1494 * Discussion:
1495 * (1) Rule 2 on how to get the child name election seems unecessary complex. First, it would be
1496 * nice to just state name and parent 2c. But xp as top-objects may sometimes be dummies, for
1497 * parsing, copy-buffers, or simply for XML nodes that do not have YANG.
1498 */
1499 int
clixon_xml_find_index(cxobj * xp,yang_stmt * yp,char * ns,char * name,cvec * cvk,clixon_xvec * xvec)1500 clixon_xml_find_index(cxobj *xp,
1501 yang_stmt *yp,
1502 char *ns,
1503 char *name,
1504 cvec *cvk,
1505 clixon_xvec *xvec)
1506 {
1507 int retval = -1;
1508 int ret;
1509 yang_stmt *yc = NULL;
1510
1511 if (xvec == NULL){
1512 clicon_err(OE_YANG, EINVAL, "xvec");
1513 goto done;
1514 }
1515 if (name == NULL){
1516 clicon_err(OE_YANG, EINVAL, "name");
1517 goto done;
1518 }
1519 if (yp == NULL)
1520 yp = xml_spec(xp);
1521 if (yp){/* 2. YANG spec of parent + name derives yc. If not found use just name. */
1522 if (yang_keyword_get(yp) == Y_SPEC)
1523 yp = yang_find_module_by_namespace(yp, ns);
1524 if (yp)
1525 yc = yang_find_datanode(yp, name);
1526 }
1527 if (yc){
1528 if ((ret = xml_find_index_yang(xp, yc, cvk, xvec)) < 0)
1529 goto done;
1530 if (ret == 0){ /* This means yang method did not work for some reason
1531 * such as not being list key indexes in cvk, for example
1532 */
1533 if (xml_find_noyang_name(xp, ns, name, cvk, xvec) < 0)
1534 goto done;
1535 }
1536 }
1537 else
1538 if (xml_find_noyang_name(xp, ns, name, cvk, xvec) < 0)
1539 goto done;
1540 retval = 0;
1541 done:
1542 return retval;
1543 }
1544
1545 /*! Find positional parameter in xml child list, eg x/y[42]. Note, not optimized
1546 *
1547 * Create a temporary search object: a list (xc) with a key (xk) and call the binary search.
1548 * @param[in] xp Parent xml node.
1549 * @param[in] yc Yang spec of list child
1550 * @param[in] pos Position
1551 * @param[out] xvec Array of found nodes
1552 * @retval 0 OK, see xret
1553 * @retval -1 Error
1554 */
1555 int
clixon_xml_find_pos(cxobj * xp,yang_stmt * yc,uint32_t pos,clixon_xvec * xvec)1556 clixon_xml_find_pos(cxobj *xp,
1557 yang_stmt *yc,
1558 uint32_t pos,
1559 clixon_xvec *xvec)
1560 {
1561 int retval = -1;
1562 cxobj *xc = NULL;
1563 char *name;
1564 uint32_t u;
1565
1566 if (yc == NULL){
1567 clicon_err(OE_YANG, ENOENT, "yang spec not found");
1568 goto done;
1569 }
1570 name = yang_argument_get(yc);
1571 u = 0;
1572 xc = NULL;
1573 while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) {
1574 if (strcmp(name, xml_name(xc)))
1575 continue;
1576 if (pos == u++){ /* Found */
1577 if (clixon_xvec_append(xvec, xc) < 0)
1578 goto done;
1579 break;
1580 }
1581 }
1582 retval = 0;
1583 done:
1584 return retval;
1585 }
1586