/******************************************************************************** * Copyright (C) 2017-2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * * * * This software is distributed under the terms of the * * GNU Lesser General Public Licence (LGPL) version 3, * * copied verbatim in the file "LICENSE" * ********************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include using namespace std; using fair::mq::Plugin; using fair::mq::tools::ToString; using fair::mq::tools::ToStrVector; namespace po = boost::program_options; namespace dll = boost::dll; using boost::optional; const std::string fair::mq::PluginManager::fgkLibPrefix = "fairmq-plugin-"; const std::string fair::mq::PluginManager::fgkLibPrefixAlt = "FairMQPlugin_"; std::vector fair::mq::PluginManager::fgDLLKeepAlive = std::vector(); fair::mq::PluginManager::PluginManager() : fPluginServices() {} fair::mq::PluginManager::PluginManager(const vector& args) : fPluginServices() { // Parse command line options auto options = ProgramOptions(); auto vm = po::variables_map{}; try { auto parsed = po::command_line_parser(args).options(options).allow_unregistered().run(); po::store(parsed, vm); po::notify(vm); } catch (const po::error& e) { throw ProgramOptionsParseError{ToString( "Error occured while parsing the 'Plugin Manager' program options: ", e.what())}; } // Process plugin search paths auto append = vector{}; auto prepend = vector{}; auto searchPaths = vector{}; if (vm.count("plugin-search-path")) { for (const auto& path : vm["plugin-search-path"].as>()) { if (path.substr(0, 1) == "<") { prepend.emplace_back(path.substr(1)); } else if (path.substr(0, 1) == ">") { append.emplace_back(path.substr(1)); } else { searchPaths.emplace_back(path); } } } // Set supplied options SetSearchPaths(searchPaths); for (const auto& path : prepend) { PrependSearchPath(path); } for (const auto& path : append) { AppendSearchPath(path); } if (vm.count("plugin")) { LoadPlugins(vm["plugin"].as>()); } } auto fair::mq::PluginManager::ValidateSearchPath(const fs::path& path) -> void { if (path.empty()) { throw BadSearchPath{"Specified path is empty."}; } // we ignore non-existing search paths if (fs::exists(path) && !fs::is_directory(path)) { throw BadSearchPath{ToString(path, " is not a directory.")}; } } auto fair::mq::PluginManager::SetSearchPaths(const vector& searchPaths) -> void { for_each(begin(searchPaths), end(searchPaths), ValidateSearchPath); fSearchPaths = searchPaths; } auto fair::mq::PluginManager::AppendSearchPath(const fs::path& path) -> void { ValidateSearchPath(path); fSearchPaths.push_back(path); } auto fair::mq::PluginManager::PrependSearchPath(const fs::path& path) -> void { ValidateSearchPath(path); fSearchPaths.insert(begin(fSearchPaths), path); } auto fair::mq::PluginManager::ProgramOptions() -> po::options_description { auto plugin_options = po::options_description{"Plugin Manager"}; plugin_options.add_options()("plugin-search-path,S", po::value>()->multitoken(), "List of plugin search paths.\n\n" "* Override default search path, e.g.\n" " -S /home/user/lib /lib\n" "* Append(>) or prepend(<) to default search path, e.g.\n" " -S >/lib /lib >(), "List of plugin names to load in order," "e.g. if the file is called 'libfairmq-plugin-example.so', just list 'example' or " "'d:example' here." "To load a prelinked plugin, list 'p:example' here."); return plugin_options; } auto fair::mq::PluginManager::LoadPlugin(const string& pluginName) -> void { if (pluginName.substr(0, 2) == "p:") { // Mechanism A: prelinked dynamic LoadPluginPrelinkedDynamic(pluginName.substr(2)); } else if (pluginName.substr(0, 2) == "d:") { // Mechanism B: dynamic LoadPluginDynamic(pluginName.substr(2)); } else if (pluginName.substr(0, 2) == "s:") { // Mechanism C: static (builtin) LoadPluginStatic(pluginName.substr(2)); } else { // Mechanism B: dynamic (default) LoadPluginDynamic(pluginName); } } auto fair::mq::PluginManager::LoadPluginPrelinkedDynamic(const string& pluginName) -> void { // Load symbol if (fPluginFactories.find(pluginName) == fPluginFactories.end()) { try { LoadSymbols(pluginName, dll::program_location()); fPluginOrder.push_back(pluginName); } catch (boost::system::system_error& e) { throw PluginLoadError( ToString("An error occurred while loading prelinked dynamic plugin ", pluginName, ": ", e.what())); } } } auto fair::mq::PluginManager::SearchPluginFile(const string& pluginName) const -> fs::path { for (const auto& searchPath : SearchPaths()) { for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) { auto const file = searchPath / ToString("lib", libPrefix, pluginName, boost::dll::detail::shared_library_impl::suffix().native()); auto const found = fs::exists(file); if (found) { return file; } } } throw PluginNotFound(ToString("Could not find plugin file for plugin ", pluginName)); } auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void { if (fPluginFactories.find(pluginName) != fPluginFactories.end()) { return; // already loaded, nothing to do } // Search in configured search path try { LoadSymbols(pluginName, SearchPluginFile(pluginName), dll::load_mode::rtld_global); fPluginOrder.push_back(pluginName); return; } catch (PluginNotFound& e) { // ignore } catch (boost::system::system_error& e) { // ignore } // Search with system logic (including RPATH, LD_LIBRARY_PATH) for (const auto& libPrefix : {fgkLibPrefix, fgkLibPrefixAlt}) { try { // LoadSymbols(pluginName, // ToString(LibPrefix(), pluginName), // dll::load_mode::search_system_folders | dll::load_mode::append_decorations); // Not sure, why the above does not work. Workaround for now: LoadSymbols(pluginName, ToString("lib", libPrefix, pluginName, boost::dll::detail::shared_library_impl::suffix().native()), dll::load_mode::search_system_folders | dll::load_mode::rtld_global); fPluginOrder.push_back(pluginName); return; } catch (boost::system::system_error& e) { // ignore } } throw PluginLoadError(ToString("Could not load dynamic plugin ", pluginName, " from configured search paths (-S) nor with ", "system lookup (system libdirs, RUNPATH, $LD_LIBRARY_PATH).")); } auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void { // Load symbol if (fPluginFactories.find(pluginName) == fPluginFactories.end()) { try { if ("control" == pluginName) { try { fPluginProgOptions.insert( {pluginName, plugins::ControlPluginProgramOptions().value()}); } catch (const boost::bad_optional_access&) { /* just ignore, if no prog options are declared */ } } else if ("config" == pluginName) { try { fPluginProgOptions.insert( {pluginName, plugins::ConfigPluginProgramOptions().value()}); } catch (const boost::bad_optional_access&) { /* just ignore, if no prog options are declared */ } } else { LoadSymbols(pluginName, dll::program_location()); } fPluginOrder.push_back(pluginName); } catch (boost::system::system_error& e) { throw PluginLoadError(ToString( "An error occurred while loading static plugin ", pluginName, ": ", e.what())); } } } auto fair::mq::PluginManager::InstantiatePlugin(const string& pluginName) -> void { if (fPlugins.find(pluginName) == fPlugins.end()) { if ("control" == pluginName) { fPlugins[pluginName] = plugins::Make_control_Plugin(fPluginServices.get()); } else if ("config" == pluginName) { fPlugins[pluginName] = plugins::Make_config_Plugin(fPluginServices.get()); } else { fPlugins[pluginName] = fPluginFactories[pluginName](*fPluginServices); } } } auto fair::mq::PluginManager::InstantiatePlugins() -> void { for (const auto& pluginName : fPluginOrder) { try { InstantiatePlugin(pluginName); } catch (std::exception& e) { throw PluginInstantiationError(ToString( "An error occurred while instantiating plugin ", pluginName, ": ", e.what())); } } }