/*
* dynamicloader.cpp
*
* This file is part of NEST.
*
* Copyright (C) 2004 The NEST Initiative
*
* NEST is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* NEST 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NEST. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
This file is part of NEST
dynamicloader.cpp -- Implements the class DynamicLoaderModule
to allow for dymanically loaded modules for extending the kernel.
Author(s):
Moritz Helias
First Version: November 2005
*/
#include "dynamicloader.h"
#ifdef HAVE_LIBLTDL
#include <ltdl.h>
#include "network.h"
#include "interpret.h"
#include "integerdatum.h"
#include "stringdatum.h"
#include "dynmodule.h"
#include "model.h"
namespace nest
{
struct sDynModule {
std::string name;
lt_dlhandle handle;
DynModule *pModule;
bool operator==(const sDynModule & rhs) const
{
return name == rhs.name;
}
// operator!= must be implemented explicitly, not all compilers
// generate it automatically from operator==
bool operator!=(const sDynModule & rhs) const
{
return !(*this == rhs);
}
};
// static member initialization
Dictionary* DynamicLoaderModule::moduledict_ = new Dictionary();
vecLinkedModules& DynamicLoaderModule::getLinkedModules()
{
static vecLinkedModules lm; // initialized empty on first call
return lm;
}
/*! At the time when DynamicLoaderModule is constructed, the SLI Interpreter
and NestModule must be already constructed and initialized.
DynamicLoaderModule relies on the presence of
the following SLI datastructures: Name, Dictionary
and on the nest::NestModule::net.
*/
DynamicLoaderModule::DynamicLoaderModule(Network *pNet, SLIInterpreter &interpreter) :
loadmodule_function(pNet, dyn_modules),
unloadmodule_function(pNet, dyn_modules)
{
assert(pNet != NULL);
pNet_ = pNet;
interpreter.def("moduledict", new DictionaryDatum(moduledict_));
}
DynamicLoaderModule::~DynamicLoaderModule()
{
// unload all loaded modules
for (vecDynModules::iterator it = dyn_modules.begin();
it != dyn_modules.end(); it++)
{
if (it->handle != NULL) {
lt_dlclose(it->handle);
it->handle = NULL;
}
}
lt_dlexit();
}
// The following concerns the new module: -----------------------
const std::string DynamicLoaderModule::name(void) const
{
return std::string("NEST-Dynamic Loader"); // Return name of the module
}
const std::string DynamicLoaderModule::commandstring(void) const
{
return std::string(""); // Run associated SLI startup script
}
// auxiliary function to check name of module via its pointer
// we cannot use a & for the second argument, as std::bind2nd() then
// becomes confused, at least with g++ 4.0.1.
bool has_name(DynModule const * const m, const std::string n)
{
return m->name() == n;
}
/*
BeginDocumentation
Name: Install - Load a dynamic module to extend the functionality.
Description:
Synopsis: (module_name) Install -> handle
*/
DynamicLoaderModule::LoadModuleFunction::LoadModuleFunction(Network *pNet, vecDynModules &dyn_modules) :
pNet_(pNet), dyn_modules_(dyn_modules)
{}
void DynamicLoaderModule::LoadModuleFunction::execute(SLIInterpreter *i) const
{
i->assert_stack_load(1);
sDynModule new_module;
new_module.name = getValue<std::string>(i->OStack.top());
if ( new_module.name.empty() )
throw DynamicModuleManagementError("Module name must not be empty.");
// check if module already loaded
// this check can happen here, since we are comparing dynamically loaded modules
// based on the name given to the Install command
if ( std::find(dyn_modules_.begin(), dyn_modules_.end(), new_module)
!= dyn_modules_.end() )
throw DynamicModuleManagementError(
"Module '" + new_module.name + "' is loaded already.");
// try to open the module
const lt_dlhandle hModule = lt_dlopenext(new_module.name.c_str());
if ( !hModule )
{
char *errstr = (char *) lt_dlerror();
std::string msg = "Module '" + new_module.name + "' could not be opened.";
if ( errstr )
msg += "\nThe dynamic loader returned the following error: '"
+ std::string(errstr) + "'.";
msg += "\n\nPlease check LD_LIBRARY_PATH (OSX: DYLD_LIBRARY_PATH)!";
throw DynamicModuleManagementError(msg);
}
// see if we can find the mod symbol in the module
DynModule * pModule = (DynModule *) lt_dlsym(hModule, "mod");
char *errstr = (char *) lt_dlerror();
if ( errstr )
{
lt_dlclose(hModule); // close module again
lt_dlerror(); // remove any error caused by lt_dlclose()
throw DynamicModuleManagementError(
"Module '" + new_module.name + "' could not be loaded.\n"
"The dynamic loader returned the following error: '"
+ std::string(errstr) + "'.");
}
// check if module is linked in. This test is based on the module name
// returned by DynModule::name(), since we have no file names for linked modules.
// We can only perform it after we have loaded the module.
if (std::find_if(DynamicLoaderModule::getLinkedModules().begin(),
DynamicLoaderModule::getLinkedModules().end(),
std::bind2nd(std::ptr_fun(has_name), pModule->name()))
!= DynamicLoaderModule::getLinkedModules().end())
{
lt_dlclose(hModule); // close module again
lt_dlerror(); // remove any error caused by lt_dlclose()
throw DynamicModuleManagementError(
"Module '" + new_module.name + "' is linked into NEST.\n"
"You neither need nor may load it dynamically in addition.");
}
// all is well an we can register the module with the interpreter
try {
pModule->install(std::cerr, i, pNet_);
}
catch ( std::exception& e )
{
// We should uninstall the partially installed module here, but
// this must wait for #152.
// For now, we just close the module file and rethrow the exception.
lt_dlclose(hModule);
lt_dlerror(); // remove any error caused by lt_dlclose()
throw; // no arg re-throws entire exception, see Stroustrup 14.3.1
}
// add the handle to list of loaded modules
new_module.handle = hModule;
new_module.pModule = pModule;
dyn_modules_.push_back(new_module);
i->message(SLIInterpreter::M_INFO, "Install", ("loaded module " + pModule->name()).c_str());
// remove operand and operator from stack
i->OStack.pop();
i->EStack.pop();
// put handle to module onto stack
int moduleid = dyn_modules_.size() - 1;
i->OStack.push(moduleid);
(*moduledict_)[new_module.name] = moduleid;
// now we can run the module initializer, after we have cleared the EStack
if ( !pModule->commandstring().empty() )
{
Token t = new StringDatum(pModule->commandstring());
i->OStack.push_move(t);
Token c = new NameDatum("initialize_module");
i->EStack.push_move(c);
}
}
/*
BeginDocumentation
Name: Uninstall - Uninstall a previously loaded module.
Description:
Synopsis: handle Uninstall
See: Install
*/
DynamicLoaderModule::UnloadModuleFunction::UnloadModuleFunction(Network *pNet, vecDynModules &dyn_modules):
pNet_(pNet), dyn_modules_(dyn_modules) { }
void DynamicLoaderModule::UnloadModuleFunction::execute(SLIInterpreter *i) const
{
if (i->OStack.load() < 1)
{
i->raiseerror(i->StackUnderflowError);
return;
}
IntegerDatum *mod_id = dynamic_cast<IntegerDatum *>(i->OStack.top().datum());
if (mod_id == NULL) {
i->message(SLIInterpreter::M_ERROR, "Uninstall", "expected argument of type integer");
i->raiseerror(i->ArgumentTypeError);
return;
}
// check, if given id is in correct range
if (static_cast<size_t>(mod_id->get()) >= dyn_modules_.size()
|| dyn_modules_[mod_id->get()].handle == 0) {
i->message(SLIInterpreter::M_ERROR, "Uninstall", "id is not bound to any loaded module");
i->raiseerror("ArgumentError");
return;
}
// Check if there are any user defined models. We cannot unload in that case.
if ( pNet_->has_user_models() )
{
i->message(SLIInterpreter::M_ERROR, "Uninstall", "Modules cannot be unloaded after use of CopyModel.");
i->raiseerror("KernelError");
return;
}
// unregister symbols/dictionaries defined in this module
try {
DynModule *pMod = dyn_modules_[mod_id->get()].pModule;
pMod->unregister(i, pNet_);
}
catch (KernelException & e)
{
i->message(SLIInterpreter::M_ERROR, "Uninstall", "Modules cannot be unloaded.");
i->message(SLIInterpreter::M_ERROR, "Uninstall", e.what());
i->raiseerror("KernelError");
return;
}
// unload the module
lt_dlclose(dyn_modules_[mod_id->get()].handle);
lt_dlerror(); // remove any error caused by lt_dlclose()
dyn_modules_[mod_id->get()].pModule = 0; // mark as unloaded
dyn_modules_[mod_id->get()].handle = 0; // mark as unloaded
dyn_modules_[mod_id->get()].name = ""; // mark as unloaded
i->message(SLIInterpreter::M_INFO, "Uninstall", "sucessfully unloaded module");
// remove operand and operator from stack
i->OStack.pop();
i->EStack.pop();
}
void DynamicLoaderModule::init(SLIInterpreter *i)
{
// bind functions to terminal names
i->createcommand("Install", &loadmodule_function);
i->createcommand("Uninstall", &unloadmodule_function);
// initialize ltdl library for loading dynamic modules
int dl_error = lt_dlinit();
if (!dl_error)
{
const char *path = getenv ("SLI_MODULE_PATH");
if (path != NULL)
{
i->message(SLIInterpreter::M_INFO, "DynamicLoaderModule::init", "Setting module path to" );
i->message(SLIInterpreter::M_INFO, "DynamicLoaderModule::init", path );
dl_error = lt_dlsetsearchpath (path);
if (dl_error)
i->message(SLIInterpreter::M_ERROR, "DynamicLoaderModule::init", "Could not set dynamic module path.");
}
}
else
{
i->message(SLIInterpreter::M_ERROR, "DynamicLoaderModule::init", "Could not initialize libltdl. No dynamic modules will be avaiable.");
}
}
int DynamicLoaderModule::registerLinkedModule(DynModule *pModule)
{
assert(pModule != 0);
getLinkedModules().push_back(pModule);
return getLinkedModules().size();
}
void DynamicLoaderModule::initLinkedModules(SLIInterpreter &interpreter)
{
for (vecLinkedModules::iterator it = getLinkedModules().begin();
it != getLinkedModules().end(); it++)
{
interpreter.message(SLIInterpreter::M_STATUS, "DynamicLoaderModule::initLinkedModules",
"adding linked module");
interpreter.message(SLIInterpreter::M_STATUS, "DynamicLoaderModule::initLinkedModules",
(*it)->name().c_str());
interpreter.addlinkeddynmodule(*it, pNet_);
}
}
} // namespace nest
#endif // HAVE_LIBLTDL