/*
This file is part of GNUnet
Copyright (C) 2002-2013 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file util/plugin.c
* @brief Methods to access plugins
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include "gnunet_util_lib.h"
#define LOG(kind,...) GNUNET_log_from (kind, "util-plugin", __VA_ARGS__)
/**
* Linked list of active plugins.
*/
struct PluginList
{
/**
* This is a linked list.
*/
struct PluginList *next;
/**
* Name of the library.
*/
char *name;
/**
* System handle.
*/
void *handle;
};
/**
* Have we been initialized?
*/
static int initialized;
/**
* Libtool search path before we started.
*/
static char *old_dlsearchpath;
/**
* List of plugins we have loaded.
*/
static struct PluginList *plugins;
/**
* Setup libtool paths.
*/
static void
plugin_init ()
{
int err;
const char *opath;
char *path;
char *cpath;
err = lt_dlinit ();
if (err > 0)
{
FPRINTF (stderr,
_("Initialization of plugin mechanism failed: %s!\n"),
lt_dlerror ());
return;
}
opath = lt_dlgetsearchpath ();
if (NULL != opath)
old_dlsearchpath = GNUNET_strdup (opath);
path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
if (NULL != path)
{
if (NULL != opath)
{
GNUNET_asprintf (&cpath, "%s:%s", opath, path);
lt_dlsetsearchpath (cpath);
GNUNET_free (path);
GNUNET_free (cpath);
}
else
{
lt_dlsetsearchpath (path);
GNUNET_free (path);
}
}
}
/**
* Shutdown libtool.
*/
static void
plugin_fini ()
{
lt_dlsetsearchpath (old_dlsearchpath);
if (NULL != old_dlsearchpath)
{
GNUNET_free (old_dlsearchpath);
old_dlsearchpath = NULL;
}
lt_dlexit ();
}
/**
* Lookup a function in the plugin.
*
* @param plug the plugin to check
* @param name name of the symbol to look for
* @return NULL if the symbol was not found
*/
static GNUNET_PLUGIN_Callback
resolve_function (struct PluginList *plug,
const char *name)
{
char *initName;
void *mptr;
GNUNET_asprintf (&initName,
"_%s_%s",
plug->name,
name);
mptr = lt_dlsym (plug->handle, &initName[1]);
if (NULL == mptr)
mptr = lt_dlsym (plug->handle, initName);
if (NULL == mptr)
LOG (GNUNET_ERROR_TYPE_ERROR,
_("`%s' failed to resolve method '%s' with error: %s\n"),
"lt_dlsym",
&initName[1], lt_dlerror ());
GNUNET_free (initName);
return mptr;
}
/**
* Test if a plugin exists.
*
* Note that the library must export a symbol called
* `library_name_init` for the test to succeed.
*
* @param library_name name of the plugin to test if it is installed
* @return #GNUNET_YES if the plugin exists, #GNUNET_NO if not
*/
int
GNUNET_PLUGIN_test (const char *library_name)
{
void *libhandle;
GNUNET_PLUGIN_Callback init;
struct PluginList plug;
if (! initialized)
{
initialized = GNUNET_YES;
plugin_init ();
}
libhandle = lt_dlopenext (library_name);
if (NULL == libhandle)
return GNUNET_NO;
plug.handle = libhandle;
plug.name = (char *) library_name;
init = resolve_function (&plug, "init");
if (NULL == init)
{
GNUNET_break (0);
lt_dlclose (libhandle);
return GNUNET_NO;
}
lt_dlclose (libhandle);
return GNUNET_YES;
}
/**
* Setup plugin (runs the `init` callback and returns whatever `init`
* returned). If `init` returns NULL, the plugin is unloaded.
*
* Note that the library must export symbols called
* `library_name_init` and `library_name_done`. These will be called
* when the library is loaded and unloaded respectively.
*
* @param library_name name of the plugin to load
* @param arg argument to the plugin initialization function
* @return whatever the initialization function returned
*/
void *
GNUNET_PLUGIN_load (const char *library_name, void *arg)
{
void *libhandle;
struct PluginList *plug;
GNUNET_PLUGIN_Callback init;
void *ret;
if (!initialized)
{
initialized = GNUNET_YES;
plugin_init ();
}
libhandle = lt_dlopenext (library_name);
if (libhandle == NULL)
{
LOG (GNUNET_ERROR_TYPE_ERROR,
_("`%s' failed for library `%s' with error: %s\n"),
"lt_dlopenext",
library_name, lt_dlerror ());
return NULL;
}
plug = GNUNET_new (struct PluginList);
plug->handle = libhandle;
plug->name = GNUNET_strdup (library_name);
plug->next = plugins;
plugins = plug;
init = resolve_function (plug, "init");
if ((init == NULL) || (NULL == (ret = init (arg))))
{
lt_dlclose (libhandle);
GNUNET_free (plug->name);
plugins = plug->next;
GNUNET_free (plug);
return NULL;
}
return ret;
}
/**
* Unload plugin (runs the `done` callback and returns whatever `done`
* returned). The plugin is then unloaded.
*
* @param library_name name of the plugin to unload
* @param arg argument to the plugin shutdown function
* @return whatever the shutdown function returned
*/
void *
GNUNET_PLUGIN_unload (const char *library_name,
void *arg)
{
struct PluginList *pos;
struct PluginList *prev;
GNUNET_PLUGIN_Callback done;
void *ret;
prev = NULL;
pos = plugins;
while ((NULL != pos) && (0 != strcmp (pos->name, library_name)))
{
prev = pos;
pos = pos->next;
}
if (NULL == pos)
return NULL;
done = resolve_function (pos, "done");
ret = NULL;
if (NULL != done)
ret = done (arg);
if (NULL == prev)
plugins = pos->next;
else
prev->next = pos->next;
lt_dlclose (pos->handle);
GNUNET_free (pos->name);
GNUNET_free (pos);
if (NULL == plugins)
{
plugin_fini ();
initialized = GNUNET_NO;
}
return ret;
}
/**
* Closure for #find_libraries().
*/
struct LoadAllContext
{
/**
* Prefix the plugin names we find have to match.
*/
const char *basename;
/**
* Argument to give to 'init' when loading the plugin.
*/
void *arg;
/**
* Function to call for each plugin.
*/
GNUNET_PLUGIN_LoaderCallback cb;
/**
* Closure for @e cb
*/
void *cb_cls;
};
/**
* Function called on each plugin in the directory. Loads
* the plugins that match the given basename.
*
* @param cls the `struct LoadAllContext` describing which
* plugins to load and what to do with them
* @param filename name of a plugin library to check
* @return #GNUNET_OK (continue loading)
*/
static int
find_libraries (void *cls, const char *filename)
{
struct LoadAllContext *lac = cls;
const char *slashpos;
const char *libname;
char *basename;
char *dot;
void *lib_ret;
size_t n;
libname = filename;
while (NULL != (slashpos = strstr (libname, DIR_SEPARATOR_STR)))
libname = slashpos + 1;
n = strlen (libname);
if (0 != strncmp (lac->basename, libname, strlen (lac->basename)))
return GNUNET_OK; /* wrong name */
if ((n > 3) && (0 == strcmp (&libname[n - 3], ".la")))
return GNUNET_OK; /* .la file */
basename = GNUNET_strdup (libname);
if (NULL != (dot = strstr (basename, ".")))
*dot = '\0';
lib_ret = GNUNET_PLUGIN_load (basename, lac->arg);
if (NULL != lib_ret)
lac->cb (lac->cb_cls, basename, lib_ret);
GNUNET_free (basename);
return GNUNET_OK;
}
/**
* Load all compatible plugins with the given base name.
*
* Note that the library must export symbols called
* `basename_ANYTHING_init` and `basename_ANYTHING__done`. These will
* be called when the library is loaded and unloaded respectively.
*
* @param basename basename of the plugins to load
* @param arg argument to the plugin initialization function
* @param cb function to call for each plugin found
* @param cb_cls closure for @a cb
*/
void
GNUNET_PLUGIN_load_all (const char *basename, void *arg,
GNUNET_PLUGIN_LoaderCallback cb, void *cb_cls)
{
struct LoadAllContext lac;
char *path;
path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
if (NULL == path)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Could not determine plugin installation path.\n"));
return;
}
lac.basename = basename;
lac.arg = arg;
lac.cb = cb;
lac.cb_cls = cb_cls;
GNUNET_DISK_directory_scan (path, &find_libraries, &lac);
GNUNET_free (path);
}
/* end of plugin.c */