From 3590a079b8304b3b1f3f4a66bad698bf91e9b4e2 Mon Sep 17 00:00:00 2001 From: Dennis Klein Date: Thu, 24 Mar 2022 16:01:47 +0100 Subject: [PATCH] feat(plugins): Allow kebab-case plugin names, e.g. `libfairmq-plugin-pmix` Camel+snake-case plugin names are still allowed! e.g. `libFairMQPlugin_pmix` --- docs/Plugins.md | 2 +- fairmq/PluginManager.cxx | 212 ++++++++++++++++++++++++--------------- fairmq/PluginManager.h | 3 + test/CMakeLists.txt | 6 +- 4 files changed, 140 insertions(+), 83 deletions(-) diff --git a/docs/Plugins.md b/docs/Plugins.md index 57ee384b..2c6003af 100644 --- a/docs/Plugins.md +++ b/docs/Plugins.md @@ -35,7 +35,7 @@ Plugin Manager: see man ld.so(8) for details. -P [ --plugin ] arg List of plugin names to load in order,e.g. if the file is called - 'libFairMQPlugin_example.so', just list + 'libfairmq-plugin-example.so', just list 'example' or 'd:example' here.To load a prelinked plugin, list 'p:example' here. diff --git a/fairmq/PluginManager.cxx b/fairmq/PluginManager.cxx index f5286b76..9bf1cc34 100644 --- a/fairmq/PluginManager.cxx +++ b/fairmq/PluginManager.cxx @@ -1,16 +1,17 @@ /******************************************************************************** - * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * 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 #include #include @@ -18,22 +19,23 @@ #include using namespace std; +using fair::mq::Plugin; using fair::mq::tools::ToString; using fair::mq::tools::ToStrVector; -using fair::mq::Plugin; namespace fs = boost::filesystem; namespace po = boost::program_options; namespace dll = boost::dll; using boost::optional; -const std::string fair::mq::PluginManager::fgkLibPrefix = "FairMQPlugin_"; +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(); +std::vector fair::mq::PluginManager::fgDLLKeepAlive = + std::vector(); fair::mq::PluginManager::PluginManager() : fPluginServices() -{ -} +{} fair::mq::PluginManager::PluginManager(const vector& args) : fPluginServices() @@ -46,7 +48,8 @@ fair::mq::PluginManager::PluginManager(const vector& args) 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())}; + throw ProgramOptionsParseError{ToString( + "Error occured while parsing the 'Plugin Manager' program options: ", e.what())}; } // Process plugin search paths @@ -55,24 +58,38 @@ fair::mq::PluginManager::PluginManager(const vector& args) 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); } + 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>()); } + 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."}; + 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.")}; + 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 @@ -96,31 +113,38 @@ auto fair::mq::PluginManager::PrependSearchPath(const fs::path& path) -> void 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 'libFairMQPlugin_example.so', just list 'example' or 'd:example' here." - "To load a prelinked plugin, list 'p:example' here."); + 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:") { + if (pluginName.substr(0, 2) == "p:") { // Mechanism A: prelinked dynamic LoadPluginPrelinkedDynamic(pluginName.substr(2)); - } else if (pluginName.substr(0,2) == "d:") { + } else if (pluginName.substr(0, 2) == "d:") { // Mechanism B: dynamic LoadPluginDynamic(pluginName.substr(2)); - } else if (pluginName.substr(0,2) == "s:") { + } else if (pluginName.substr(0, 2) == "s:") { // Mechanism C: static (builtin) LoadPluginStatic(pluginName.substr(2)); } else { @@ -137,52 +161,76 @@ auto fair::mq::PluginManager::LoadPluginPrelinkedDynamic(const string& pluginNam 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())); + throw PluginLoadError( + ToString("An error occurred while loading prelinked dynamic plugin ", + pluginName, + ": ", + e.what())); } } } -auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void +auto fair::mq::PluginManager::SearchPluginFile(const string& pluginName) const + -> boost::filesystem::path { - // Search plugin and load, if found - if (fPluginFactories.find(pluginName) == fPluginFactories.end()) { - auto success = false; - for (const auto& searchPath : SearchPaths()) { - try { - LoadSymbols(pluginName, searchPath / ToString(LibPrefix(), pluginName), - dll::load_mode::append_decorations | dll::load_mode::rtld_global); - fPluginOrder.push_back(pluginName); - success = true; - break; - } catch (boost::system::system_error& e) { - if (string{e.what()}.find("No such file or directory") == string::npos) { - throw PluginLoadError( - ToString("An error occurred while loading dynamic plugin ", - pluginName, ": ", e.what())); - } - } - } - - if (!success) { - 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); - } catch (boost::system::system_error& e) { - throw PluginLoadError( - ToString("An error occurred while loading dynamic plugin ", - pluginName, ": ", e.what())); + 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 = boost::filesystem::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 @@ -192,20 +240,25 @@ auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void try { if ("control" == pluginName) { try { - fPluginProgOptions.insert({pluginName, plugins::ControlPluginProgramOptions().value()}); + fPluginProgOptions.insert( + {pluginName, plugins::ControlPluginProgramOptions().value()}); + } catch (const boost::bad_optional_access&) { + /* just ignore, if no prog options are declared */ } - catch (const boost::bad_optional_access& e) { /* just ignore, if no prog options are declared */ } } else if ("config" == pluginName) { try { - fPluginProgOptions.insert({pluginName, plugins::ConfigPluginProgramOptions().value()}); + fPluginProgOptions.insert( + {pluginName, plugins::ConfigPluginProgramOptions().value()}); + } catch (const boost::bad_optional_access&) { + /* just ignore, if no prog options are declared */ } - catch (const boost::bad_optional_access& e) { /* 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())); + throw PluginLoadError(ToString( + "An error occurred while loading static plugin ", pluginName, ": ", e.what())); } } } @@ -225,11 +278,12 @@ auto fair::mq::PluginManager::InstantiatePlugin(const string& pluginName) -> voi auto fair::mq::PluginManager::InstantiatePlugins() -> void { - for(const auto& pluginName : fPluginOrder) { + for (const auto& pluginName : fPluginOrder) { try { InstantiatePlugin(pluginName); } catch (std::exception& e) { - throw PluginInstantiationError(ToString("An error occurred while instantiating plugin ", pluginName, ": ", e.what())); + throw PluginInstantiationError(ToString( + "An error occurred while instantiating plugin ", pluginName, ": ", e.what())); } } } diff --git a/fairmq/PluginManager.h b/fairmq/PluginManager.h index 40b9bf52..34710e5a 100644 --- a/fairmq/PluginManager.h +++ b/fairmq/PluginManager.h @@ -66,6 +66,8 @@ class PluginManager auto SearchPaths() const -> const std::vector& { return fSearchPaths; } struct BadSearchPath : std::invalid_argument { using std::invalid_argument::invalid_argument; }; + auto SearchPluginFile(const std::string&) const -> boost::filesystem::path; + struct PluginNotFound : std::runtime_error { using std::runtime_error::runtime_error; }; auto LoadPlugin(const std::string& pluginName) -> void; auto LoadPlugins(const std::vector& pluginNames) -> void { for(const auto& pluginName : pluginNames) { LoadPlugin(pluginName); } } struct PluginLoadError : std::runtime_error { using std::runtime_error::runtime_error; }; @@ -118,6 +120,7 @@ class PluginManager auto InstantiatePlugin(const std::string& pluginName) -> void; static const std::string fgkLibPrefix; + static const std::string fgkLibPrefixAlt; std::vector fSearchPaths; static std::vector fgDLLKeepAlive; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) std::map> fPluginFactories; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f23e4569..b4527ab2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -151,7 +151,7 @@ set(VERSION_MINOR 2) set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/helper/plugins/dummy2.h.in ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h) -add_testlib(FairMQPlugin_test_dummy2 +add_testlib(fairmq-plugin-test_dummy2 SOURCES ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h helper/plugins/dummy2.cxx @@ -171,7 +171,7 @@ add_testsuite(Plugins LINKS FairMQ INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 + DEPENDS FairMQPlugin_test_dummy fairmq-plugin-test_dummy2 TIMEOUT 20 ) @@ -180,7 +180,7 @@ add_testsuite(PluginsPrelinked ${CMAKE_CURRENT_BINARY_DIR}/runner.cxx plugins/_plugin_manager_prelink.cxx - LINKS FairMQ FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 + LINKS FairMQ FairMQPlugin_test_dummy fairmq-plugin-test_dummy2 INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} TIMEOUT 20