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