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-04-26 09:41:19 Functions: 14 20 70.0 %

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

Generated by: LCOV version 1.14