/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren 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 #define __USE_GNU /* strverscmp */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include #include "clixon_backend_transaction.h" #include "backend_plugin.h" #include "backend_commit.h" /*! Load a plugin group. * @param[in] h Clicon handle * @retval 0 OK * @retval -1 Error */ int backend_plugin_initiate(clicon_handle h) { char *dir; /* Load application plugins */ if ((dir = clicon_backend_dir(h)) == NULL) return 0; return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, clicon_option_str(h, "CLICON_BACKEND_REGEXP")); } /*! Request plugins to reset system state * The system 'state' should be the same as the contents of running_db * @param[in] h Clicon handle * @param[in] db Name of database * @retval 0 OK * @retval -1 Error */ int clixon_plugin_reset(clicon_handle h, char *db) { clixon_plugin *cp = NULL; plgreset_t *resetfn; /* Plugin auth */ int retval = 1; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((resetfn = cp->cp_api.ca_reset) == NULL) continue; if ((retval = resetfn(h, db)) < 0) { clicon_debug(1, "plugin_start() failed"); return -1; } break; } return retval; } /*! 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] xpath String with XPATH syntax. or NULL for all * @param[in,out] xtop State XML tree is merged with existing tree. * @retval -1 Error * @retval 0 OK * @retval 1 Statedata callback failed (xret set with netconf-error) * @note xtop can be replaced */ int clixon_plugin_statedata(clicon_handle h, yang_spec *yspec, char *xpath, cxobj **xret) { int retval = -1; int ret; cxobj *x = NULL; clixon_plugin *cp = NULL; plgstatedata_t *fn; /* Plugin statedata fn */ while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_statedata) == NULL) continue; if ((x = xml_new("config", NULL, NULL)) == NULL) goto done; if (fn(h, xpath, x) < 0){ retval = 1; goto done; /* Dont quit here on user callbacks */ } if ((ret = netconf_trymerge(x, yspec, xret)) != 0){ retval = ret; goto done; } if (x){ xml_free(x); x = NULL; } } retval = 0; done: if (x) xml_free(x); return retval; } /*! Call configuration upgrade routines in backend plugins * @param[in] h Clicon handle * @param[in] xms XML tree of module state differences * @retval 0 OK * @retval -1 Error in one (first) of user callbacks */ int clixon_plugin_upgrade(clicon_handle h, cxobj *xmodst) { int retval = -1; clixon_plugin *cp = NULL; upgrade_cb_t *fn; /* Plugin configuration upgrade fn */ while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_upgrade) == NULL) continue; if (fn(h, xmodst) < 0) goto done; } retval = 0; done: return retval; } /*! Create and initialize transaction */ 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 */ 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; } /* 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_begin) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_begin callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); break; } } 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_validate) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_validate callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); break; } } 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_complete) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_complete callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); break; } } 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. */ int plugin_transaction_revert(clicon_handle h, transaction_data_t *td, int nr) { int retval = 0; transaction_data_t tr; /* revert transaction */ clixon_plugin *cp = NULL; trans_cb_t *fn; /* Create a new reversed transaction from the original where src and target are swapped */ memcpy(&tr, td, sizeof(tr)); tr.td_src = td->td_target; tr.td_target = td->td_src; tr.td_dlen = td->td_alen; tr.td_dvec = td->td_avec; tr.td_alen = td->td_dlen; tr.td_avec = td->td_dvec; tr.td_clen = td->td_clen; tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; if ((retval = fn(h, (transaction_data)&tr)) < 0){ clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed", __FUNCTION__, cp->cp_name); break; } } return retval; /* ignore errors */ } /*! 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; int i=0; while ((cp = clixon_plugin_each(h, cp)) != NULL) { i++; if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); /* Make an effort to revert transaction */ plugin_transaction_revert(h, td, i-1); break; } } 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_end) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_end callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); break; } } 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(clicon_handle h, transaction_data_t *td) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_abort) == NULL) continue; fn(h, (transaction_data)td); /* dont abort on error */ } return retval; }