* Fixed: [very slow execution of load_set_file #288](https://github.com/clicon/clixon/issues/288) * New `clixon-lib@2021-11-11.yang` revision * Modified option: RPC stats extended with YANG stats * Modified `clixon-config@2021-11-11.yang` revision * Added option: * CLICON_PLUGIN_CALLBACK_CHECK * Enable to make plugin context check before and after all callbacks. * Added statistics for YANG: number of objects and memory used * Use the treeref no-copy option of CLIgen to reduce memory * Refactored cli-generation/autocli-start code * Refactored cligen glue functions to use cligen_eval directly (remove clicon_eval,clixon_cligen_eval)
1049 lines
31 KiB
C
1049 lines
31 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
|
|
Copyright (C) 2017-2019 Olof Hagsand
|
|
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
|
|
|
This file is part of CLIXON.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 3 or later (the "GPL"),
|
|
in which case the provisions of the GPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of the GPL, and not to allow others to
|
|
use your version of this file under the terms of Apache License version 2,
|
|
indicate your decision by deleting the provisions above and replace them with
|
|
the notice and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the Apache License version 2 or the GPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <dlfcn.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/param.h>
|
|
#include <netinet/in.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clicon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "clixon_backend_transaction.h"
|
|
#include "clixon_backend_plugin.h"
|
|
#include "clixon_backend_commit.h"
|
|
|
|
/*! Request plugins to reset system state
|
|
* The system 'state' should be the same as the contents of running_db
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clicon handle
|
|
* @param[in] db Name of datastore
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_plugin_reset_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
char *db)
|
|
{
|
|
int retval = -1;
|
|
plgreset_t *fn; /* callback */
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_reset) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, db) < 0) {
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (clicon_errno < 0)
|
|
clicon_log(LOG_WARNING, "%s: Internal error: Reset callback in plugin: %s returned -1 but did not make a clicon_err call",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call all plugins reset callbacks
|
|
* The system 'state' should be the same as the contents of running_db
|
|
* @param[in] h Clixon handle
|
|
* @param[in] db Name of datastore
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_plugin_reset_all(clicon_handle h,
|
|
char *db)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
/* Loop through all plugins, call callbacks in each */
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (clixon_plugin_reset_one(cp, h, db) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin "pre-" daemonize callback
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
clixon_plugin_pre_daemon_one(clixon_plugin_t *cp,
|
|
clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
plgdaemon_t *fn; /* Daemonize plugin callback function */
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_pre_daemon) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h) < 0) {
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (clicon_errno < 0)
|
|
clicon_log(LOG_WARNING, "%s: Internal error: Pre-daemon callback in plugin:\
|
|
%s returned -1 but did not make a clicon_err call",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call all plugins "pre-" daemonize callbacks
|
|
*
|
|
* This point in time is after "start" and before
|
|
* before daemonization/fork,
|
|
* It is not called if backend is started in daemon mode.
|
|
* @param[in] h Clicon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_plugin_pre_daemon_all(clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
/* Loop through all plugins, call callbacks in each */
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (clixon_plugin_pre_daemon_one(cp, h) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin "post-" daemonize callback
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
clixon_plugin_daemon_one(clixon_plugin_t *cp,
|
|
clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
plgdaemon_t *fn; /* Daemonize plugin callback function */
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_daemon) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h) < 0) {
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (clicon_errno < 0)
|
|
clicon_log(LOG_WARNING, "%s: Internal error: Daemon callback in plugin: %s returned -1 but did not make a clicon_err call",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call all plugins "post-" daemonize callbacks
|
|
*
|
|
* This point in time is after "start" and after "pre-daemon" and
|
|
* after daemonization/fork, ie when
|
|
* daemon is in the background but before dropped privileges.
|
|
* In case of foreground mode (-F) it is still called but no fork has occured.
|
|
* @param[in] h Clicon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note Also called for non-background mode
|
|
*/
|
|
int
|
|
clixon_plugin_daemon_all(clicon_handle h)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
/* Loop through all plugins, call callbacks in each */
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (clixon_plugin_daemon_one(cp, h) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single backend statedata callback
|
|
*
|
|
* Create an xml state tree (xret) for one callback only on the form:
|
|
* <config>...</config>,
|
|
* call a user supplied function (ca_statedata) which can do two things:
|
|
* - Fill in state XML in the tree and return 0
|
|
* - Call cli_error() and return -1
|
|
* In the former case, this function returns the state XML tree to the caller (which
|
|
* typically merges the tree with other state trees).
|
|
* In the latter error case, this function returns 0 (invalid) to the caller with no tree
|
|
* If a fatal error occurs in this function, -1 is returned.
|
|
*
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h clicon handle
|
|
* @param[in] nsc namespace context for xpath
|
|
* @param[in] xpath String with XPATH syntax. or NULL for all
|
|
* @param[in] pagmode List pagination mode
|
|
* @param[in] offset Offset, for list pagination
|
|
* @param[in] limit Limit, for list pagination
|
|
* @param[out] remaining Remaining elements (if limit is non-zero)
|
|
* @param[out] xp If retval=1, state tree created and returned: <config>...
|
|
* @retval -1 Fatal error
|
|
* @retval 0 Statedata callback failed. no XML tree returned
|
|
* @retval 1 OK if callback found (and called) xret is set
|
|
*/
|
|
static int
|
|
clixon_plugin_statedata_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
cvec *nsc,
|
|
char *xpath,
|
|
cxobj **xp)
|
|
{
|
|
int retval = -1;
|
|
plgstatedata_t *fn; /* Plugin statedata fn */
|
|
cxobj *x = NULL;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_statedata) != NULL){
|
|
if ((x = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, nsc, xpath, x) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (clicon_errno < 0)
|
|
clicon_log(LOG_WARNING, "%s: Internal error: State callback in plugin: %s returned -1 but did not make a clicon_err call",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto fail; /* Dont quit here on user callbacks */
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
if (xp && x)
|
|
*xp = x;
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Go through all backend statedata callbacks and collect state data
|
|
* This is internal system call, plugin is invoked (does not call) this function
|
|
* Backend plugins can register
|
|
* @param[in] h clicon handle
|
|
* @param[in] yspec Yang spec
|
|
* @param[in] nsc Namespace context
|
|
* @param[in] xpath String with XPATH syntax. or NULL for all
|
|
* @param[in] pagmode List pagination mode
|
|
* @param[in] offset Offset, for list pagination
|
|
* @param[in] limit Limit, for list pagination
|
|
* @param[out] remaining Remaining elements (if limit is non-zero)
|
|
* @param[in,out] xret State XML tree is merged with existing tree.
|
|
* @retval -1 Error
|
|
* @retval 0 Statedata callback failed (xret set with netconf-error)
|
|
* @retval 1 OK
|
|
* @note xret can be replaced in this function
|
|
*/
|
|
int
|
|
clixon_plugin_statedata_all(clicon_handle h,
|
|
yang_stmt *yspec,
|
|
cvec *nsc,
|
|
char *xpath,
|
|
cxobj **xret)
|
|
{
|
|
int retval = -1;
|
|
int ret;
|
|
cxobj *x = NULL;
|
|
clixon_plugin_t *cp = NULL;
|
|
cbuf *cberr = NULL;
|
|
cxobj *xerr = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if ((ret = clixon_plugin_statedata_one(cp, h, nsc, xpath, &x)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* error reason should be in clicon_err_reason */
|
|
cprintf(cberr, "Internal error, state callback in plugin %s returned invalid XML: %s",
|
|
clixon_plugin_name_get(cp), clicon_err_reason);
|
|
if (netconf_operation_failed_xml(&xerr, "application", cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
xml_free(*xret);
|
|
*xret = xerr;
|
|
xerr = NULL;
|
|
goto fail;
|
|
}
|
|
if (x == NULL)
|
|
continue;
|
|
if (xml_child_nr(x) == 0){
|
|
xml_free(x);
|
|
x = NULL;
|
|
continue;
|
|
}
|
|
if (clicon_debug_get())
|
|
clicon_log_xml(LOG_DEBUG, x, "%s %s STATE:", __FUNCTION__, clixon_plugin_name_get(cp));
|
|
/* XXX: ret == 0 invalid yang binding should be handled as internal error */
|
|
if ((ret = xml_bind_yang(x, YB_MODULE, yspec, &xerr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if (clixon_netconf_internal_error(xerr,
|
|
". Internal error, state callback returned invalid XML from plugin: ",
|
|
clixon_plugin_name_get(cp)) < 0)
|
|
goto done;
|
|
xml_free(*xret);
|
|
*xret = xerr;
|
|
xerr = NULL;
|
|
goto fail;
|
|
}
|
|
if (xml_sort_recurse(x) < 0)
|
|
goto done;
|
|
/* Mark non-presence containers */
|
|
if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_TRANSIENT) < 0)
|
|
goto done;
|
|
/* Clear XML tree of defaults */
|
|
if (xml_tree_prune_flagged(x, XML_FLAG_TRANSIENT, 1) < 0)
|
|
goto done;
|
|
/* clear mark and change */
|
|
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
|
(void*)(0xffff));
|
|
if (xml_default_recurse(x, 1) < 0)
|
|
goto done;
|
|
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto fail;
|
|
if (x){
|
|
xml_free(x);
|
|
x = NULL;
|
|
}
|
|
} /* while plugin */
|
|
retval = 1;
|
|
done:
|
|
if (xerr)
|
|
xml_free(xerr);
|
|
if (cberr)
|
|
cbuf_free(cberr);
|
|
if (x)
|
|
xml_free(x);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Lock database status has changed status
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] db Database name (eg "running")
|
|
* @param[in] lock Lock status: 0: unlocked, 1: locked
|
|
* @param[in] id Session id (of locker/unlocker)
|
|
* @retval -1 Fatal error
|
|
* @retval 0 OK
|
|
*/
|
|
static int
|
|
clixon_plugin_lockdb_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
char *db,
|
|
int lock,
|
|
int id)
|
|
{
|
|
int retval = -1;
|
|
plglockdb_t *fn; /* Plugin statedata fn */
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_lockdb) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, db, lock, id) < 0)
|
|
goto done;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Lock database status has changed status
|
|
* @param[in] h Clixon handle
|
|
* @param[in] db Database name (eg "running")
|
|
* @param[in] lock Lock status: 0: unlocked, 1: locked
|
|
* @param[in] id Session id (of locker/unlocker)
|
|
* @retval -1 Fatal error
|
|
* @retval 0 OK
|
|
*/
|
|
int
|
|
clixon_plugin_lockdb_all(clicon_handle h,
|
|
char *db,
|
|
int lock,
|
|
int id
|
|
)
|
|
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (clixon_plugin_lockdb_one(cp, h, db, lock, id) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Traverse state data callbacks
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xpath Registered XPath using canonical prefixes
|
|
*/
|
|
int
|
|
clixon_pagination_cb_call(clicon_handle h,
|
|
char *xpath,
|
|
int locked,
|
|
uint32_t offset,
|
|
uint32_t limit,
|
|
cxobj *xstate)
|
|
{
|
|
int retval = -1;
|
|
pagination_data_t pd;
|
|
dispatcher_entry_t *htable = NULL;
|
|
|
|
pd.pd_offset = offset;
|
|
pd.pd_limit = limit;
|
|
pd.pd_locked = locked;
|
|
pd.pd_xstate = xstate;
|
|
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
|
|
if (htable && dispatcher_call_handlers(htable, h, xpath, &pd) < 0)
|
|
goto done;
|
|
retval = 1;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Register a state data callback
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] fn Callback
|
|
* @param[in] xpath Registered XPath using canonical prefixes
|
|
* @param[in] arg Domain-specific argument to send to callback
|
|
*/
|
|
int
|
|
clixon_pagination_cb_register(clicon_handle h,
|
|
handler_function fn,
|
|
char *xpath,
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
dispatcher_definition x = {xpath, fn, arg};
|
|
dispatcher_entry_t *htable = NULL;
|
|
|
|
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
|
|
if (dispatcher_register_handler(&htable, &x) < 0){
|
|
clicon_err(OE_PLUGIN, errno, "dispatcher");
|
|
goto done;
|
|
}
|
|
if (clicon_ptr_set(h, "pagination-entries", htable) < 0)
|
|
goto done;
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Free pagination callback structure
|
|
*
|
|
* @param[in] h Clixon handle
|
|
*/
|
|
int
|
|
clixon_pagination_free(clicon_handle h)
|
|
{
|
|
dispatcher_entry_t *htable = NULL;
|
|
|
|
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
|
|
if (htable)
|
|
dispatcher_free(htable);
|
|
return 0;
|
|
}
|
|
|
|
/*! Create and initialize a validate/commit transaction
|
|
* @retval td New alloced transaction,
|
|
* @retval NULL Error
|
|
* @see transaction_free which deallocates the returned handle
|
|
*/
|
|
transaction_data_t *
|
|
transaction_new(void)
|
|
{
|
|
transaction_data_t *td;
|
|
static uint64_t id = 0; /* Global transaction id */
|
|
|
|
if ((td = malloc(sizeof(*td))) == NULL){
|
|
clicon_err(OE_CFG, errno, "malloc");
|
|
return NULL;
|
|
}
|
|
memset(td, 0, sizeof(*td));
|
|
td->td_id = id++;
|
|
return td;
|
|
}
|
|
|
|
/*! Free transaction structure
|
|
*
|
|
* @param[in] td Transaction data will be deallocated after the call
|
|
*/
|
|
int
|
|
transaction_free(transaction_data_t *td)
|
|
{
|
|
if (td->td_src)
|
|
xml_free(td->td_src);
|
|
if (td->td_target)
|
|
xml_free(td->td_target);
|
|
if (td->td_dvec)
|
|
free(td->td_dvec);
|
|
if (td->td_avec)
|
|
free(td->td_avec);
|
|
if (td->td_scvec)
|
|
free(td->td_scvec);
|
|
if (td->td_tcvec)
|
|
free(td->td_tcvec);
|
|
free(td);
|
|
return 0;
|
|
}
|
|
|
|
/*! Call single plugin transaction_begin() before a validate/commit.
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_begin_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_begin) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/* The plugin_transaction routines need access to struct plugin which is local to this file */
|
|
|
|
/*! Call transaction_begin() in all plugins before a validate/commit.
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error: one of the plugin callbacks returned error
|
|
*/
|
|
int
|
|
plugin_transaction_begin_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_begin_one(cp, h, td) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin transaction_validate() in a validate/commit transaction
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_validate_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_validate) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_validate callbacks in all backend plugins
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK. Validation succeeded in all plugins
|
|
* @retval -1 Error: one of the plugin callbacks returned validation fail
|
|
*/
|
|
int
|
|
plugin_transaction_validate_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_validate_one(cp, h, td) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin transaction_complete() in a validate/commit transaction
|
|
* complete is called after validate (before commit)
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_complete_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_complete) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_complete() in all plugins after validation (before commit)
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error: one of the plugin callbacks returned error
|
|
* @note Call plugins which have commit dependencies?
|
|
* @note Rename to transaction_complete?
|
|
*/
|
|
int
|
|
plugin_transaction_complete_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_complete_one(cp, h, td) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Revert a commit
|
|
* @param[in] h CLICON handle
|
|
* @param[in] td Transaction data
|
|
* @param[in] nr The plugin where an error occured.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* The revert is made in plugin before this one. Eg if error occurred in
|
|
* plugin 2, then the revert will be made in plugins 1 and 0.
|
|
*/
|
|
static int
|
|
plugin_transaction_revert_all(clicon_handle h,
|
|
transaction_data_t *td,
|
|
int nr)
|
|
{
|
|
int retval = 0;
|
|
clixon_plugin_t *cp = NULL;
|
|
trans_cb_t *fn;
|
|
|
|
while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_revert) == NULL)
|
|
continue;
|
|
if ((retval = fn(h, (transaction_data)td)) < 0){
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_revert callback failed",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
break;
|
|
}
|
|
}
|
|
return retval; /* ignore errors */
|
|
}
|
|
|
|
|
|
/*! Call single plugin transaction_commit() in a commit transaction
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_commit_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_commit) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_commit callbacks in all backend plugins
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error: one of the plugin callbacks returned error
|
|
* If any of the commit callbacks fail by returning -1, a revert of the
|
|
* transaction is tried by calling the commit callbacsk with reverse arguments
|
|
* and in reverse order.
|
|
*/
|
|
int
|
|
plugin_transaction_commit_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
int i=0;
|
|
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
i++;
|
|
if (plugin_transaction_commit_one(cp, h, td) < 0){
|
|
/* Make an effort to revert transaction */
|
|
plugin_transaction_revert_all(h, td, i-1);
|
|
goto done;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin transaction_commit_done() in a commit transaction
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_commit_done_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_commit_done) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_commit_done callbacks in all backend plugins
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error: one of the plugin callbacks returned error
|
|
* @note no revert is done
|
|
*/
|
|
int
|
|
plugin_transaction_commit_done_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_commit_done_one(cp, h, td) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call single plugin transaction_end() in a commit/validate transaction
|
|
* @param[in] cp Plugin handle
|
|
* @param[in] h Clixon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_end_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_end) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_end() in all plugins after a successful commit.
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_end_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_end_one(cp, h, td) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
plugin_transaction_abort_one(clixon_plugin_t *cp,
|
|
clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
trans_cb_t *fn;
|
|
void *wh = NULL;
|
|
|
|
if ((fn = clixon_plugin_api_get(cp)->ca_trans_abort) != NULL){
|
|
wh = NULL;
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (fn(h, (transaction_data)td) < 0){
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
|
|
clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error",
|
|
__FUNCTION__, clixon_plugin_name_get(cp));
|
|
goto done;
|
|
}
|
|
if (plugin_context_check(h, &wh, clixon_plugin_name_get(cp), __FUNCTION__) < 0)
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Call transaction_abort() in all plugins after a failed validation/commit.
|
|
* @param[in] h Clicon handle
|
|
* @param[in] td Transaction data
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
plugin_transaction_abort_all(clicon_handle h,
|
|
transaction_data_t *td)
|
|
{
|
|
int retval = -1;
|
|
clixon_plugin_t *cp = NULL;
|
|
|
|
clicon_debug(1, "%s", __FUNCTION__);
|
|
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
|
|
if (plugin_transaction_abort_one(cp, h, td) < 0)
|
|
; /* dont abort on error */
|
|
}
|
|
retval = 0;
|
|
return retval;
|
|
}
|