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  *
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 <string.h>
45 #include <stdlib.h>
46 #include <stdarg.h>
47 #include <dlfcn.h>
48 #include <dirent.h>
49 #include <unistd.h>
50 #include <errno.h>
51 #include <signal.h>
52 #include <syslog.h>
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <sys/param.h>
56 #include <netinet/in.h>
57 
58 /* cligen */
59 #include <cligen/cligen.h>
60 
61 /* clicon */
62 #include <clixon/clixon.h>
63 
64 #include "clixon_backend_transaction.h"
65 #include "backend_plugin.h"
66 #include "backend_commit.h"
67 
68 /*! Request plugins to reset system state
69  * The system 'state' should be the same as the contents of running_db
70  * @param[in]  cp      Plugin handle
71  * @param[in]  h       Clicon handle
72  * @param[in]  db      Name of datastore
73  * @retval     0       OK
74  * @retval    -1       Error
75  */
76 int
clixon_plugin_reset_one(clixon_plugin * cp,clicon_handle h,char * db)77 clixon_plugin_reset_one(clixon_plugin *cp,
78 			clicon_handle  h,
79 			char         *db)
80 {
81     int          retval = -1;
82     plgreset_t  *fn;       /* callback */
83 
84     if ((fn = cp->cp_api.ca_reset) != NULL){
85 	if (fn(h, db) < 0) {
86 	    if (clicon_errno < 0)
87 		clicon_log(LOG_WARNING, "%s: Internal error: Reset callback in plugin: %s returned -1 but did not make a clicon_err call",
88 			   __FUNCTION__, cp->cp_name);
89 	    goto done;
90 	}
91     }
92     retval = 0;
93  done:
94     return retval;
95 }
96 
97 /*! Call all plugins reset callbacks
98  * The system 'state' should be the same as the contents of running_db
99  * @param[in]  h       Clixon handle
100  * @param[in]  db      Name of datastore
101  * @retval     0       OK
102  * @retval    -1       Error
103  */
104 int
clixon_plugin_reset_all(clicon_handle h,char * db)105 clixon_plugin_reset_all(clicon_handle h,
106 			char         *db)
107 {
108     int             retval = -1;
109     clixon_plugin  *cp = NULL;
110 
111     /* Loop through all plugins, call callbacks in each */
112     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
113 	if (clixon_plugin_reset_one(cp, h, db) < 0)
114 	    goto done;
115     }
116     retval = 0;
117  done:
118     return retval;
119 }
120 
121 /*! Call single plugin "post-" daemonize callback
122  * @param[in]  cp      Plugin handle
123  * @param[in]  h       Clixon handle
124  * @retval     0       OK
125  * @retval    -1       Error
126  */
127 int
clixon_plugin_daemon_one(clixon_plugin * cp,clicon_handle h)128 clixon_plugin_daemon_one(clixon_plugin *cp,
129 			 clicon_handle  h)
130 {
131     int          retval = -1;
132     plgdaemon_t *fn;          /* Daemonize plugin callback function */
133 
134     if ((fn = cp->cp_api.ca_daemon) != NULL){
135 	if (fn(h) < 0) {
136 	    if (clicon_errno < 0)
137 		clicon_log(LOG_WARNING, "%s: Internal error: Daemon callback in plugin: %s returned -1 but did not make a clicon_err call",
138 			   __FUNCTION__, cp->cp_name);
139 	    goto done;
140 	}
141     }
142     retval = 0;
143  done:
144     return retval;
145 }
146 
147 /*! Call all plugins "post-" daemonize callbacks
148  * @param[in]  h       Clicon handle
149  * @retval     0       OK
150  * @retval    -1       Error
151  */
152 int
clixon_plugin_daemon_all(clicon_handle h)153 clixon_plugin_daemon_all(clicon_handle h)
154 {
155     int            retval = -1;
156     clixon_plugin *cp = NULL;
157 
158     /* Loop through all plugins, call callbacks in each */
159     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
160 	if (clixon_plugin_daemon_one(cp, h) < 0)
161 	    goto done;
162     }
163     retval = 0;
164  done:
165     return retval;
166 }
167 
168 /*! Call single backend statedata callback
169  *
170  * Create an xml state tree (xret) for one callback only on the form:
171  *   <config>...</config>,
172  * call a user supplied function (ca_statedata) which can do two things:
173  *  - Fill in state XML in the tree and return 0
174  *  - Call cli_error() and return -1
175  * In the former case, this function returns the state XML tree to the caller (which
176  * typically merges the tree with other state trees).
177  * In the latter error case, this function returns 0 (invalid) to the caller with no tree
178  * If a fatal error occurs in this function, -1 is returned.
179  *
180  * @param[in]  cp      Plugin handle
181  * @param[in]  h       clicon handle
182  * @param[in]  xpath   String with XPATH syntax. or NULL for all
183  * @param[out] xret    If retval=1, state tree created and returned: <config>...
184  * @retval    -1       Fatal error
185  * @retval     0       Statedata callback failed. no XML tree returned
186  * @retval     1       OK if callback found (and called) xret is set
187  */
188 static int
clixon_plugin_statedata_one(clixon_plugin * cp,clicon_handle h,cvec * nsc,char * xpath,cxobj ** xp)189 clixon_plugin_statedata_one(clixon_plugin  *cp,
190 			    clicon_handle   h,
191 			    cvec           *nsc,
192 			    char           *xpath,
193 			    cxobj         **xp)
194 {
195     int             retval = -1;
196     plgstatedata_t *fn;          /* Plugin statedata fn */
197     cxobj          *x = NULL;
198 
199     if ((fn = cp->cp_api.ca_statedata) != NULL){
200 	if ((x = xml_new("config", NULL, CX_ELMNT)) == NULL)
201 	    goto done;
202 	if (fn(h, nsc, xpath, x) < 0){
203 	    if (clicon_errno < 0)
204 		clicon_log(LOG_WARNING, "%s: Internal error: State callback in plugin: %s returned -1 but did not make a clicon_err call",
205 			   __FUNCTION__, cp->cp_name);
206 	    goto fail;  /* Dont quit here on user callbacks */
207 	}
208 
209     }
210     if (xp && x)
211 	*xp = x;
212     retval = 1;
213  done:
214     return retval;
215  fail:
216     retval = 0;
217     goto done;
218 }
219 
220 /*! Go through all backend statedata callbacks and collect state data
221  * This is internal system call, plugin is invoked (does not call) this function
222  * Backend plugins can register
223  * @param[in]     h       clicon handle
224  * @param[in]     yspec   Yang spec
225  * @param[in]     nsc     Namespace context
226  * @param[in]     xpath   String with XPATH syntax. or NULL for all
227  * @param[in,out] xret    State XML tree is merged with existing tree.
228  * @retval       -1       Error
229  * @retval        0       Statedata callback failed (xret set with netconf-error)
230  * @retval        1       OK
231  * @note xret can be replaced in this function
232  */
233 int
clixon_plugin_statedata_all(clicon_handle h,yang_stmt * yspec,cvec * nsc,char * xpath,cxobj ** xret)234 clixon_plugin_statedata_all(clicon_handle    h,
235 			    yang_stmt       *yspec,
236 			    cvec            *nsc,
237 			    char            *xpath,
238 			    cxobj          **xret)
239 {
240     int             retval = -1;
241     int             ret;
242     cxobj          *x = NULL;
243     clixon_plugin  *cp = NULL;
244     cbuf           *cberr = NULL;
245     cxobj          *xerr = NULL;
246 
247     clicon_debug(1, "%s", __FUNCTION__);
248     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
249 	if ((ret = clixon_plugin_statedata_one(cp, h, nsc, xpath, &x)) < 0)
250 	    goto done;
251 	if (ret == 0){
252 	    if ((cberr = cbuf_new()) == NULL){
253 		clicon_err(OE_UNIX, errno, "cbuf_new");
254 		goto done;
255 	    }
256 	    /* error reason should be in clicon_err_reason */
257 	    cprintf(cberr, "Internal error, state callback in plugin %s returned invalid XML: %s",
258 		    cp->cp_name, clicon_err_reason);
259 	    if (netconf_operation_failed_xml(&xerr, "application", cbuf_get(cberr)) < 0)
260 		goto done;
261 	    xml_free(*xret);
262 	    *xret = xerr;
263 	    xerr = NULL;
264 	    goto fail;
265 	}
266 	if (x == NULL)
267 	    continue;
268 	if (xml_child_nr(x) == 0){
269 	    xml_free(x);
270 	    x = NULL;
271 	    continue;
272 	}
273 #if 1
274 	if (clicon_debug_get())
275 	    clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__);
276 #endif
277 	/* XXX: ret == 0 invalid yang binding should be handled as internal error */
278 	if ((ret = xml_bind_yang(x, YB_MODULE, yspec, &xerr)) < 0)
279 	    goto done;
280 	if (ret == 0){
281 	    if (clixon_netconf_internal_error(xerr,
282 					      ". Internal error, state callback returned invalid XML from plugin: ",
283 					      cp->cp_name) < 0)
284 		goto done;
285 	    xml_free(*xret);
286 	    *xret = xerr;
287 	    xerr = NULL;
288 	    goto fail;
289 	}
290 	if (xml_sort_recurse(x) < 0)
291 	    goto done;
292 	/* Mark non-presence containers as XML_FLAG_DEFAULT */
293 	if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
294 	    goto done;
295 	/* Clear XML tree of defaults */
296 	if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
297 	    goto done;
298         /* clear mark and change */
299 	xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
300 		   (void*)(0xffff));
301 	if (xml_default_recurse(x, 1) < 0)
302 	    goto done;
303 	if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
304 	    goto done;
305 	if (ret == 0)
306 	    goto fail;
307 	if (x){
308 	    xml_free(x);
309 	    x = NULL;
310 	}
311     } /* while plugin */
312     retval = 1;
313  done:
314     if (xerr)
315 	xml_free(xerr);
316     if (cberr)
317 	cbuf_free(cberr);
318     if (x)
319 	xml_free(x);
320     return retval;
321  fail:
322     retval = 0;
323     goto done;
324 }
325 
326 /*! Create and initialize a validate/commit transaction
327  * @retval  td     New alloced transaction,
328  * @retval  NULL   Error
329  * @see transaction_free  which deallocates the returned handle
330  */
331 transaction_data_t *
transaction_new(void)332 transaction_new(void)
333 {
334     transaction_data_t *td;
335     static uint64_t     id = 0; /* Global transaction id */
336 
337     if ((td = malloc(sizeof(*td))) == NULL){
338 	clicon_err(OE_CFG, errno, "malloc");
339 	return NULL;
340     }
341     memset(td, 0, sizeof(*td));
342     td->td_id = id++;
343     return td;
344 }
345 
346 /*! Free transaction structure
347  *
348  * @param[in]  td      Transaction data will be deallocated after the call
349  */
350 int
transaction_free(transaction_data_t * td)351 transaction_free(transaction_data_t *td)
352 {
353     if (td->td_src)
354 	xml_free(td->td_src);
355     if (td->td_target)
356 	xml_free(td->td_target);
357     if (td->td_dvec)
358 	free(td->td_dvec);
359     if (td->td_avec)
360 	free(td->td_avec);
361     if (td->td_scvec)
362 	free(td->td_scvec);
363     if (td->td_tcvec)
364 	free(td->td_tcvec);
365     free(td);
366     return 0;
367 }
368 
369 /*! Call single plugin transaction_begin() before a validate/commit.
370  * @param[in]  cp      Plugin handle
371  * @param[in]  h       Clixon handle
372  * @param[in]  td      Transaction data
373  * @retval     0       OK
374  * @retval    -1       Error
375  */
376 int
plugin_transaction_begin_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)377 plugin_transaction_begin_one(clixon_plugin      *cp,
378 			     clicon_handle       h,
379 			     transaction_data_t *td)
380 {
381     int         retval = -1;
382     trans_cb_t *fn;
383 
384     if ((fn = cp->cp_api.ca_trans_begin) != NULL){
385 	if (fn(h, (transaction_data)td) < 0){
386 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
387 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
388 		       __FUNCTION__, cp->cp_name);
389 	    goto done;
390 	}
391     }
392     retval = 0;
393  done:
394     return retval;
395 }
396 
397 /* The plugin_transaction routines need access to struct plugin which is local to this file */
398 
399 /*! Call transaction_begin() in all plugins before a validate/commit.
400  * @param[in]  h       Clicon handle
401  * @param[in]  td      Transaction data
402  * @retval     0       OK
403  * @retval    -1       Error: one of the plugin callbacks returned error
404  */
405 int
plugin_transaction_begin_all(clicon_handle h,transaction_data_t * td)406 plugin_transaction_begin_all(clicon_handle       h,
407 			     transaction_data_t *td)
408 {
409     int            retval = -1;
410     clixon_plugin *cp = NULL;
411 
412     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
413 	if (plugin_transaction_begin_one(cp, h, td) < 0)
414 	    goto done;
415     }
416     retval = 0;
417  done:
418     return retval;
419 }
420 
421 /*! Call single plugin transaction_validate() in a validate/commit transaction
422  * @param[in]  cp      Plugin handle
423  * @param[in]  h       Clixon handle
424  * @param[in]  td      Transaction data
425  * @retval     0       OK
426  * @retval    -1       Error
427  */
428 int
plugin_transaction_validate_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)429 plugin_transaction_validate_one(clixon_plugin      *cp,
430 				clicon_handle       h,
431 				transaction_data_t *td)
432 {
433     int         retval = -1;
434     trans_cb_t *fn;
435 
436     if ((fn = cp->cp_api.ca_trans_validate) != NULL){
437 	if (fn(h, (transaction_data)td) < 0){
438 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
439 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
440 		       __FUNCTION__, cp->cp_name);
441 	    goto done;
442 	}
443     }
444     retval = 0;
445  done:
446     return retval;
447 }
448 
449 /*! Call transaction_validate callbacks in all backend plugins
450  * @param[in]  h       Clicon handle
451  * @param[in]  td      Transaction data
452  * @retval     0       OK. Validation succeeded in all plugins
453  * @retval    -1       Error: one of the plugin callbacks returned validation fail
454  */
455 int
plugin_transaction_validate_all(clicon_handle h,transaction_data_t * td)456 plugin_transaction_validate_all(clicon_handle       h,
457 				transaction_data_t *td)
458 {
459     int            retval = -1;
460     clixon_plugin *cp = NULL;
461 
462     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
463 	if (plugin_transaction_validate_one(cp, h, td) < 0)
464 	    goto done;
465     }
466     retval = 0;
467  done:
468     return retval;
469 }
470 
471 /*! Call single plugin transaction_complete() in a validate/commit transaction
472  * complete is called after validate (before commit)
473  * @param[in]  cp      Plugin handle
474  * @param[in]  h       Clixon handle
475  * @param[in]  td      Transaction data
476  * @retval     0       OK
477  * @retval    -1       Error
478  */
479 int
plugin_transaction_complete_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)480 plugin_transaction_complete_one(clixon_plugin      *cp,
481 				clicon_handle       h,
482 				transaction_data_t *td)
483 {
484     int         retval = -1;
485     trans_cb_t *fn;
486 
487     if ((fn = cp->cp_api.ca_trans_complete) != NULL){
488 	if (fn(h, (transaction_data)td) < 0){
489 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
490 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
491 		       __FUNCTION__, cp->cp_name);
492 	    goto done;
493 	}
494     }
495     retval = 0;
496  done:
497     return retval;
498 }
499 
500 /*! Call transaction_complete() in all plugins after validation (before commit)
501  * @param[in]  h       Clicon handle
502  * @param[in]  td      Transaction data
503  * @retval     0       OK
504  * @retval    -1       Error: one of the plugin callbacks returned error
505  * @note Call plugins which have commit dependencies?
506  * @note Rename to transaction_complete?
507  */
508 int
plugin_transaction_complete_all(clicon_handle h,transaction_data_t * td)509 plugin_transaction_complete_all(clicon_handle       h,
510 				transaction_data_t *td)
511 {
512     int            retval = -1;
513     clixon_plugin *cp = NULL;
514 
515     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
516 	if (plugin_transaction_complete_one(cp, h, td) < 0)
517 	    goto done;
518     }
519     retval = 0;
520  done:
521     return retval;
522 }
523 
524 /*! Revert a commit
525  * @param[in]  h   CLICON handle
526  * @param[in]  td  Transaction data
527  * @param[in]  nr  The plugin where an error occured.
528  * @retval     0       OK
529  * @retval    -1       Error
530  * The revert is made in plugin before this one. Eg if error occurred in
531  * plugin 2, then the revert will be made in plugins 1 and 0.
532  */
533 static int
plugin_transaction_revert_all(clicon_handle h,transaction_data_t * td,int nr)534 plugin_transaction_revert_all(clicon_handle       h,
535 			      transaction_data_t *td,
536 			      int                 nr)
537 {
538     int                retval = 0;
539     clixon_plugin     *cp = NULL;
540     trans_cb_t        *fn;
541 
542     while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
543 	if ((fn = cp->cp_api.ca_trans_revert) == NULL)
544 	    continue;
545 	if ((retval = fn(h, (transaction_data)td)) < 0){
546 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_revert callback failed",
547 			   __FUNCTION__, cp->cp_name);
548 		break;
549 	}
550     }
551     return retval; /* ignore errors */
552 }
553 
554 
555 /*! Call single plugin transaction_commit() in a commit transaction
556  * @param[in]  cp      Plugin handle
557  * @param[in]  h       Clixon handle
558  * @param[in]  td      Transaction data
559  * @retval     0       OK
560  * @retval    -1       Error
561  */
562 int
plugin_transaction_commit_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)563 plugin_transaction_commit_one(clixon_plugin      *cp,
564 			      clicon_handle       h,
565 			      transaction_data_t *td)
566 {
567     int         retval = -1;
568     trans_cb_t *fn;
569 
570     if ((fn = cp->cp_api.ca_trans_commit) != NULL){
571 	if (fn(h, (transaction_data)td) < 0){
572 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
573 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
574 		       __FUNCTION__, cp->cp_name);
575 	    goto done;
576 	}
577     }
578     retval = 0;
579  done:
580     return retval;
581 }
582 
583 /*! Call transaction_commit callbacks in all backend plugins
584  * @param[in]  h       Clicon handle
585  * @param[in]  td      Transaction data
586  * @retval     0       OK
587  * @retval    -1       Error: one of the plugin callbacks returned error
588  * If any of the commit callbacks fail by returning -1, a revert of the
589  * transaction is tried by calling the commit callbacsk with reverse arguments
590  * and in reverse order.
591  */
592 int
plugin_transaction_commit_all(clicon_handle h,transaction_data_t * td)593 plugin_transaction_commit_all(clicon_handle       h,
594 			      transaction_data_t *td)
595 {
596     int            retval = -1;
597     clixon_plugin *cp = NULL;
598     int            i=0;
599 
600     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
601 	i++;
602 	if (plugin_transaction_commit_one(cp, h, td) < 0){
603 	    /* Make an effort to revert transaction */
604 	    plugin_transaction_revert_all(h, td, i-1);
605 	    goto done;
606 	}
607     }
608     retval = 0;
609  done:
610     return retval;
611 }
612 
613 
614 /*! Call single plugin transaction_commit_done() in a commit transaction
615  * @param[in]  cp      Plugin handle
616  * @param[in]  h       Clixon handle
617  * @param[in]  td      Transaction data
618  * @retval     0       OK
619  * @retval    -1       Error
620  */
621 int
plugin_transaction_commit_done_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)622 plugin_transaction_commit_done_one(clixon_plugin      *cp,
623 				   clicon_handle       h,
624 				   transaction_data_t *td)
625 {
626     int         retval = -1;
627     trans_cb_t *fn;
628 
629     if ((fn = cp->cp_api.ca_trans_commit_done) != NULL){
630 	if (fn(h, (transaction_data)td) < 0){
631 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
632 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
633 		       __FUNCTION__, cp->cp_name);
634 	    goto done;
635 	}
636     }
637     retval = 0;
638  done:
639     return retval;
640 }
641 
642 /*! Call transaction_commit_done callbacks in all backend plugins
643  * @param[in]  h       Clicon handle
644  * @param[in]  td      Transaction data
645  * @retval     0       OK
646  * @retval    -1       Error: one of the plugin callbacks returned error
647  * @note no revert is done
648  */
649 int
plugin_transaction_commit_done_all(clicon_handle h,transaction_data_t * td)650 plugin_transaction_commit_done_all(clicon_handle       h,
651 				   transaction_data_t *td)
652 {
653     int            retval = -1;
654     clixon_plugin *cp = NULL;
655 
656     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
657 	if (plugin_transaction_commit_done_one(cp, h, td) < 0)
658 	    goto done;
659     }
660     retval = 0;
661  done:
662     return retval;
663 }
664 
665 /*! Call single plugin transaction_end() in a commit/validate transaction
666  * @param[in]  cp      Plugin handle
667  * @param[in]  h       Clixon handle
668  * @param[in]  td      Transaction data
669  * @retval     0       OK
670  * @retval    -1       Error
671  */
672 int
plugin_transaction_end_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)673 plugin_transaction_end_one(clixon_plugin      *cp,
674 			   clicon_handle       h,
675 			   transaction_data_t *td)
676 {
677     int         retval = -1;
678     trans_cb_t *fn;
679 
680     if ((fn = cp->cp_api.ca_trans_end) != NULL){
681 	if (fn(h, (transaction_data)td) < 0){
682 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
683 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
684 		       __FUNCTION__, cp->cp_name);
685 	    goto done;
686 	}
687     }
688     retval = 0;
689  done:
690     return retval;
691 }
692 
693 /*! Call transaction_end() in all plugins after a successful commit.
694  * @param[in]  h       Clicon handle
695  * @param[in]  td      Transaction data
696  * @retval     0       OK
697  * @retval    -1       Error
698  */
699 int
plugin_transaction_end_all(clicon_handle h,transaction_data_t * td)700 plugin_transaction_end_all(clicon_handle h,
701 			   transaction_data_t *td)
702 {
703     int            retval = -1;
704     clixon_plugin *cp = NULL;
705 
706     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
707 	if (plugin_transaction_end_one(cp, h, td) < 0)
708 	    goto done;
709     }
710     retval = 0;
711  done:
712     return retval;
713 }
714 
715 int
plugin_transaction_abort_one(clixon_plugin * cp,clicon_handle h,transaction_data_t * td)716 plugin_transaction_abort_one(clixon_plugin      *cp,
717 			     clicon_handle       h,
718 			     transaction_data_t *td)
719 {
720     int         retval = -1;
721     trans_cb_t *fn;
722 
723     if ((fn = cp->cp_api.ca_trans_abort) != NULL){
724 	if (fn(h, (transaction_data)td) < 0){
725 	    if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
726 		clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
727 		       __FUNCTION__, cp->cp_name);
728 	    goto done;
729 	}
730     }
731     retval = 0;
732  done:
733     return retval;
734 }
735 
736 /*! Call transaction_abort() in all plugins after a failed validation/commit.
737  * @param[in]  h       Clicon handle
738  * @param[in]  td      Transaction data
739  * @retval     0       OK
740  * @retval    -1       Error
741  */
742 int
plugin_transaction_abort_all(clicon_handle h,transaction_data_t * td)743 plugin_transaction_abort_all(clicon_handle       h,
744 			     transaction_data_t *td)
745 {
746     int            retval = -1;
747     clixon_plugin *cp = NULL;
748 
749     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
750 	if (plugin_transaction_abort_one(cp, h, td) < 0)
751 	    ; /* dont abort on error */
752     }
753     retval = 0;
754     return retval;
755 }
756 
757