LCOV - code coverage report
Current view: top level - src/plugin - pluginmanager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 8.9 % 203 18
Test Date: 2026-06-13 09:18:46 Functions: 7.4 % 54 4

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 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           32 : PluginManager::PluginManager()
      26              : {
      27           32 :     pluginApi_.context = reinterpret_cast<void*>(this);
      28           32 : }
      29              : 
      30           32 : PluginManager::~PluginManager()
      31              : {
      32           32 :     for (auto& func : exitFunc_) {
      33              :         try {
      34            0 :             func.second();
      35            0 :         } catch (...) {
      36            0 :             JAMI_ERROR("Exception caught during plugin exit");
      37            0 :         }
      38              :     }
      39              : 
      40           32 :     dynPluginMap_.clear();
      41           32 :     exactMatchMap_.clear();
      42           32 :     wildCardVec_.clear();
      43           32 :     exitFunc_.clear();
      44           32 : }
      45              : 
      46              : bool
      47            0 : PluginManager::load(const std::string& path)
      48              : {
      49            0 :     auto it = dynPluginMap_.find(path);
      50            0 :     if (it != dynPluginMap_.end()) {
      51            0 :         unload(path);
      52              :     }
      53              : 
      54            0 :     std::string error;
      55              :     // Load plugin library
      56            0 :     std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
      57            0 :     if (!plugin) {
      58            0 :         JAMI_ERROR("Plugin: {}", error);
      59            0 :         return false;
      60              :     }
      61              : 
      62              :     // Get init function from loaded library
      63            0 :     const auto& init_func = plugin->getInitFunction();
      64            0 :     if (!init_func) {
      65            0 :         JAMI_ERROR("Plugin: no init symbol{}", error);
      66            0 :         return false;
      67              :     }
      68              : 
      69              :     // Register plugin by running init function
      70            0 :     if (!registerPlugin(plugin))
      71            0 :         return false;
      72              : 
      73              :     // Put Plugin loader into loaded plugins Map.
      74            0 :     dynPluginMap_[path] = {std::move(plugin), true};
      75            0 :     return true;
      76            0 : }
      77              : 
      78              : bool
      79            0 : PluginManager::unload(const std::string& path)
      80              : {
      81            0 :     destroyPluginComponents(path);
      82            0 :     auto it = dynPluginMap_.find(path);
      83            0 :     if (it != dynPluginMap_.end()) {
      84            0 :         std::lock_guard lk(mtx_);
      85            0 :         exitFunc_[path]();
      86            0 :         dynPluginMap_.erase(it);
      87            0 :         exitFunc_.erase(path);
      88            0 :     }
      89              : 
      90            0 :     return true;
      91              : }
      92              : 
      93              : bool
      94            0 : PluginManager::checkLoadedPlugin(const std::string& rootPath) const
      95              : {
      96            0 :     for (const auto& item : dynPluginMap_) {
      97            0 :         if (item.first.find(rootPath) != std::string::npos && item.second.second)
      98            0 :             return true;
      99              :     }
     100            0 :     return false;
     101              : }
     102              : 
     103              : std::vector<std::string>
     104            0 : PluginManager::getLoadedPlugins() const
     105              : {
     106            0 :     std::vector<std::string> res {};
     107            0 :     for (const auto& pair : dynPluginMap_) {
     108            0 :         if (pair.second.second)
     109            0 :             res.push_back(pair.first);
     110              :     }
     111            0 :     return res;
     112            0 : }
     113              : 
     114              : void
     115            0 : PluginManager::destroyPluginComponents(const std::string& path)
     116              : {
     117            0 :     auto itComponents = pluginComponentsMap_.find(path);
     118            0 :     if (itComponents != pluginComponentsMap_.end()) {
     119            0 :         for (auto pairIt = itComponents->second.begin(); pairIt != itComponents->second.end();) {
     120            0 :             auto clcm = componentsLifeCycleManagers_.find(pairIt->first);
     121            0 :             if (clcm != componentsLifeCycleManagers_.end()) {
     122            0 :                 clcm->second.destroyComponent(pairIt->second, mtx_);
     123            0 :                 pairIt = itComponents->second.erase(pairIt);
     124              :             }
     125              :         }
     126              :     }
     127            0 : }
     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_ERROR("{}", e.what());
     145            0 :             return false;
     146            0 :         }
     147              : 
     148            0 :         if (!exitFunc) {
     149            0 :             JAMI_ERROR("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            0 : 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            0 :     const auto& initFunc = plugin->getInitFunction();
     165            0 :     DLPlugin* pluginPtr = static_cast<DLPlugin*>(plugin.get());
     166            0 :     JAMI_PluginExitFunc exitFunc = nullptr;
     167              : 
     168            0 :     pluginPtr->apiContext_ = this;
     169            0 :     pluginPtr->api_.version = {JAMI_PLUGIN_ABI_VERSION, JAMI_PLUGIN_API_VERSION};
     170            0 :     pluginPtr->api_.registerObjectFactory = registerObjectFactory_;
     171              : 
     172              :     // Implements JAMI_PluginAPI.invokeService().
     173              :     // Must be C accessible.
     174            0 :     pluginPtr->api_.invokeService = [](const JAMI_PluginAPI* api, const char* name, void* data) {
     175            0 :         auto* plugin = static_cast<DLPlugin*>(api->context);
     176            0 :         auto* manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
     177            0 :         if (!manager) {
     178            0 :             JAMI_ERROR("invokeService called with null plugin API");
     179            0 :             return -1;
     180              :         }
     181              : 
     182            0 :         return manager->invokeService(plugin, name, data);
     183              :     };
     184              : 
     185              :     // Implements JAMI_PluginAPI.manageComponents().
     186              :     // Must be C accessible.
     187            0 :     pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void* data) {
     188            0 :         auto* plugin = static_cast<DLPlugin*>(api->context);
     189            0 :         if (!plugin) {
     190            0 :             JAMI_ERROR("createComponent called with null context");
     191            0 :             return -1;
     192              :         }
     193            0 :         auto* manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
     194            0 :         if (!manager) {
     195            0 :             JAMI_ERROR("createComponent called with null plugin API");
     196            0 :             return -1;
     197              :         }
     198            0 :         return manager->manageComponent(plugin, name, data);
     199              :     };
     200              : 
     201              :     try {
     202            0 :         exitFunc = initFunc(&pluginPtr->api_);
     203            0 :     } catch (const std::runtime_error& e) {
     204            0 :         JAMI_ERROR("{}", e.what());
     205            0 :     }
     206              : 
     207            0 :     if (!exitFunc) {
     208            0 :         JAMI_ERROR("Plugin: init failed");
     209            0 :         return false;
     210              :     }
     211              : 
     212            0 :     exitFunc_[pluginPtr->getPath()] = exitFunc;
     213            0 :     return true;
     214              : }
     215              : 
     216              : bool
     217          160 : PluginManager::registerService(const std::string& name, ServiceFunction&& func)
     218              : {
     219          160 :     services_[name] = std::forward<ServiceFunction>(func);
     220          160 :     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            0 : PluginManager::invokeService(const DLPlugin* plugin, const std::string& name, void* data)
     231              : {
     232              :     // Search if desired service exists
     233            0 :     const auto& iterFunc = services_.find(name);
     234            0 :     if (iterFunc == services_.cend()) {
     235            0 :         JAMI_ERROR("Services not found: {}", name);
     236            0 :         return -1;
     237              :     }
     238              : 
     239            0 :     const auto& func = iterFunc->second;
     240              : 
     241              :     try {
     242              :         // Call service with data
     243            0 :         return func(plugin, data);
     244            0 :     } catch (const std::runtime_error& e) {
     245            0 :         JAMI_ERROR("{}", e.what());
     246            0 :         return -1;
     247            0 :     }
     248              : }
     249              : 
     250              : int32_t
     251            0 : PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name, void* data)
     252              : {
     253            0 :     const auto& iter = componentsLifeCycleManagers_.find(name);
     254            0 :     if (iter == componentsLifeCycleManagers_.end()) {
     255            0 :         JAMI_ERROR("Component lifecycle manager not found: {}", name);
     256            0 :         return -1;
     257              :     }
     258              : 
     259            0 :     const auto& componentLifecycleManager = iter->second;
     260              : 
     261              :     try {
     262            0 :         int32_t r = componentLifecycleManager.takeComponentOwnership(data, mtx_);
     263            0 :         if (r == 0) {
     264            0 :             pluginComponentsMap_[plugin->getPath()].emplace_back(name, data);
     265              :         }
     266            0 :         return r;
     267            0 :     } catch (const std::runtime_error& e) {
     268            0 :         JAMI_ERROR("{}", 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          128 : PluginManager::registerComponentManager(const std::string& name,
     312              :                                         ComponentFunction&& takeOwnership,
     313              :                                         ComponentFunction&& destroyComponent)
     314              : {
     315          256 :     componentsLifeCycleManagers_[name] = {std::forward<ComponentFunction>(takeOwnership),
     316          128 :                                           std::forward<ComponentFunction>(destroyComponent)};
     317          128 :     return true;
     318          128 : }
     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_ERROR("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_ERROR("registerObjectFactory called with null plugin API");
     365            0 :         return -1;
     366              :     }
     367              : 
     368            0 :     if (!data) {
     369            0 :         JAMI_ERROR("registerObjectFactory called with null factory data");
     370            0 :         return -1;
     371              :     }
     372              : 
     373            0 :     auto* const factory = reinterpret_cast<JAMI_PluginObjectFactory*>(data);
     374            0 :     return manager->registerObjectFactory(type, *factory) ? 0 : -1;
     375              : }
     376              : } // namespace jami
        

Generated by: LCOV version 2.0-1