clixon/lib/src/clixon_plugin.c

412 lines
12 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/param.h>
/* cligen */
#include <cligen/cligen.h>
#include "clixon_err.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_log.h"
#include "clixon_file.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_plugin.h"
/* XXX The below should be placed in clixon handle when done */
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
* @see clixon_plugins_load Load all plugins
*/
static clixon_plugin *
plugin_load_one(clicon_handle h,
char *file,
char *function,
int dlflags)
{
char *error;
void *handle = NULL;
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) {
error = (char*)dlerror();
clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error");
goto done;
}
/* 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;
}
if ((error = (char*)dlerror()) != NULL) {
clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
goto done;
}
if ((api = initfn(h)) == NULL) {
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
file);
goto err;
}
/* 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:
return cp;
err:
if (handle)
dlclose(handle);
return NULL;
}
/*! 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;
int ndp;
struct dirent *dp = NULL;
int i;
char filename[MAXPATHLEN];
clixon_plugin *cp;
clicon_debug(1, "%s", __FUNCTION__);
/* Get plugin objects names from plugin directory */
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
goto done;
/* Load all plugins */
for (i = 0; i < ndp; i++) {
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, function, RTLD_NOW)) == NULL)
goto done;
_clixon_nplugins++;
if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
clicon_err(OE_UNIX, errno, "realloc");
goto done;
}
_clixon_plugins[_clixon_nplugins-1] = *cp;
free(cp);
}
retval = 0;
done:
if (dp)
free(dp);
return retval;
}
/*! Load a dynamic plugin object and call its init-function
* Note 'file' may be destructively modified
* @param[in] h Clicon handle
* @param[in] file Which plugin to load
* @param[in] dlflags See man(3) dlopen
* @note OBSOLETE
*/
plghndl_t
plugin_load(clicon_handle h,
char *file,
int dlflags)
{
char *error;
void *handle = NULL;
plginit_t *initfn;
clicon_debug(1, "%s", __FUNCTION__);
dlerror(); /* Clear any existing error */
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, PLUGIN_INIT)) == NULL){
clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading clixon plugin %s", file);
goto err;
}
if ((error = (char*)dlerror()) != NULL) {
clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
goto done;
}
if (initfn(h) != 0) {
clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
file);
goto err;
}
done:
return handle;
err:
if (handle)
dlclose(handle);
return NULL;
}
/*! Unload a plugin
* @param[in] h Clicon handle
* @param[in] handle Clicon handle
* @note OBSOLETE
*/
int
plugin_unload(clicon_handle h,
plghndl_t *handle)
{
int retval = 0;
char *error;
plgexit_t *exitfn;
/* Call exit function is it exists */
exitfn = dlsym(handle, PLUGIN_EXIT);
if (dlerror() == NULL)
exitfn(h);
dlerror(); /* Clear any existing error */
if (dlclose(handle) != 0) {
error = (char*)dlerror();
clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error");
/* Just report */
}
return retval;
}
/*! Call plugin_start in all plugins
* @param[in] h Clicon handle
*/
int
clixon_plugin_start(clicon_handle h,
int argc,
char **argv)
{
clixon_plugin *cp;
int i;
plgstart_t *startfn; /* Plugin start */
for (i = 0; i < _clixon_nplugins; i++) {
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;
}
}
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
* @param[in] h Clicon handle
* @param[in] arg Argument, such as fastcgi handler for restconf
* @retval -1 Error
* @retval 0 Not authenticated
* @retval 1 Authenticated
* @note If authenticated either a callback was called and clicon_username_set()
* Or no callback was found.
*/
int
clixon_plugin_auth(clicon_handle h,
void *arg)
{
clixon_plugin *cp;
int i;
plgauth_t *authfn; /* Plugin auth */
int retval = 1;
for (i = 0; i < _clixon_nplugins; i++) {
cp = &_clixon_plugins[i];
if ((authfn = cp->cp_api.ca_auth) == NULL)
continue;
if ((retval = authfn(h, arg)) < 0) {
clicon_debug(1, "plugin_start() failed\n");
return -1;
}
break;
}
return retval;
}