From b9a54f07f39eff1d0d91e5d7bd749c898cc0d5f7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 19:27:57 +0200 Subject: [PATCH] Restructure and more generic plugin API for backend --- CHANGELOG.md | 57 ++- README.md | 2 +- apps/backend/Makefile.in | 4 +- apps/backend/backend_client.c | 2 +- apps/backend/backend_main.c | 8 +- apps/backend/backend_plugin.c | 666 ++++++++-------------------- apps/backend/backend_plugin.h | 6 +- apps/netconf/netconf_main.c | 4 +- apps/restconf/restconf_main.c | 4 +- example/Makefile.in | 17 +- example/README.md | 1 + example/example_backend.c | 85 ++-- example/example_backend_secondary.c | 102 +++++ example/example_netconf.c | 18 +- example/example_restconf.c | 17 +- lib/clixon/clixon_plugin.h | 40 +- lib/src/clixon_file.c | 11 +- lib/src/clixon_plugin.c | 153 +++++-- lib/src/clixon_xml.c | 2 +- 19 files changed, 570 insertions(+), 629 deletions(-) create mode 100644 example/example_backend_secondary.c diff --git a/CHANGELOG.md b/CHANGELOG.md index dc4dbe17..b9ab88eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,28 +6,55 @@ ### Major changes: * Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. + * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. * Moved specific plugin functions from apps/ to generic functions in lib/ * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() - * Authorization - * Example extended with authorization - * Test added with http basic authorization (test/test_auth.sh) - * Documentation in FAQ.md * Removed some obscure plugin code that seem not to be used (please report if needed!) * Client-local netconf plugins netconf_plugin_callbacks() * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() - * Example of new plugin module: + * backend system plugins (CLIXON_BACKEND_SYSDIR) + * CLICON_MASTER_PLUGIN config variable + * Example of migrating a backend plugin module: + * Add all callbacks in a clixon_plugin_api struct + * Rename plugin_init() -> clixon_plugin_init() and return api as function value ``` - static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - ... - } - clixon_plugin_api *clixon_plugin_init(clicon_handle h){ - return (void*)&api; - } +/* This is old style with hardcoded function names (eg plugin_start) */ +int plugin_start(clicon_handle h, int argc, char **argv) +{ + return 0; +} +int +plugin_init(clicon_handle h) +{ + return 0; +} + +/* This is new style with all function names in api struct */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, + plugin_start, + plugin_exit, + NULL, /* auth N/A for backend */ + plugin_reset, + plugin_statedata, + transaction_begin, + transaction_validate, + transaction_complete, + transaction_commit, + transaction_end, + transaction_abort +}; + +clixon_plugin_api *clixon_plugin_init(clicon_handle h) +{ + return &api; /* Return NULL on error */ +} ``` * Added Clixon Restconf library @@ -42,8 +69,12 @@ ### Minor changes: +* Authentication + * Example extended with http basic authentication for restconf + * Documentation in FAQ.md * Updated ietf-netconf-acm to ietf-netconf-acm@2018-02-14.yang from RFC 8341 * The Clixon example has changed name from "routing" to "example" affecting all config files, plugins, tests, etc. + * Secondary backend plugin added * Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. diff --git a/README.md b/README.md index 6f4e3ee3..aff68c49 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Extending ========= Clixon provides a core system and can be used as-is using available Yang specifications. However, an application very quickly needs to -specialize funxtions. Clixon is extended by (most commonly) writing +specialize functions. Clixon is extended by (most commonly) writing plugins for cli and backend. Extensions for netconf and restconf are also available. diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 662b884b..585bc059 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -55,8 +55,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ # Use this clixon lib for linking CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) -# Location of system plugins -CLIXON_BACKEND_SYSDIR = $(libdir)/clixon/plugins/backend # For dependency. A little strange that we rely on it being built in the src dir # even though it may exist in $(libdir). But the new version may not have been installed yet. @@ -119,7 +117,7 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_BACKEND_SYSDIR=\"$(CLIXON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $< + $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index a6ea354f..cf0bd2a5 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -289,7 +289,7 @@ from_client_get(clicon_handle h, /* Get state data from plugins as defined by plugin_statedata(), if any */ assert(xret); clicon_err_reset(); - if ((ret = backend_statedata_call(h, selector, xret)) < 0) + if ((ret = clixon_plugin_statedata(h, selector, xret)) < 0) goto done; if (ret == 0){ /* OK */ cprintf(cbret, ""); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index c8eb25eb..45c097c9 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -86,7 +86,7 @@ backend_terminate(clicon_handle h) clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); - plugin_finish(h); + clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ backend_rpc_cb_delete_all(); if (pidfile) @@ -254,7 +254,7 @@ plugin_start_useroptions(clicon_handle h, tmp = *(argv-1); *(argv-1) = argv0; - if (plugin_start_argv(h, argc+1, argv-1) < 0) + if (clixon_plugin_start(h, argc+1, argv-1) < 0) return -1; *(argv-1) = tmp; return 0; @@ -370,7 +370,7 @@ startup_mode_running(clicon_handle h, if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) @@ -443,7 +443,7 @@ startup_mode_startup(clicon_handle h, if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index ffe76941..00025617 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -64,58 +64,6 @@ #include "backend_plugin.h" #include "backend_commit.h" -/* - * Types - */ -/* Following are specific to backend. For common see clicon_plugin.h - * @note the following should match the prototypes in clixon_backend.h - */ -#define PLUGIN_RESET "plugin_reset" - - -/*! Plugin callback, if defined called to get state data from plugin - * @param[in] h Clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in] xtop XML tree, on entry. - * @retval 0 OK - * @retval -1 Error - * @see xmldb_get - */ -#define PLUGIN_STATEDATA "plugin_statedata" - - -#define PLUGIN_TRANS_BEGIN "transaction_begin" -#define PLUGIN_TRANS_VALIDATE "transaction_validate" -#define PLUGIN_TRANS_COMPLETE "transaction_complete" -#define PLUGIN_TRANS_COMMIT "transaction_commit" -#define PLUGIN_TRANS_END "transaction_end" -#define PLUGIN_TRANS_ABORT "transaction_abort" - - -/* Backend (config) plugins */ -struct plugin { - char p_name[PATH_MAX]; /* Plugin name */ - void *p_handle; /* Dynamic object handle */ - plginit_t *p_init; /* Init */ - plgstart_t *p_start; /* Start */ - plgexit_t *p_exit; /* Exit */ - plgreset_t *p_reset; /* Reset state */ - plgstatedata_t *p_statedata; /* State-data callback */ - trans_cb_t *p_trans_begin; /* Transaction start */ - trans_cb_t *p_trans_validate; /* Transaction validation */ - trans_cb_t *p_trans_complete; /* Transaction validation complete */ - trans_cb_t *p_trans_commit; /* Transaction commit */ - trans_cb_t *p_trans_end; /* Transaction completed */ - trans_cb_t *p_trans_abort; /* Transaction aborted */ - -}; - -/* - * Local variables - */ -static int _nplugins = 0; -static struct plugin *_plugins = NULL; - /*! Initialize plugin code (not the plugins themselves) * @param[in] h Clicon handle * @retval 0 OK @@ -127,263 +75,6 @@ backend_plugin_init(clicon_handle h) return 0; } -/*! Unload a plugin - * @param[in] h Clicon handle - * @param[in] plg Plugin structure - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_unload(clicon_handle h, - struct plugin *plg) -{ - int retval=-1; - char *error; - - /* Call exit function is it exists */ - if (plg->p_exit) - plg->p_exit(h); - - dlerror(); /* Clear any existing error */ - if (dlclose(plg->p_handle) != 0) { - error = (char*)dlerror(); - clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error"); - goto done; - /* Just report */ - } - else - clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name); - retval = 0; - done: - return retval; -} - - -/*! Load a dynamic plugin and call its init-function - * @param[in] h Clicon handle - * @param[in] file The plugin (.so) to load - * @param[in] dlflags Arguments to dlopen(3) - * @retval plugin Plugin struct - * @retval NULL Error - */ -static struct plugin * -backend_plugin_load (clicon_handle h, - char *file, - int dlflags) -{ - void *handle; - char *name; - struct plugin *new = NULL; - - if ((handle = plugin_load(h, file, dlflags)) == NULL) - goto done; - if ((new = malloc(sizeof(*new))) == NULL) { - clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno)); - dlclose(handle); - return NULL; - } - memset(new, 0, sizeof(*new)); - name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; - clicon_debug(2, "Loading plugin '%s'.", name); - snprintf(new->p_name, sizeof(new->p_name), "%*s", - (int)strlen(name)-2, name); - new->p_handle = handle; - if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_START); - if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_EXIT); - if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_RESET); - if ((new->p_statedata = dlsym(handle, PLUGIN_STATEDATA)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_STATEDATA); - if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN); - if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE); - if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE); - if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT); - if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END); - if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT); - clicon_debug(2, "Plugin '%s' loaded.\n", name); - done: - return new; -} - -/*! 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] dbname Name of database - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_reset_state(clicon_handle h, - const char *db) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_reset) { - clicon_debug(1, "Calling plugin_reset() for %s\n", - p->p_name); - if (((p->p_reset)(h, db)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Call plugin_start in all plugins - * @param[in] h Clicon handle - * @param[in] argc Command-line arguments - * @param[in] argv Command-line arguments - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_start_argv(clicon_handle h, - int argc, - char **argv) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_start) { - optind = 0; - if (((p->p_start)(h, argc, argv)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Append plugin to list - * @param[in] p Plugin - * @retval 0 OK - * @retval -1 Error - */ -static int -plugin_append(struct plugin *p) -{ - struct plugin *new; - - if ((new = realloc(_plugins, (_nplugins+1) * sizeof (*p))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - return -1; - } - - memset (&new[_nplugins], 0, sizeof(new[_nplugins])); - memcpy (&new[_nplugins], p, sizeof(new[_nplugins])); - _plugins = new; - _nplugins++; - - return 0; -} - -/*! Load backend plugins found in a directory - * The plugins must have the '.so' suffix - * @param[in] h Clicon handle - * @param[in] dir Backend plugin directory - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_load_dir(clicon_handle h, - const char *dir) -{ - int retval = -1; - int i; - int np = 0; - int ndp; - struct stat st; - char filename[MAXPATHLEN]; - struct dirent *dp = NULL; - struct plugin *new; - struct plugin *p = NULL; - char master[MAXPATHLEN]; - char *master_plugin; - - /* Format master plugin path */ - if ((master_plugin = clicon_master_plugin(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); - goto quit; - } - snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin); - - /* Allocate plugin group object */ - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* reset num plugins */ - np = 0; - - /* Master plugin must be loaded first if it exists. */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master); - if (stat(filename, &st) == 0) { - clicon_debug(1, "Loading master plugin '%.*s' ...", - (int)strlen(filename), filename); - - new = backend_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL); - if (new == NULL) - goto quit; - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* Now load the rest. Note plugins is the global variable */ - for (i = 0; i < ndp; i++) { - if (strcmp(dp[i].d_name, master) == 0) - continue; /* Skip master now */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename); - new = backend_plugin_load(h, filename, RTLD_NOW); - if (new == NULL) - goto quit; - /* Append to 'plugins' */ - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* All good. */ - retval = 0; - -quit: - if (retval != 0) { - /* XXX p is always NULL */ - if (_plugins) { - while (--np >= 0){ - if ((p = &_plugins[np]) == NULL) - continue; - backend_plugin_unload(h, p); - free(p); - } - free(_plugins); - _plugins=0; - } - } - if (dp) - free(dp); - return retval; -} - - /*! Load a plugin group. * @param[in] h Clicon handle * @retval 0 OK @@ -394,42 +85,115 @@ plugin_initiate(clicon_handle h) { char *dir; - /* First load CLICON system plugins */ - if (backend_plugin_load_dir(h, CLIXON_BACKEND_SYSDIR) < 0) - return -1; - - /* Then load application plugins */ - dir = clicon_backend_dir(h); - /* If backend directory, load the backend plugisn */ - if (dir && backend_plugin_load_dir(h, dir) < 0) - return -1; - - return 0; + /* Load application plugins */ + if ((dir = clicon_backend_dir(h)) == NULL) + return 0; + return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir); } -/*! Unload and deallocate all backend plugins +/*! 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 -plugin_finish(clicon_handle h) +clixon_plugin_reset(clicon_handle h, + char *db) { - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - backend_plugin_unload(h, p); + clixon_plugin *cp = NULL; + plgreset_t *resetfn; /* Plugin auth */ + int retval = 1; + + while ((cp = plugin_each(cp)) != NULL) { + if ((resetfn = cp->cp_api.ca_reset) == NULL) + continue; + if ((retval = resetfn(h, db)) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + break; } - if (_plugins){ - free(_plugins); - _plugins = NULL; - } - _nplugins = 0; - return 0; + 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] xpath String with XPATH syntax. or NULL for all + * @param[in,out] xml XML tree. + * @retval -1 Error + * @retval 0 OK + * @retval 1 Statedata callback failed + */ +int +clixon_plugin_statedata(clicon_handle h, + char *xpath, + cxobj *xtop) +{ + int retval = -1; + int i; + cxobj *x = NULL; + yang_spec *yspec; + cxobj **xvec = NULL; + size_t xlen; + clixon_plugin *cp = NULL; + plgstatedata_t *fn; /* Plugin statedata fn */ + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (xtop==NULL){ + clicon_err(OE_CFG, ENOENT, "XML tree expected"); + goto done; + } + while ((cp = plugin_each(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 (xml_merge(xtop, x, yspec) < 0) + goto done; + if (x){ + xml_free(x); + x = NULL; + } + } + /* Code complex to filter out anything that is outside of xpath */ + if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; ip_trans_begin) - if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN); + while ((cp = plugin_each(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; } @@ -508,20 +271,18 @@ plugin_transaction_validate(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - struct plugin *p; - - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_validate) - if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE); - - break; - } + while ((cp = plugin_each(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; } @@ -538,20 +299,20 @@ int plugin_transaction_complete(clicon_handle h, transaction_data_t *td) { - int i; int retval = 0; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_complete) - if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE); - - break; - } + while ((cp = plugin_each(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; } @@ -572,9 +333,9 @@ plugin_transaction_revert(clicon_handle h, { int retval = 0; transaction_data_t tr; /* revert transaction */ - int i; - struct plugin *p; - + 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)); @@ -588,14 +349,14 @@ plugin_transaction_revert(clicon_handle h, tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; - for (i = nr-1; i>=0; i--){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){ - clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed", - p->p_name, PLUGIN_TRANS_COMMIT); + while ((cp = plugin_each_revert(cp, nr)) != NULL) { + if ((fn = cp->cp_api.ca_trans_commit) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed", + __FUNCTION__, cp->cp_name); break; - } + } } return retval; /* ignore errors */ } @@ -614,20 +375,22 @@ plugin_transaction_commit(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; + int i=0; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT); - /* Make an effort to revert transaction */ - plugin_transaction_revert(h, td, i); - break; - } + while ((cp = plugin_each(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; } @@ -643,19 +406,18 @@ plugin_transaction_end(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_end) - if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_END); - - break; - } + while ((cp = plugin_each(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; } @@ -671,94 +433,14 @@ plugin_transaction_abort(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_abort) - (p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */ + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_abort) == NULL) + continue; + fn(h, (transaction_data)td); /* dont abort on error */ } return retval; } -/*---------------------------------------------------------------------- - * Backend state data callbacks - */ - -/*! 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] xpath String with XPATH syntax. or NULL for all - * @param[in,out] xml XML tree. - * @retval -1 Error - * @retval 0 OK - * @retval 1 Statedata callback failed - */ -int -backend_statedata_call(clicon_handle h, - char *xpath, - cxobj *xtop) -{ - int retval = -1; - struct plugin *p; - int i; - cxobj *x = NULL; - yang_spec *yspec; - cxobj **xvec = NULL; - size_t xlen; - - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if (xtop==NULL){ - clicon_err(OE_CFG, ENOENT, "XML tree expected"); - goto done; - } - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_statedata) { - if ((x = xml_new("config", NULL, NULL)) == NULL) - goto done; - if ((p->p_statedata)(h, xpath, x) < 0){ - retval = 1; - goto done; /* Dont quit here on user callbacks */ - } - if (xml_merge(xtop, x, yspec) < 0) - goto done; - if (x){ - xml_free(x); - x = NULL; - } - } - } - /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) - goto done; - - /* If vectors are specified then mark the nodes found and - * then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i +#include +#include +#include +#include +#include +#include + +/* clicon */ +#include + +/* Clicon library functions. */ +#include + +/* These include signatures for plugin and transaction callbacks. */ +#include + + +int +transaction_commit_2(clicon_handle h, + transaction_data td) +{ + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +int +plugin_start_2(clicon_handle h, + int argc, + char **argv) +{ + return 0; +} + +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "secondary", /* name */ + clixon_plugin_init, /* init */ + plugin_start_2, /* start */ + NULL, /* exit */ + NULL, /* auth */ + NULL, /* reset */ + NULL, /* statedata */ + NULL, /* trans begin */ + NULL, /* trans validate */ + NULL, /* trans complete */ + transaction_commit_2,/* trans commit */ + NULL, /* trans end */ + NULL /* trans abort */ +}; + +/*! Backend plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s backend secondary", __FUNCTION__); + return &api; +} diff --git a/example/example_netconf.c b/example/example_netconf.c index 65883e2c..f574de6f 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -64,19 +64,23 @@ plugin_exit(clicon_handle h) clixon_plugin_api * clixon_plugin_init(clicon_handle h); -static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - plugin_start, - plugin_exit, - NULL +static struct clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + plugin_exit, /* exit */ + NULL /* auth */ }; /*! Netconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct */ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { - return (void*)&api; + clicon_debug(1, "%s restconf", __FUNCTION__); + return &api; } diff --git a/example/example_restconf.c b/example/example_restconf.c index d3340a2f..87c378ed 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -269,19 +269,22 @@ plugin_credentials(clicon_handle h, clixon_plugin_api * clixon_plugin_init(clicon_handle h); -static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - NULL, - NULL, - plugin_credentials, +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + plugin_credentials /* auth */ }; /*! Restconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct */ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); - return (void*)&api; + return &api; } diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 3c9fb28f..82a44adc 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -73,7 +73,6 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ /*! Called by restconf to check credentials and return username */ -#define PLUGIN_CREDENTIALS "plugin_credentials" /* Plugin authorization. Set username option (or not) * @param[in] Clicon handle @@ -98,21 +97,12 @@ struct clixon_plugin_api; typedef struct clixon_plugin_api* (plginit2_t)(clicon_handle); /* Clixon plugin Init */ struct clixon_plugin_api{ - char ca_name[PATH_MAX]; /* Name of plugin */ + char ca_name[PATH_MAX]; /* Name of plugin (given by plugin) */ plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ plgstart_t *ca_start; /* Plugin start */ plgexit_t *ca_exit; /* Plugin exit */ plgauth_t *ca_auth; /* Auth credentials */ -}; - -struct clixon_backend_api{ - char ca_name[PATH_MAX]; /* Name of plugin */ - plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ - plgstart_t *ca_start; /* Plugin start */ - plgexit_t *ca_exit; /* Plugin exit */ - plgauth_t *ca_auth; /* Auth credentials */ - /*--Above here common fields with clixon_netconf_api, clixon_restconf_api, - * etc ----------*/ + /*--Above here common fields w clixon_backend_api ----------*/ plgreset_t *ca_reset; /* Reset system status (backend only) */ plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */ @@ -125,7 +115,6 @@ struct clixon_backend_api{ }; typedef struct clixon_plugin_api clixon_plugin_api; -#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ /*! Called when plugin loaded. Only mandadory callback. All others optional * @see plginit_t */ @@ -133,24 +122,43 @@ typedef struct clixon_plugin_api clixon_plugin_api; /* Internal plugin structure with dlopen() handle and plugin_api */ struct clixon_plugin{ + char cp_name[PATH_MAX]; /* Plugin filename. Note api ca_name is given by plugin itself */ plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */ struct clixon_plugin_api cp_api; }; typedef struct clixon_plugin clixon_plugin; +/* + * Pseudo-Prototypes + * User-defineed plugins, not in library code + */ +#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ + +/*! Plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + /* * Prototypes */ -int clixon_plugins_load(clicon_handle h, char *dir); +clixon_plugin *plugin_each(clixon_plugin *cpprev); +clixon_plugin *plugin_each_revert(clixon_plugin *cpprev, int nr); +int clixon_plugins_load(clicon_handle h, char *function, char *dir); + +/* obsolete */ plghndl_t plugin_load (clicon_handle h, char *file, int dlflags); +/* obsolete */ int plugin_unload(clicon_handle h, plghndl_t *handle); -int clixon_plugin_unload(clicon_handle h); - int clixon_plugin_start(clicon_handle h, int argc, char **argv); +int clixon_plugin_exit(clicon_handle h); + int clixon_plugin_auth(clicon_handle h, void *arg); #endif /* _CLIXON_PLUGIN_H_ */ diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index a1a552bd..2377352d 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -62,8 +62,7 @@ #include "clixon_string.h" #include "clixon_file.h" -/* - * qsort function +/*! qsort "compar" for directory alphabetically sorting, see qsort(3) */ static int clicon_file_dirent_sort(const void* arg1, @@ -79,8 +78,7 @@ clicon_file_dirent_sort(const void* arg1, #endif /* HAVE_STRVERSCMP */ } - -/*! Return sorted matching files from a directory +/*! Return alphabetically sorted files from a directory matching regexp * @param[in] dir Directory path * @param[out] ent Entries pointer, will be filled in with dir entries. Free * after use @@ -120,16 +118,13 @@ clicon_file_dirent(const char *dir, struct dirent *new = NULL; struct dirent *dvecp = NULL; - *ent = NULL; nent = 0; - if (regexp && (res = regcomp(&re, regexp, REG_EXTENDED)) != 0) { regerror(res, &re, errbuf, sizeof(errbuf)); clicon_err(OE_DB, 0, "regcomp: %s", errbuf); return -1; } - if ((dirp = opendir (dir)) == NULL) { if (errno == ENOENT) /* Dir does not exist -> return 0 matches */ retval = 0; @@ -137,7 +132,6 @@ clicon_file_dirent(const char *dir, clicon_err(OE_UNIX, errno, "opendir(%s)", dir); goto quit; } - for (res = readdir_r (dirp, &dent, &dresp); dresp; res = readdir_r (dirp, &dent, &dresp)) { @@ -162,7 +156,6 @@ clicon_file_dirent(const char *dir, if ((type & st.st_mode) == 0) continue; } - if ((tmp = realloc(new, (nent+1)*sizeof(*dvecp))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); goto quit; diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 6a5f589e..8b789ac1 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -64,9 +64,83 @@ static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */ static int _clixon_nplugins = 0; /* Number of plugins */ +/*! Iterator over clixon plugins + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = plugin_each(cp)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list + */ +clixon_plugin * +plugin_each(clixon_plugin *cpprev) +{ + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; + + if (cpprev == NULL) + cpnext = _clixon_plugins; + else{ + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; + } + if (cp && i < _clixon_nplugins-1) + cpnext = &_clixon_plugins[i+1]; + } + return cpnext; +} + +/*! Reverse iterator over clixon plugins, iterater from nr to 0 + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = plugin_each_revert(cp, nr)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list + */ +clixon_plugin * +plugin_each_revert(clixon_plugin *cpprev, + int nr) +{ + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; + + if (cpprev == NULL) + cpnext = &_clixon_plugins[nr-1]; + else{ + for (i = nr-1; i >= 0; i--) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; + } + if (cp && i > 0) + cpnext = &_clixon_plugins[i-1]; + } + return cpnext; +} + /*! Load a dynamic plugin object and call its init-function * @param[in] h Clicon handle * @param[in] file Which plugin to load + * @param[in] function Which function symbol to load and call * @param[in] dlflags See man(3) dlopen * @retval cp Clixon plugin structure * @retval NULL Error @@ -74,7 +148,8 @@ static int _clixon_nplugins = 0; /* Number of plugins */ */ static clixon_plugin * plugin_load_one(clicon_handle h, - char *file, + char *file, + char *function, int dlflags) { char *error; @@ -82,16 +157,17 @@ plugin_load_one(clicon_handle h, plginit2_t *initfn; clixon_plugin_api *api = NULL; clixon_plugin *cp = NULL; + char *name; clicon_debug(1, "%s", __FUNCTION__); dlerror(); /* Clear any existing error */ - if ((handle = dlopen (file, dlflags)) == NULL) { + if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); goto done; } - /* call plugin_init() if defined */ - if ((initfn = dlsym(handle, CLIXON_PLUGIN_INIT)) == NULL){ + /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */ + if ((initfn = dlsym(handle, function)) == NULL){ clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file); goto err; } @@ -106,11 +182,15 @@ plugin_load_one(clicon_handle h, file); goto err; } - if ((cp = (clixon_plugin *)malloc(sizeof(*cp))) == NULL){ + /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ + if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); goto done; } cp->cp_handle = handle; + name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; + snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", + (int)strlen(name)-2, name); cp->cp_api = *api; clicon_debug(1, "%s", __FUNCTION__); done: @@ -123,12 +203,14 @@ plugin_load_one(clicon_handle h, /*! Load a set of plugin objects from a directory and and call their init-function * @param[in] h Clicon handle + * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT) * @param[in] dir Directory. .so files in this dir will be loaded. * @retval 0 OK * @retval -1 Error */ int clixon_plugins_load(clicon_handle h, + char *function, char *dir) { int retval = -1; @@ -147,7 +229,7 @@ clixon_plugins_load(clicon_handle h, snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", (int)strlen(filename), filename); - if ((cp = plugin_load_one(h, filename, RTLD_NOW)) == NULL) + if ((cp = plugin_load_one(h, filename, function, RTLD_NOW)) == NULL) goto done; _clixon_nplugins++; if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) { @@ -169,7 +251,7 @@ done: * @param[in] h Clicon handle * @param[in] file Which plugin to load * @param[in] dlflags See man(3) dlopen - * @see plugin_load_one for netxgen, this is soon OBSOLETE + * @note OBSOLETE */ plghndl_t plugin_load(clicon_handle h, @@ -211,10 +293,10 @@ plugin_load(clicon_handle h, return NULL; } - /*! Unload a plugin * @param[in] h Clicon handle * @param[in] handle Clicon handle + * @note OBSOLETE */ int plugin_unload(clicon_handle h, @@ -238,27 +320,6 @@ plugin_unload(clicon_handle h, return retval; } -/*! Unload all plugins - * @param[in] h Clicon handle - */ -int -clixon_plugin_unload(clicon_handle h) -{ - clixon_plugin *cp; - int i; - - for (i = 0; i < _clixon_nplugins; i++) { - cp = &_clixon_plugins[i]; - plugin_unload(h, cp->cp_handle); - } - if (_clixon_plugins){ - free(_clixon_plugins); - _clixon_plugins = NULL; - } - _clixon_nplugins = 0; - return 0; -} - /*! Call plugin_start in all plugins * @param[in] h Clicon handle */ @@ -275,6 +336,7 @@ clixon_plugin_start(clicon_handle h, cp = &_clixon_plugins[i]; if ((startfn = cp->cp_api.ca_start) == NULL) continue; + // optind = 0; if (startfn(h, argc, argv) < 0) { clicon_debug(1, "plugin_start() failed\n"); return -1; @@ -283,6 +345,39 @@ clixon_plugin_start(clicon_handle h, return 0; } +/*! Unload all plugins: call exit function and close shared handle + * @param[in] h Clicon handle + */ +int +clixon_plugin_exit(clicon_handle h) +{ + clixon_plugin *cp; + plgexit_t *exitfn; + int i; + char *error; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((exitfn = cp->cp_api.ca_exit) == NULL) + continue; + if (exitfn(h) < 0) { + clicon_debug(1, "plugin_exit() failed\n"); + return -1; + } + if (dlclose(cp->cp_handle) != 0) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + } + } + if (_clixon_plugins){ + free(_clixon_plugins); + _clixon_plugins = NULL; + } + _clixon_nplugins = 0; + return 0; +} + + /*! Run the restconf user-defined credentials callback if present * Find first authentication callback and call that, then return. * The callback is to set the authenticated user diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index eaaf33c4..6811e550 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -433,7 +433,7 @@ xml_child_i_set(cxobj *xt, /*! Iterator over xml children objects * - * NOTE: Never manipulate the child-list during operation or using the + * @note Never manipulate the child-list during operation or using the * same object recursively, the function uses an internal field to remember the * index used. It works as long as the same object is not iterated concurrently. *