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, indicate
29   your decision by deleting the provisions above and replace them with the
30   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   * The example have the following optional arguments that you can pass as
37   * argc/argv after -- in clixon_backend:
38   *  -r  enable the reset function
39   *  -s  enable the state function
40   *  -S <file>  read state data from file, otherwise construct it programmatically (requires -s)
41   *  -i  read state file on init not by request for optimization (requires -sS <file>)
42   *  -u  enable upgrade function - auto-upgrade testing
43   *  -U  general-purpose upgrade
44   *  -t  enable transaction logging (cal syslog for every transaction)
45   *  -v <xpath> Failing validate and commit if <xpath> is present (synthetic error)
46  */
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <stdint.h>
50 #include <inttypes.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <signal.h>
54 #include <unistd.h>
55 #include <syslog.h>
56 #include <fcntl.h>
57 #include <sys/time.h>
58 
59 /* clicon */
60 #include <cligen/cligen.h>
61 
62 /* Clicon library functions. */
63 #include <clixon/clixon.h>
64 
65 /* These include signatures for plugin and transaction callbacks. */
66 #include <clixon/clixon_backend.h>
67 
68 /* Command line options to be passed to getopt(3) */
69 #define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:"
70 
71 /*! Variable to control if reset code is run.
72  * The reset code inserts "extra XML" which assumes ietf-interfaces is
73  * loaded, and this is not always the case.
74  * Start backend with -- -r
75  */
76 static int _reset = 0;
77 
78 /*! Variable to control if state code is run
79  * The state code adds extra non-config data
80  * Start backend with -- -s
81  */
82 static int _state = 0;
83 
84 /*! File where state XML is read from, if _state is true -- -sS <file>
85  * Primarily for testing
86  * Start backend with -- -sS <file>
87  */
88 static char *_state_file = NULL;
89 
90 /*! Read state file init on startup instead of on request
91  * Primarily for testing
92  * Start backend with -- -siS <file>
93  */
94 static int _state_file_init = 0;
95 static cxobj *_state_xstate = NULL;
96 
97 /*! Variable to control module-specific upgrade callbacks.
98  * If set, call test-case for upgrading ietf-interfaces, otherwise call
99  * auto-upgrade
100  * Start backend with -- -u
101  */
102 static int _module_upgrade = 0;
103 
104 /*! Variable to control general-purpose upgrade callbacks.
105  * Start backend with -- -U
106  */
107 static int _general_upgrade = 0;
108 
109 /*! Variable to control transaction logging (for debug)
110  * If set, call syslog for every transaction callback
111  * Start backend with -- -t
112  */
113 static int _transaction_log = 0;
114 
115 /*! Variable to control transaction logging (for debug)
116  * If set, call syslog for every transaction callback
117  * Start backend with -- -v <xpath>
118  */
119 static char *_validate_fail_xpath = NULL;
120 static int   _validate_fail_toggle = 0; /* fail at validate and commit */
121 
122 /* forward */
123 static int example_stream_timer_setup(clicon_handle h);
124 
125 int
main_begin(clicon_handle h,transaction_data td)126 main_begin(clicon_handle    h,
127 	   transaction_data td)
128 {
129     if (_transaction_log)
130 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
131     return 0;
132 }
133 /*! This is called on validate (and commit). Check validity of candidate
134  */
135 int
main_validate(clicon_handle h,transaction_data td)136 main_validate(clicon_handle    h,
137 	      transaction_data td)
138 {
139     if (_transaction_log)
140 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
141     if (_validate_fail_xpath){
142 	if (_validate_fail_toggle==0 &&
143 	    xpath_first(transaction_target(td), NULL, "%s", _validate_fail_xpath)){
144 	    _validate_fail_toggle = 1; /* toggle if triggered */
145 	    clicon_err(OE_XML, 0, "User error");
146 	    return -1; /* induce fail */
147 	}
148     }
149     return 0;
150 }
151 
152 int
main_complete(clicon_handle h,transaction_data td)153 main_complete(clicon_handle    h,
154 	      transaction_data td)
155 {
156     if (_transaction_log)
157 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
158     return 0;
159 }
160 
161 /*! This is called on commit. Identify modifications and adjust machine state
162  */
163 int
main_commit(clicon_handle h,transaction_data td)164 main_commit(clicon_handle    h,
165 	    transaction_data td)
166 {
167     cxobj  *target = transaction_target(td); /* wanted XML tree */
168     cxobj **vec = NULL;
169     int     i;
170     int     len;
171     cvec   *nsc = NULL;
172 
173     if (_transaction_log)
174 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
175 
176     if (_validate_fail_xpath){
177 	if (_validate_fail_toggle==1 &&
178 	    xpath_first(transaction_target(td), NULL, "%s", _validate_fail_xpath)){
179 	    _validate_fail_toggle = 0; /* toggle if triggered */
180 	    clicon_err(OE_XML, 0, "User error");
181 	    return -1; /* induce fail */
182 	}
183     }
184 
185     /* Create namespace context for xpath */
186     if ((nsc = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
187 	goto done;
188 
189     /* Get all added i/fs */
190     if (xpath_vec_flag(target, nsc, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
191 	return -1;
192     if (clicon_debug_get())
193 	for (i=0; i<len; i++)             /* Loop over added i/fs */
194 	    xml_print(stdout, vec[i]); /* Print the added interface */
195   done:
196     if (nsc)
197 	xml_nsctx_free(nsc);
198     if (vec)
199 	free(vec);
200     return 0;
201 }
202 
203 int
main_commit_done(clicon_handle h,transaction_data td)204 main_commit_done(clicon_handle    h,
205 		 transaction_data td)
206 {
207     if (_transaction_log)
208 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
209     return 0;
210 }
211 
212 int
main_revert(clicon_handle h,transaction_data td)213 main_revert(clicon_handle    h,
214 	    transaction_data td)
215 {
216     if (_transaction_log)
217 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
218     return 0;
219 }
220 
221 int
main_end(clicon_handle h,transaction_data td)222 main_end(clicon_handle    h,
223 	 transaction_data td)
224 {
225     if (_transaction_log)
226 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
227     return 0;
228 }
229 
230 int
main_abort(clicon_handle h,transaction_data td)231 main_abort(clicon_handle    h,
232 	   transaction_data td)
233 {
234     if (_transaction_log)
235 	transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
236     return 0;
237 }
238 
239 /*! Routing example notification timer handler. Here is where the periodic action is
240  */
241 static int
example_stream_timer(int fd,void * arg)242 example_stream_timer(int   fd,
243 		     void *arg)
244 {
245     int                    retval = -1;
246     clicon_handle          h = (clicon_handle)arg;
247 
248     /* XXX Change to actual netconf notifications and namespace */
249     if (stream_notify(h, "EXAMPLE", "<event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
250 	goto done;
251     if (example_stream_timer_setup(h) < 0)
252 	goto done;
253     retval = 0;
254  done:
255     return retval;
256 }
257 
258 /*! Set up example stream notification timer
259  */
260 static int
example_stream_timer_setup(clicon_handle h)261 example_stream_timer_setup(clicon_handle h)
262 {
263     struct timeval t, t1;
264 
265     gettimeofday(&t, NULL);
266     t1.tv_sec = 5; t1.tv_usec = 0;
267     timeradd(&t, &t1, &t);
268     return clixon_event_reg_timeout(t, example_stream_timer, h, "example stream timer");
269 }
270 
271 /*! Smallest possible RPC declaration for test
272  * Yang/XML:
273  * If the RPC operation invocation succeeded and no output parameters
274  * are returned, the <rpc-reply> contains a single <ok/> element defined
275  * in [RFC6241].
276  */
277 static int
empty_rpc(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)278 empty_rpc(clicon_handle h,            /* Clicon handle */
279 	  cxobj        *xe,           /* Request: <rpc><xn></rpc> */
280 	  cbuf         *cbret,        /* Reply eg <rpc-reply>... */
281 	  void         *arg,          /* client_entry */
282 	  void         *regarg)       /* Argument given at register */
283 {
284     cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
285     return 0;
286 }
287 
288 /*! More elaborate example RPC for testing
289  * The RPC returns the incoming parameters
290  */
291 static int
example_rpc(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)292 example_rpc(clicon_handle h,            /* Clicon handle */
293 	    cxobj        *xe,           /* Request: <rpc><xn></rpc> */
294 	    cbuf         *cbret,        /* Reply eg <rpc-reply>... */
295 	    void         *arg,          /* client_entry */
296 	    void         *regarg)       /* Argument given at register */
297 {
298     int    retval = -1;
299     cxobj *x = NULL;
300     char  *namespace;
301 
302     /* get namespace from rpc name, return back in each output parameter */
303     if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){
304 	clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe));
305 	goto done;
306     }
307     cprintf(cbret, "<rpc-reply xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
308     if (!xml_child_nr_type(xe, CX_ELMNT))
309 	cprintf(cbret, "<ok/>");
310     else while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
311 	    if (xmlns_set(x, NULL, namespace) < 0)
312 		goto done;
313 	    if (clicon_xml2cbuf(cbret, x, 0, 0, -1) < 0)
314 		goto done;
315 	}
316     cprintf(cbret, "</rpc-reply>");
317     retval = 0;
318  done:
319     return retval;
320 }
321 
322 /*! This will be called as a hook right after the original system copy-config
323  */
324 static int
example_copy_extra(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)325 example_copy_extra(clicon_handle h,            /* Clicon handle */
326 		   cxobj        *xe,           /* Request: <rpc><xn></rpc> */
327 		   cbuf         *cbret,        /* Reply eg <rpc-reply>... */
328 		   void         *arg,          /* client_entry */
329 		   void         *regarg)       /* Argument given at register */
330 {
331     int    retval = -1;
332 
333     //    fprintf(stderr, "%s\n", __FUNCTION__);
334     retval = 0;
335     // done:
336     return retval;
337 }
338 
339 /*! Called to get state data from plugin
340  * @param[in]    h      Clicon handle
341  * @param[in]    nsc    External XML namespace context, or NULL
342  * @param[in]    xpath  String with XPATH syntax. or NULL for all
343  * @param[in]    xstate XML tree, <config/> on entry.
344  * @retval       0      OK
345  * @retval      -1      Error
346  * @see xmldb_get
347  * @note this example code returns requires this yang snippet:
348        container state {
349          config false;
350          description "state data for example application";
351          leaf-list op {
352             type string;
353          }
354        }
355  * This yang snippet is present in clixon-example.yang for example.
356  */
357 int
example_statedata(clicon_handle h,cvec * nsc,char * xpath,cxobj * xstate)358 example_statedata(clicon_handle h,
359 		  cvec         *nsc,
360 		  char         *xpath,
361 		  cxobj        *xstate)
362 {
363     int     retval = -1;
364     cxobj **xvec = NULL;
365     size_t  xlen = 0;
366     cbuf   *cb = cbuf_new();
367     int     i;
368     cxobj  *xt = NULL;
369     char   *name;
370     cvec   *nsc1 = NULL;
371     cvec   *nsc2 = NULL;
372     yang_stmt *yspec = NULL;
373     int     fd;
374 
375     if (!_state)
376 	goto ok;
377     yspec = clicon_dbspec_yang(h);
378 
379     /* If -S is set, then read state data from file, otherwise construct it programmatically */
380     if (_state_file){
381 	if (_state_file_init){
382 #if 0 /* This is just for a zero-copy version (only works once) */
383 	    {
384 		cxobj *xx = NULL;
385 		while (xml_child_nr(_state_xstate)){
386 		    xx = xml_child_i(_state_xstate,0);
387 		    if (xml_addsub(xstate, xx) < 0)
388 			goto done;
389 		}
390 	    }
391 #else
392 	    if (xml_copy(_state_xstate, xstate) < 0)
393 		goto done;
394 #endif
395 	}
396 	else{
397 	    cxobj *x1;
398 	    if ((fd = open(_state_file, O_RDONLY)) < 0){
399 		clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
400 		goto done;
401 	    }
402 	    if ((xt = xml_new("config", NULL, CX_ELMNT)) == NULL)
403 		goto done;
404 	    if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
405 		goto done;
406 	    close(fd);
407 	    if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
408 		goto done;
409 	    for (i=0; i<xlen; i++){
410 		x1 = xvec[i];
411 		xml_flag_set(x1, XML_FLAG_MARK);
412 	    }
413 	    /* Remove everything that is not marked */
414 	    if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
415 		goto done;
416 	    for (i=0; i<xlen; i++){
417 		x1 = xvec[i];
418 		xml_flag_reset(x1, XML_FLAG_MARK);
419 	    }
420 	    if (xml_copy(xt, xstate) < 0)
421 		goto done;
422 	    if (xvec){
423 		free(xvec);
424 		xvec = 0;
425 		xlen = 0;
426 	    }
427 	}
428     }
429     else {
430 	/* Example of statedata, in this case merging state data with
431 	 * state information. In this case adding dummy interface operation state
432 	 * to configured interfaces.
433 	 * Get config according to xpath */
434 	if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
435 	    goto done;
436 	if (xmldb_get0(h, "running", YB_MODULE, nsc1, "/interfaces/interface/name", 1, &xt, NULL) < 0)
437 	    goto done;
438 	if (xpath_vec(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0)
439 	    goto done;
440 	if (xlen){
441 	    cprintf(cb, "<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">");
442 	    for (i=0; i<xlen; i++){
443 		name = xml_body(xvec[i]);
444 		cprintf(cb, "<interface xmlns:ex=\"urn:example:clixon\"><name>%s</name><type>ex:eth</type><oper-status>up</oper-status>", name);
445 		cprintf(cb, "<ex:my-status><ex:int>42</ex:int><ex:str>foo</ex:str></ex:my-status>");
446 		cprintf(cb, "</interface>");
447 	    }
448 	    cprintf(cb, "</interfaces>");
449 	    if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, &xstate, NULL) < 0)
450 		goto done;
451 	}
452 	/* State in test_yang.sh , test_restconf.sh and test_order.sh */
453 	if (yang_find_module_by_namespace(yspec, "urn:example:clixon") != NULL){
454 	    if (clixon_xml_parse_string("<state xmlns=\"urn:example:clixon\">"
455 					"<op>42</op>"
456 					"<op>41</op>"
457 					"<op>43</op>" /* should not be ordered */
458 					"</state>",
459 					YB_NONE,
460 					NULL, &xstate, NULL) < 0)
461 		goto done; /* For the case when urn:example:clixon is not loaded */
462 	}
463 	/* Event state from RFC8040 Appendix B.3.1
464 	 * Note: (1) order is by-system so is different,
465 	 *       (2) event-count is XOR on name, so is not 42 and 4
466 	 */
467 	if (yang_find_module_by_namespace(yspec, "urn:example:events") != NULL){
468 	    cbuf_reset(cb);
469 	    cprintf(cb, "<events xmlns=\"urn:example:events\">");
470 	    cprintf(cb, "<event><name>interface-down</name><event-count>90</event-count></event>");
471 	    cprintf(cb, "<event><name>interface-up</name><event-count>77</event-count></event>");
472 	    cprintf(cb, "</events>");
473 	    if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, &xstate, NULL) < 0)
474 		goto done;
475 	}
476     }
477  ok:
478     retval = 0;
479  done:
480     if (nsc1)
481 	xml_nsctx_free(nsc1);
482     if (nsc2)
483 	xml_nsctx_free(nsc2);
484     if (xt)
485 	xml_free(xt);
486     if (cb)
487 	cbuf_free(cb);
488     if (xvec)
489 	free(xvec);
490     return retval;
491 }
492 
493 /*! Callback for yang extensions example:e4
494  *
495  * @param[in] h    Clixon handle
496  * @param[in] yext Yang node of extension
497  * @param[in] ys   Yang node of (unknown) statement belonging to extension
498  * @retval     0   OK, all callbacks executed OK
499  * @retval    -1   Error in one callback
500  */
501 int
example_extension(clicon_handle h,yang_stmt * yext,yang_stmt * ys)502 example_extension(clicon_handle h,
503 		  yang_stmt    *yext,
504 		  yang_stmt    *ys)
505 {
506     int        retval = -1;
507     char      *extname;
508     char      *modname;
509     yang_stmt *ymod;
510     yang_stmt *yc;
511     yang_stmt *yn = NULL;
512 
513     ymod = ys_module(yext);
514     modname = yang_argument_get(ymod);
515     extname = yang_argument_get(yext);
516     if (strcmp(modname, "example") != 0 || strcmp(extname, "e4") != 0)
517 	goto ok;
518     clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
519     if ((yc = yang_find(ys, 0, NULL)) == NULL)
520 	goto ok;
521     if ((yn = ys_dup(yc)) == NULL)
522 	goto done;
523     if (yn_insert(yang_parent_get(ys), yn) < 0)
524 	goto done;
525  ok:
526     retval = 0;
527  done:
528     return retval;
529 }
530 
531 /* Here follows code for general-purpose datastore upgrade
532  * Nodes affected are identified by paths.
533  * In this example nodes' namespaces are changed, or they are removed altogether
534  * @note Order is significant, the rules are traversed in the order stated here, which means that if
535  *       namespaces changed, or objects are removed in one rule, you have to take that into account
536  *       in the next rule.
537  */
538 /* Remove these paths */
539 static const char *remove_map[] = {
540     "/a:remove_me",
541     /* add more paths to be deleted here */
542     NULL
543 };
544 
545 /* Rename the namespaces of these paths.
546  * That is, paths (on the left) should get namespaces (to the right)
547  */
548 static const map_str2str namespace_map[] = {
549     {"/a:x/a:y/a:z/descendant-or-self::node()", "urn:example:b"},
550     /* add more paths to be renamed here */
551     {NULL,                          NULL}
552 };
553 
554 /*! General-purpose datastore upgrade callback called once on startup
555  *
556  * Gets called on startup after initial XML parsing, but before module-specific upgrades
557  * and before validation.
558  * @param[in] h    Clicon handle
559  * @param[in] db   Name of datastore, eg "running", "startup" or "tmp"
560  * @param[in] xt   XML tree. Upgrade this "in place"
561  * @param[in] msd  Info on datastore module-state, if any
562  * @retval   -1    Error
563  * @retval    0    OK
564  */
565 int
example_upgrade(clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)566 example_upgrade(clicon_handle    h,
567 		const char      *db,
568 		cxobj           *xt,
569 		modstate_diff_t *msd)
570 {
571     int                       retval = -1;
572     cvec                     *nsc = NULL;    /* Canonical namespace */
573     yang_stmt                *yspec;
574     const struct map_str2str *ms;            /* map iterator */
575     cxobj                   **xvec = NULL;   /* vector of result nodes */
576     size_t                    xlen;
577     int                       i;
578     const char              **pp;
579 
580     if (_general_upgrade == 0)
581 	goto ok;
582     if (strcmp(db, "startup") != 0) /* skip other than startup datastore */
583 	goto ok;
584     if (msd && msd->md_status) /* skip if there is proper module-state in datastore */
585 	goto ok;
586     yspec = clicon_dbspec_yang(h);     /* Get all yangs */
587     /* Get canonical namespaces for using "normalized" prefixes */
588     if (xml_nsctx_yangspec(yspec, &nsc) < 0)
589 	goto done;
590     /* 1. Remove paths */
591     for (pp = remove_map; *pp; ++pp){
592 	/* Find all nodes matching n */
593 	if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, *pp) < 0)
594 	    goto done;
595 	/* Remove them */
596 	/* Loop through all nodes matching mypath and change theoir namespace */
597 	for (i=0; i<xlen; i++){
598 	    if (xml_purge(xvec[i]) < 0)
599 		goto done;
600 	}
601 	if (xvec){
602 	    free(xvec);
603 	    xvec = NULL;
604 	}
605     }
606     /* 2. Rename namespaces of the paths declared in the namespace map
607      */
608     for (ms = &namespace_map[0]; ms->ms_s0; ms++){
609 	char *mypath;
610 	char *mynamespace;
611 	char *myprefix = NULL;
612 
613 	mypath = ms->ms_s0;
614 	mynamespace = ms->ms_s1;
615 	if (xml_nsctx_get_prefix(nsc, mynamespace, &myprefix) == 0){
616 	    clicon_err(OE_XML, ENOENT, "Namespace %s not found in canonical namespace map",
617 		       mynamespace);
618 	    goto done;
619 	}
620 	/* Find all nodes matching mypath */
621 	if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, mypath) < 0)
622 	    goto done;
623 	/* Loop through all nodes matching mypath and change theoir namespace */
624 	for (i=0; i<xlen; i++){
625 	    /* Change namespace of this node (using myprefix) */
626 	    if (xml_namespace_change(xvec[i], mynamespace, myprefix) < 0)
627 		goto done;
628 	}
629 	if (xvec){
630 	    free(xvec);
631 	    xvec = NULL;
632 	}
633     }
634  ok:
635     retval = 0;
636  done:
637     if (xvec)
638 	free(xvec);
639     if (nsc)
640 	cvec_free(nsc);
641     return retval;
642 }
643 
644 /*! Testcase module-specific upgrade function moving interfaces-state to interfaces
645  * @param[in]  h       Clicon handle
646  * @param[in]  xn      XML tree to be updated
647  * @param[in]  ns      Namespace of module (for info)
648  * @param[in]  op      One of XML_FLAG_ADD, _DEL, _CHANGE
649  * @param[in]  from    From revision on the form YYYYMMDD
650  * @param[in]  to      To revision on the form YYYYMMDD (0 not in system)
651  * @param[in]  arg     User argument given at rpc_callback_register()
652  * @param[out] cbret   Return xml tree, eg <rpc-reply>..., <rpc-error..  if retval = 0
653  * @retval     1       OK
654  * @retval     0       Invalid
655  * @retval    -1       Error
656  * @see clicon_upgrade_cb
657  * @see test_upgrade_interfaces.sh
658  * @see upgrade_2014_to_2016
659  * This example shows a two-step upgrade where the 2014 function does:
660  * - Move /if:interfaces-state/if:interface/if:admin-status to
661  *        /if:interfaces/if:interface/
662  * - Move /if:interfaces-state/if:interface/if:statistics to
663  *        /if:interfaces/if:interface/
664  * - Rename /interfaces/interface/description to descr
665  */
666 static int
upgrade_2014_to_2016(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)667 upgrade_2014_to_2016(clicon_handle h,
668 		     cxobj        *xt,
669 		     char         *ns,
670 		     uint16_t      op,
671 		     uint32_t      from,
672 		     uint32_t      to,
673 		     void         *arg,
674 		     cbuf         *cbret)
675 {
676     int        retval = -1;
677     yang_stmt *yspec;
678     yang_stmt *ym;
679     cxobj    **vec = NULL;
680     cxobj     *xc;
681     cxobj     *xi;  /* xml /interfaces-states/interface node */
682     cxobj     *x;
683     cxobj     *xif; /* xml /interfaces/interface node */
684     size_t     vlen;
685     int        i;
686     char      *name;
687 
688     clicon_debug(1, "%s from:%d to:%d", __FUNCTION__, from, to);
689     if (op != XML_FLAG_CHANGE) /* Only treat fully present modules */
690     	goto ok;
691     /* Get Yang module for this namespace. Note it may not exist (if obsolete) */
692     yspec = clicon_dbspec_yang(h);
693     if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
694 	goto ok; /* shouldnt happen */
695     /* Get all XML nodes with that namespace */
696     if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
697 	goto done;
698     for (i=0; i<vlen; i++){
699 	xc = vec[i];
700 	/* Iterate through interfaces-state */
701 	if (strcmp(xml_name(xc),"interfaces-state") == 0){
702 	    /* Note you cannot delete or move xml objects directly under xc
703 	     * in the loop (eg xi objects) but you CAN move children of xi
704 	     */
705 	    xi = NULL;
706 	    while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
707 		if (strcmp(xml_name(xi), "interface"))
708 		    continue;
709 		if ((name = xml_find_body(xi, "name")) == NULL)
710 		    continue; /* shouldnt happen */
711 		/* Get corresponding /interfaces/interface entry */
712 		xif = xpath_first(xt, NULL, "/interfaces/interface[name=\"%s\"]", name);
713 		/* - Move /if:interfaces-state/if:interface/if:admin-status to
714 		 *        /if:interfaces/if:interface/ */
715 		if ((x = xml_find(xi, "admin-status")) != NULL && xif){
716 		    if (xml_addsub(xif, x) < 0)
717 			goto done;
718 		}
719 		/* - Move /if:interfaces-state/if:interface/if:statistics to
720 		 *        /if:interfaces/if:interface/*/
721 		if ((x = xml_find(xi, "statistics")) != NULL){
722 		    if (xml_addsub(xif, x) < 0)
723 			goto done;
724 		}
725 	    }
726 	}
727 	else if (strcmp(xml_name(xc),"interfaces") == 0){
728 	    /* Iterate through interfaces */
729 	    xi = NULL;
730 	    while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
731 		if (strcmp(xml_name(xi), "interface"))
732 		    continue;
733 		/* Rename /interfaces/interface/description to descr */
734 		if ((x = xml_find(xi, "description")) != NULL)
735 		    if (xml_name_set(x, "descr") < 0)
736 			goto done;
737 	    }
738 	}
739     }
740  ok:
741     retval = 1;
742  done:
743     if (vec)
744 	free(vec);
745     return retval;
746 }
747 
748 /*! Testcase upgrade function removing interfaces-state
749  * @param[in]  h       Clicon handle
750  * @param[in]  xn      XML tree to be updated
751  * @param[in]  ns      Namespace of module (for info)
752  * @param[in]  op      One of XML_FLAG_ADD, _DEL, _CHANGE
753  * @param[in]  from    From revision on the form YYYYMMDD
754  * @param[in]  to      To revision on the form YYYYMMDD (0 not in system)
755  * @param[in]  arg     User argument given at rpc_callback_register()
756  * @param[out] cbret   Return xml tree, eg <rpc-reply>..., <rpc-error..  if retval = 0
757  * @retval     1       OK
758  * @retval     0       Invalid
759  * @retval    -1       Error
760  * @see clicon_upgrade_cb
761  * @see test_upgrade_interfaces.sh
762  * @see upgrade_2016_to_2018
763  * The 2016 function does:
764  * - Delete /if:interfaces-state
765  * - Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr
766  * - Change type /interfaces/interface/statistics/in-octets to decimal64 with
767  *   fraction-digits 3 and divide all values with 1000
768  */
769 static int
upgrade_2016_to_2018(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)770 upgrade_2016_to_2018(clicon_handle h,
771 		     cxobj        *xt,
772 		     char         *ns,
773 		     uint16_t      op,
774 		     uint32_t      from,
775 		     uint32_t      to,
776 		     void         *arg,
777 		     cbuf         *cbret)
778 {
779     int        retval = -1;
780     yang_stmt *yspec;
781     yang_stmt *ym;
782     cxobj    **vec = NULL;
783     cxobj     *xc;
784     cxobj     *xi;
785     cxobj     *x;
786     cxobj     *xb;
787     size_t     vlen;
788     int        i;
789 
790     clicon_debug(1, "%s from:%d to:%d", __FUNCTION__, from, to);
791     if (op != XML_FLAG_CHANGE) /* Only treat fully present modules */
792     	goto ok;
793     /* Get Yang module for this namespace. Note it may not exist (if obsolete) */
794     yspec = clicon_dbspec_yang(h);
795     if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
796 	goto ok; /* shouldnt happen */
797     clicon_debug(1, "%s module %s", __FUNCTION__, ym?yang_argument_get(ym):"none");
798     /* Get all XML nodes with that namespace */
799     if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
800 	goto done;
801     for (i=0; i<vlen; i++){
802 	xc = vec[i];
803 	/* Delete /if:interfaces-state */
804 	if (strcmp(xml_name(xc), "interfaces-state") == 0)
805 	    xml_purge(xc);
806 	/* Iterate through interfaces */
807 	else if (strcmp(xml_name(xc),"interfaces") == 0){
808 	    /* Iterate through interfaces */
809 	    xi = NULL;
810 	    while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
811 		if (strcmp(xml_name(xi), "interface"))
812 		    continue;
813 		/* Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr */
814 		if ((x = xml_find(xi, "descr")) != NULL)
815 		    if (xml_wrap(x, "docs") < 0)
816 			goto done;
817 		/* Change type /interfaces/interface/statistics/in-octets to
818 		 * decimal64 with fraction-digits 3 and divide values with 1000
819 		 */
820 		if ((x = xpath_first(xi, NULL, "statistics/in-octets")) != NULL){
821 		    if ((xb = xml_body_get(x)) != NULL){
822 			uint64_t u64;
823 			cbuf *cb = cbuf_new();
824 			parse_uint64(xml_value(xb), &u64, NULL);
825 			cprintf(cb, "%" PRIu64 ".%03d", u64/1000, (int)(u64%1000));
826 			xml_value_set(xb, cbuf_get(cb));
827 			cbuf_free(cb);
828 		    }
829 		}
830 	    }
831 	}
832     }
833  ok:
834     retval = 1;
835  done:
836     if (vec)
837 	free(vec);
838     return retval;
839 }
840 
841 /*! Testcase module-specific upgrade function moving interfaces-state to interfaces
842  * @param[in]  h       Clicon handle
843  * @param[in]  xn      XML tree to be updated
844  * @param[in]  ns      Namespace of module (for info)
845  * @param[in]  op      One of XML_FLAG_ADD, _DEL, _CHANGE
846  * @param[in]  from    From revision on the form YYYYMMDD
847  * @param[in]  to      To revision on the form YYYYMMDD (0 not in system)
848  * @param[in]  arg     User argument given at rpc_callback_register()
849  * @param[out] cbret   Return xml tree, eg <rpc-reply>..., <rpc-error..  if retval = 0
850  * @retval     1       OK
851  * @retval     0       Invalid
852  * @retval    -1       Error
853  * @see clicon_upgrade_cb
854  * @see test_upgrade_interfaces.sh
855  * @see upgrade_2014_to_2016
856  * This example shows a two-step upgrade where the 2014 function does:
857  * - Move /if:interfaces-state/if:interface/if:admin-status to
858  *        /if:interfaces/if:interface/
859  * - Move /if:interfaces-state/if:interface/if:statistics to
860  *        /if:interfaces/if:interface/
861  * - Rename /interfaces/interface/description to descr
862  */
863 static int
upgrade_interfaces(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)864 upgrade_interfaces(clicon_handle h,
865 		   cxobj        *xt,
866 		   char         *ns,
867 		   uint16_t      op,
868 		   uint32_t      from,
869 		   uint32_t      to,
870 		   void         *arg,
871 		   cbuf         *cbret)
872 {
873     int retval = -1;
874 
875     if (_module_upgrade) /* For testing */
876 	clicon_log(LOG_NOTICE, "%s %s op:%s from:%d to:%d",
877 		   __FUNCTION__, ns,
878 		   (op&XML_FLAG_ADD)?"ADD":(op&XML_FLAG_DEL)?"DEL":"CHANGE",
879 		   from, to);
880     if (from <= 20140508){
881 	if ((retval = upgrade_2014_to_2016(h, xt, ns, op, from, to, arg, cbret)) < 0)
882 	    goto done;
883 	if (retval == 0)
884 	    goto done;
885     }
886     if (from <= 20160101){
887     	if ((retval = upgrade_2016_to_2018(h, xt, ns, op, from, to, arg, cbret)) < 0)
888 	    goto done;
889 	if (retval == 0)
890 	    goto done;
891     }
892     // ok:
893     retval = 1;
894  done:
895     return retval;
896 }
897 
898 /*! Plugin state reset. Add xml or set state in backend machine.
899  * Called in each backend plugin. plugin_reset is called after all plugins
900  * have been initialized. This give the application a chance to reset
901  * system state back to a base state.
902  * This is generally done when a system boots up to
903  * make sure the initial system state is well defined. This can be creating
904  * default configuration files for various daemons, set interface flags etc.
905  * @param[in] h   Clicon handle
906  * @param[in] db  Name of database. Not may be other than "running"
907  * In this example, a loopback interface is added
908  * @note This assumes example yang with interfaces/interface
909  */
910 int
example_reset(clicon_handle h,const char * db)911 example_reset(clicon_handle h,
912 	      const char   *db)
913 {
914     int    retval = -1;
915     cxobj *xt = NULL;
916     int    ret;
917     cbuf  *cbret = NULL;
918 
919     if (!_reset)
920 	goto ok; /* Note not enabled by default */
921     if (clixon_xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">"
922 				"<interface><name>lo</name><type>ex:loopback</type>"
923 				"</interface></interfaces></config>", YB_NONE, NULL, &xt, NULL) < 0)
924 	goto done;
925     /* Replace parent w first child */
926     if (xml_rootchild(xt, 0, &xt) < 0)
927 	goto done;
928     if ((cbret = cbuf_new()) == NULL){
929 	clicon_err(OE_UNIX, errno, "cbuf_new");
930 	goto done;
931     }
932     /* Merge user reset state */
933     if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret)) < 0)
934 	goto done;
935     if (ret == 0){
936 	clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
937 		   cbuf_get(cbret));
938 	goto done;
939     }
940  ok:
941     retval = 0;
942  done:
943     if (cbret)
944 	cbuf_free(cbret);
945     if (xt != NULL)
946 	xml_free(xt);
947     return retval;
948 }
949 
950 /*! Plugin start.
951  * @param[in]  h     Clicon handle
952  *
953  * plugin_start is called once everything has been initialized, right before
954  * the main event loop is entered.
955  */
956 int
example_start(clicon_handle h)957 example_start(clicon_handle h)
958 {
959     return 0;
960 }
961 
962 /*! Plugin daemon.
963  * @param[in]  h     Clicon handle
964  *
965  * plugin_daemon is called once after damonization has been made but before lowering of privileges
966  * the main event loop is entered.
967  */
968 int
example_daemon(clicon_handle h)969 example_daemon(clicon_handle h)
970 {
971     int retval = -1;
972     int ret;
973 
974     /* Read state file (or should this be in init/start?) */
975     if (_state && _state_file && _state_file_init){
976 	int fd;
977 	yang_stmt *yspec = clicon_dbspec_yang(h);
978 
979 	if ((fd = open(_state_file, O_RDONLY)) < 0){
980 	    clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
981 	    goto done;
982 	}
983 	if ((ret = clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &_state_xstate, NULL)) < 0)
984 	    goto done;
985 	close(fd);
986 	if (ret == 0){
987 	    fprintf(stderr, "%s error\n", __FUNCTION__);
988 	    goto done;
989 	}
990 	fprintf(stderr, "%s done\n", __FUNCTION__);
991     }
992     retval = 0;
993  done:
994     return retval;
995 }
996 
997 int
example_exit(clicon_handle h)998 example_exit(clicon_handle h)
999 {
1000     return 0;
1001 }
1002 
1003 clixon_plugin_api *clixon_plugin_init(clicon_handle h);
1004 
1005 static clixon_plugin_api api = {
1006     "example",                              /* name */
1007     clixon_plugin_init,                     /* init - must be called clixon_plugin_init */
1008     example_start,                          /* start */
1009     example_exit,                           /* exit */
1010     .ca_extension=example_extension,        /* yang extensions */
1011     .ca_daemon=example_daemon,              /* daemon */
1012     .ca_reset=example_reset,                /* reset */
1013     .ca_statedata=example_statedata,        /* statedata */
1014     .ca_trans_begin=main_begin,             /* trans begin */
1015     .ca_trans_validate=main_validate,       /* trans validate */
1016     .ca_trans_complete=main_complete,       /* trans complete */
1017     .ca_trans_commit=main_commit,           /* trans commit */
1018     .ca_trans_commit_done=main_commit_done, /* trans commit done */
1019     .ca_trans_revert=main_revert,           /* trans revert */
1020     .ca_trans_end=main_end,                 /* trans end */
1021     .ca_trans_abort=main_abort,             /* trans abort */
1022     .ca_datastore_upgrade=example_upgrade,  /* general-purpose upgrade. */
1023 };
1024 
1025 /*! Backend plugin initialization
1026  * @param[in]  h    Clixon handle
1027  * @retval     NULL Error with clicon_err set
1028  * @retval     api  Pointer to API struct
1029  * In this example, you can pass -r, -s, -u to control the behaviour, mainly
1030  * for use in the test suites.
1031  */
1032 clixon_plugin_api *
clixon_plugin_init(clicon_handle h)1033 clixon_plugin_init(clicon_handle h)
1034 {
1035     struct timeval retention = {0,0};
1036     int            argc; /* command-line options (after --) */
1037     char         **argv;
1038     int            c;
1039 
1040     clicon_debug(1, "%s backend", __FUNCTION__);
1041 
1042     /* Get user command-line options (after --) */
1043     if (clicon_argv_get(h, &argc, &argv) < 0)
1044 	goto done;
1045     opterr = 0;
1046     optind = 1;
1047     while ((c = getopt(argc, argv, BACKEND_EXAMPLE_OPTS)) != -1)
1048 	switch (c) {
1049 	case 'r':
1050 	    _reset = 1;
1051 	    break;
1052 	case 's': /* state callback */
1053 	    _state = 1;
1054 	    break;
1055 	case 'S': /* state file (requires -s) */
1056 	    _state_file = optarg;
1057 	    break;
1058 	case 'i': /* read state file on init not by request (requires -sS <file> */
1059 	    _state_file_init = 1;
1060 	    break;
1061        case 'u': /* module-specific upgrade */
1062            _module_upgrade = 1;
1063            break;
1064        case 'U': /* general-purpose upgrade */
1065            _general_upgrade = 1;
1066 	   break;
1067 	case 't': /* transaction log */
1068 	    _transaction_log = 1;
1069 	    break;
1070 	case 'v': /* validate fail */
1071 	    _validate_fail_xpath = optarg;
1072 	    break;
1073 	}
1074 
1075     /* Example stream initialization:
1076      * 1) Register EXAMPLE stream
1077      * 2) setup timer for notifications, so something happens on stream
1078      * 3) setup stream callbacks for notification to push channel
1079      */
1080     if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
1081 	retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
1082     if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
1083 	goto done;
1084     /* Enable nchan pub/sub streams
1085      * assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
1086      */
1087     if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
1088 	stream_publish(h, "EXAMPLE") < 0)
1089 	goto done;
1090     if (example_stream_timer_setup(h) < 0)
1091 	goto done;
1092 
1093     /* Register callback for routing rpc calls
1094      */
1095     /* From example.yang (clicon) */
1096     if (rpc_callback_register(h, empty_rpc,
1097 			      NULL,
1098 			      "urn:example:clixon",
1099 			      "empty"/* Xml tag when callback is made */
1100 			      ) < 0)
1101 	goto done;
1102     /* Same as example but with optional input/output */
1103     if (rpc_callback_register(h, example_rpc,
1104 			      NULL,
1105 			      "urn:example:clixon",
1106 			      "optional"/* Xml tag when callback is made */
1107 			      ) < 0)
1108 	goto done;
1109         /* Same as example but with optional input/output */
1110     if (rpc_callback_register(h, example_rpc,
1111 			      NULL,
1112 			      "urn:example:clixon",
1113 			      "example"/* Xml tag when callback is made */
1114 			      ) < 0)
1115 	goto done;
1116     /* Called after the regular system copy_config callback */
1117     if (rpc_callback_register(h, example_copy_extra,
1118 			      NULL,
1119 			      NETCONF_BASE_NAMESPACE,
1120 			      "copy-config"
1121 			      ) < 0)
1122 	goto done;
1123     /* Upgrade callback: if you start the backend with -- -u you will get the
1124      * test interface example. Otherwise the auto-upgrade feature is enabled.
1125      */
1126     if (_module_upgrade){
1127 	if (upgrade_callback_register(h, upgrade_interfaces, "urn:example:interfaces", NULL) < 0)
1128 	    goto done;
1129     }
1130     else
1131 	if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, NULL) < 0)
1132 	    goto done;
1133 
1134     /* Return plugin API */
1135     return &api;
1136  done:
1137     return NULL;
1138 }
1139