LCOV - code coverage report
Current view: top level - src/plugin - pluginmanager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 97 204 47.5 %
Date: 2024-12-21 08:56:24 Functions: 14 20 70.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "pluginmanager.h"
      19             : #include "logger.h"
      20             : 
      21             : #include <utility>
      22             : 
      23             : namespace jami {
      24             : 
      25          33 : PluginManager::PluginManager()
      26             : {
      27          33 :     pluginApi_.context = reinterpret_cast<void*>(this);
      28          33 : }
      29             : 
      30          33 : PluginManager::~PluginManager()
      31             : {
      32          33 :     for (auto& func : exitFunc_) {
      33             :         try {
      34           0 :             func.second();
      35           0 :         } catch (...) {
      36           0 :             JAMI_ERR() << "Exception caught during plugin exit";
      37           0 :         }
      38             :     }
      39             : 
      40          33 :     dynPluginMap_.clear();
      41          33 :     exactMatchMap_.clear();
      42          33 :     wildCardVec_.clear();
      43          33 :     exitFunc_.clear();
      44          33 : }
      45             : 
      46             : bool
      47           8 : PluginManager::load(const std::string& path)
      48             : {
      49           8 :     auto it = dynPluginMap_.find(path);
      50           8 :     if (it != dynPluginMap_.end()) {
      51           1 :         unload(path);
      52             :     }
      53             : 
      54           8 :     std::string error;
      55             :     // Load plugin library
      56           8 :     std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
      57           8 :     if (!plugin) {
      58           0 :         JAMI_ERR() << "Plugin: " << error;
      59           0 :         return false;
      60             :     }
      61             : 
      62             :     // Get init function from loaded library
      63           8 :     const auto& init_func = plugin->getInitFunction();
      64           8 :     if (!init_func) {
      65           0 :         JAMI_ERR() << "Plugin: no init symbol" << error;
      66           0 :         return false;
      67             :     }
      68             : 
      69             :     // Register plugin by running init function
      70           8 :     if (!registerPlugin(plugin))
      71           0 :         return false;
      72             : 
      73             :     // Put Plugin loader into loaded plugins Map.
      74           8 :     dynPluginMap_[path] = {std::move(plugin), true};
      75           8 :     return true;
      76           8 : }
      77             : 
      78             : bool
      79           8 : PluginManager::unload(const std::string& path)
      80             : {
      81           8 :     destroyPluginComponents(path);
      82           8 :     auto it = dynPluginMap_.find(path);
      83           8 :     if (it != dynPluginMap_.end()) {
      84           8 :         std::lock_guard lk(mtx_);
      85           8 :         exitFunc_[path]();
      86           8 :         dynPluginMap_.erase(it);
      87           8 :         exitFunc_.erase(path);
      88           8 :     }
      89             : 
      90           8 :     return true;
      91             : }
      92             : 
      93             : bool
      94          12 : PluginManager::checkLoadedPlugin(const std::string& rootPath) const
      95             : {
      96          12 :     for (const auto& item : dynPluginMap_) {
      97           5 :         if (item.first.find(rootPath) != std::string::npos && item.second.second)
      98           5 :             return true;
      99             :     }
     100           7 :     return false;
     101             : }
     102             : 
     103             : std::vector<std::string>
     104           4 : PluginManager::getLoadedPlugins() const
     105             : {
     106           4 :     std::vector<std::string> res {};
     107           6 :     for (const auto& pair : dynPluginMap_) {
     108           2 :         if (pair.second.second)
     109           2 :             res.push_back(pair.first);
     110             :     }
     111           4 :     return res;
     112           0 : }
     113             : 
     114             : void
     115           8 : PluginManager::destroyPluginComponents(const std::string& path)
     116             : {
     117           8 :     auto itComponents = pluginComponentsMap_.find(path);
     118           8 :     if (itComponents != pluginComponentsMap_.end()) {
     119          32 :         for (auto pairIt = itComponents->second.begin(); pairIt != itComponents->second.end();) {
     120          24 :             auto clcm = componentsLifeCycleManagers_.find(pairIt->first);
     121          24 :             if (clcm != componentsLifeCycleManagers_.end()) {
     122          24 :                 clcm->second.destroyComponent(pairIt->second, mtx_);
     123          24 :                 pairIt = itComponents->second.erase(pairIt);
     124             :             }
     125             :         }
     126             :     }
     127           8 : }
     128             : 
     129             : bool
     130           0 : PluginManager::callPluginInitFunction(const std::string& path)
     131             : {
     132           0 :     bool returnValue {false};
     133           0 :     auto it = dynPluginMap_.find(path);
     134           0 :     if (it != dynPluginMap_.end()) {
     135             :         // Since the Plugin was found it's of type DLPlugin with a valid init symbol
     136           0 :         std::shared_ptr<DLPlugin> plugin = std::static_pointer_cast<DLPlugin>(it->second.first);
     137           0 :         const auto& initFunc = plugin->getInitFunction();
     138           0 :         JAMI_PluginExitFunc exitFunc = nullptr;
     139             : 
     140             :         try {
     141             :             // Call Plugin Init function
     142           0 :             exitFunc = initFunc(&plugin->api_);
     143           0 :         } catch (const std::runtime_error& e) {
     144           0 :             JAMI_ERR() << e.what();
     145           0 :             return false;
     146           0 :         }
     147             : 
     148           0 :         if (!exitFunc) {
     149           0 :             JAMI_ERR() << "Plugin: init failed";
     150             :             // emit signal with error message to let user know that jamid was unable to load plugin
     151           0 :             returnValue = false;
     152             :         } else {
     153           0 :             returnValue = true;
     154             :         }
     155           0 :     }
     156             : 
     157           0 :     return returnValue;
     158             : }
     159             : 
     160             : bool
     161           8 : PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin)
     162             : {
     163             :     // Here we already know that Plugin is of type DLPlugin with a valid init symbol
     164           8 :     const auto& initFunc = plugin->getInitFunction();
     165           8 :     DLPlugin* pluginPtr = static_cast<DLPlugin*>(plugin.get());
     166           8 :     JAMI_PluginExitFunc exitFunc = nullptr;
     167             : 
     168           8 :     pluginPtr->apiContext_ = this;
     169           8 :     pluginPtr->api_.version = {JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION};
     170           8 :     pluginPtr->api_.registerObjectFactory = registerObjectFactory_;
     171             : 
     172             :     // Implements JAMI_PluginAPI.invokeService().
     173             :     // Must be C accessible.
     174          16 :     pluginPtr->api_.invokeService = [](const JAMI_PluginAPI* api, const char* name, void* data) {
     175           8 :         auto plugin = static_cast<DLPlugin*>(api->context);
     176           8 :         auto manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
     177           8 :         if (!manager) {
     178           0 :             JAMI_ERR() << "invokeService called with null plugin API";
     179           0 :             return -1;
     180             :         }
     181             : 
     182           8 :         return manager->invokeService(plugin, name, data);
     183           8 :     };
     184             : 
     185             :     // Implements JAMI_PluginAPI.manageComponents().
     186             :     // Must be C accessible.
     187          32 :     pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void* data) {
     188          24 :         auto plugin = static_cast<DLPlugin*>(api->context);
     189          24 :         if (!plugin) {
     190           0 :             JAMI_ERR() << "createComponent called with null context";
     191           0 :             return -1;
     192             :         }
     193          24 :         auto manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
     194          24 :         if (!manager) {
     195           0 :             JAMI_ERR() << "createComponent called with null plugin API";
     196           0 :             return -1;
     197             :         }
     198          24 :         return manager->manageComponent(plugin, name, data);
     199           8 :     };
     200             : 
     201             :     try {
     202           8 :         exitFunc = initFunc(&pluginPtr->api_);
     203           0 :     } catch (const std::runtime_error& e) {
     204           0 :         JAMI_ERR() << e.what();
     205           0 :     }
     206             : 
     207           8 :     if (!exitFunc) {
     208           0 :         JAMI_ERR() << "Plugin: init failed";
     209           0 :         return false;
     210             :     }
     211             : 
     212           8 :     exitFunc_[pluginPtr->getPath()] = exitFunc;
     213           8 :     return true;
     214             : }
     215             : 
     216             : bool
     217         165 : PluginManager::registerService(const std::string& name, ServiceFunction&& func)
     218             : {
     219         165 :     services_[name] = std::forward<ServiceFunction>(func);
     220         165 :     return true;
     221             : }
     222             : 
     223             : void
     224           0 : PluginManager::unRegisterService(const std::string& name)
     225             : {
     226           0 :     services_.erase(name);
     227           0 : }
     228             : 
     229             : int32_t
     230           8 : PluginManager::invokeService(const DLPlugin* plugin, const std::string& name, void* data)
     231             : {
     232             :     // Search if desired service exists
     233           8 :     const auto& iterFunc = services_.find(name);
     234           8 :     if (iterFunc == services_.cend()) {
     235           0 :         JAMI_ERR() << "Services not found: " << name;
     236           0 :         return -1;
     237             :     }
     238             : 
     239           8 :     const auto& func = iterFunc->second;
     240             : 
     241             :     try {
     242             :         // Call service with data
     243           8 :         return func(plugin, data);
     244           0 :     } catch (const std::runtime_error& e) {
     245           0 :         JAMI_ERR() << e.what();
     246           0 :         return -1;
     247           0 :     }
     248             : }
     249             : 
     250             : int32_t
     251          24 : PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name, void* data)
     252             : {
     253          24 :     const auto& iter = componentsLifeCycleManagers_.find(name);
     254          24 :     if (iter == componentsLifeCycleManagers_.end()) {
     255           0 :         JAMI_ERR() << "Component lifecycle manager not found: " << name;
     256           0 :         return -1;
     257             :     }
     258             : 
     259          24 :     const auto& componentLifecycleManager = iter->second;
     260             : 
     261             :     try {
     262          24 :         int32_t r = componentLifecycleManager.takeComponentOwnership(data, mtx_);
     263          24 :         if (r == 0) {
     264          24 :             pluginComponentsMap_[plugin->getPath()].emplace_back(name, data);
     265             :         }
     266          24 :         return r;
     267           0 :     } catch (const std::runtime_error& e) {
     268           0 :         JAMI_ERR() << e.what();
     269           0 :         return -1;
     270           0 :     }
     271             : }
     272             : 
     273             : bool
     274           0 : PluginManager::registerObjectFactory(const char* type, const JAMI_PluginObjectFactory& factoryData)
     275             : {
     276           0 :     if (!type)
     277           0 :         return false;
     278             : 
     279           0 :     if (!factoryData.create || !factoryData.destroy)
     280           0 :         return false;
     281             : 
     282             :     // Strict compatibility on ABI
     283           0 :     if (factoryData.version.abi != pluginApi_.version.abi)
     284           0 :         return false;
     285             : 
     286             :     // Backward compatibility on API
     287           0 :     if (factoryData.version.api < pluginApi_.version.api)
     288           0 :         return false;
     289             : 
     290           0 :     const std::string key(type);
     291           0 :     auto deleter = [factoryData](void* o) {
     292           0 :         factoryData.destroy(o, factoryData.closure);
     293           0 :     };
     294           0 :     ObjectFactory factory = {factoryData, deleter};
     295             : 
     296             :     // Wildcard registration
     297           0 :     if (key == "*") {
     298           0 :         wildCardVec_.push_back(factory);
     299           0 :         return true;
     300             :     }
     301             : 
     302             :     // Fails on duplicate for exactMatch map
     303           0 :     if (exactMatchMap_.find(key) != exactMatchMap_.end())
     304           0 :         return false;
     305             : 
     306           0 :     exactMatchMap_[key] = factory;
     307           0 :     return true;
     308           0 : }
     309             : 
     310             : bool
     311         132 : PluginManager::registerComponentManager(const std::string& name,
     312             :                                         ComponentFunction&& takeOwnership,
     313             :                                         ComponentFunction&& destroyComponent)
     314             : {
     315         132 :     componentsLifeCycleManagers_[name] = {std::forward<ComponentFunction>(takeOwnership),
     316         264 :                                           std::forward<ComponentFunction>(destroyComponent)};
     317         132 :     return true;
     318             : }
     319             : 
     320             : std::unique_ptr<void, PluginManager::ObjectDeleter>
     321           0 : PluginManager::createObject(const std::string& type)
     322             : {
     323           0 :     if (type == "*")
     324           0 :         return {nullptr, nullptr};
     325             : 
     326             :     JAMI_PluginObjectParams op = {
     327           0 :         /*.pluginApi = */ &pluginApi_,
     328           0 :         /*.type = */ type.c_str(),
     329           0 :     };
     330             : 
     331             :     // Try to find an exact match
     332           0 :     const auto& factoryIter = exactMatchMap_.find(type);
     333           0 :     if (factoryIter != exactMatchMap_.end()) {
     334           0 :         const auto& factory = factoryIter->second;
     335           0 :         auto object = factory.data.create(&op, factory.data.closure);
     336           0 :         if (object)
     337           0 :             return {object, factory.deleter};
     338             :     }
     339             : 
     340             :     // Try to find a wildcard match
     341           0 :     for (const auto& factory : wildCardVec_) {
     342           0 :         auto object = factory.data.create(&op, factory.data.closure);
     343           0 :         if (object) {
     344             :             // Promote registration to exactMatch_
     345             :             // (but keep also wildcard registration for other object types)
     346           0 :             int32_t res = registerObjectFactory(op.type, factory.data);
     347           0 :             if (res < 0) {
     348           0 :                 JAMI_ERR() << "failed to register object " << op.type;
     349           0 :                 return {nullptr, nullptr};
     350             :             }
     351             : 
     352           0 :             return {object, factory.deleter};
     353             :         }
     354             :     }
     355             : 
     356           0 :     return {nullptr, nullptr};
     357             : }
     358             : 
     359             : int32_t
     360           0 : PluginManager::registerObjectFactory_(const JAMI_PluginAPI* api, const char* type, void* data)
     361             : {
     362           0 :     auto manager = reinterpret_cast<PluginManager*>(api->context);
     363           0 :     if (!manager) {
     364           0 :         JAMI_ERR() << "registerObjectFactory called with null plugin API";
     365           0 :         return -1;
     366             :     }
     367             : 
     368           0 :     if (!data) {
     369           0 :         JAMI_ERR() << "registerObjectFactory called with null factory data";
     370           0 :         return -1;
     371             :     }
     372             : 
     373           0 :     const auto factory = reinterpret_cast<JAMI_PluginObjectFactory*>(data);
     374           0 :     return manager->registerObjectFactory(type, *factory) ? 0 : -1;
     375             : }
     376             : } // namespace jami

Generated by: LCOV version 1.14