Ring Daemon 16.0.0
Loading...
Searching...
No Matches
pluginmanager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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
23namespace jami {
24
26{
27 pluginApi_.context = reinterpret_cast<void*>(this);
28}
29
31{
32 for (auto& func : exitFunc_) {
33 try {
34 func.second();
35 } catch (...) {
36 JAMI_ERR() << "Exception caught during plugin exit";
37 }
38 }
39
40 dynPluginMap_.clear();
41 exactMatchMap_.clear();
42 wildCardVec_.clear();
43 exitFunc_.clear();
44}
45
46bool
47PluginManager::load(const std::string& path)
48{
49 auto it = dynPluginMap_.find(path);
50 if (it != dynPluginMap_.end()) {
51 unload(path);
52 }
53
54 std::string error;
55 // Load plugin library
56 std::unique_ptr<Plugin> plugin(Plugin::load(path, error));
57 if (!plugin) {
58 JAMI_ERR() << "Plugin: " << error;
59 return false;
60 }
61
62 // Get init function from loaded library
63 const auto& init_func = plugin->getInitFunction();
64 if (!init_func) {
65 JAMI_ERR() << "Plugin: no init symbol" << error;
66 return false;
67 }
68
69 // Register plugin by running init function
70 if (!registerPlugin(plugin))
71 return false;
72
73 // Put Plugin loader into loaded plugins Map.
74 dynPluginMap_[path] = {std::move(plugin), true};
75 return true;
76}
77
78bool
79PluginManager::unload(const std::string& path)
80{
81 destroyPluginComponents(path);
82 auto it = dynPluginMap_.find(path);
83 if (it != dynPluginMap_.end()) {
84 std::lock_guard lk(mtx_);
85 exitFunc_[path]();
86 dynPluginMap_.erase(it);
87 exitFunc_.erase(path);
88 }
89
90 return true;
91}
92
93bool
95{
96 for (const auto& item : dynPluginMap_) {
97 if (item.first.find(rootPath) != std::string::npos && item.second.second)
98 return true;
99 }
100 return false;
101}
102
103std::vector<std::string>
105{
106 std::vector<std::string> res {};
107 for (const auto& pair : dynPluginMap_) {
108 if (pair.second.second)
109 res.push_back(pair.first);
110 }
111 return res;
112}
113
114void
115PluginManager::destroyPluginComponents(const std::string& path)
116{
117 auto itComponents = pluginComponentsMap_.find(path);
118 if (itComponents != pluginComponentsMap_.end()) {
119 for (auto pairIt = itComponents->second.begin(); pairIt != itComponents->second.end();) {
120 auto clcm = componentsLifeCycleManagers_.find(pairIt->first);
121 if (clcm != componentsLifeCycleManagers_.end()) {
122 clcm->second.destroyComponent(pairIt->second, mtx_);
123 pairIt = itComponents->second.erase(pairIt);
124 }
125 }
126 }
127}
128
129bool
130PluginManager::callPluginInitFunction(const std::string& path)
131{
132 bool returnValue {false};
133 auto it = dynPluginMap_.find(path);
134 if (it != dynPluginMap_.end()) {
135 // Since the Plugin was found it's of type DLPlugin with a valid init symbol
136 std::shared_ptr<DLPlugin> plugin = std::static_pointer_cast<DLPlugin>(it->second.first);
137 const auto& initFunc = plugin->getInitFunction();
139
140 try {
141 // Call Plugin Init function
142 exitFunc = initFunc(&plugin->api_);
143 } catch (const std::runtime_error& e) {
144 JAMI_ERR() << e.what();
145 return false;
146 }
147
148 if (!exitFunc) {
149 JAMI_ERR() << "Plugin: init failed";
150 // emit signal with error message to let user know that jamid was unable to load plugin
151 returnValue = false;
152 } else {
153 returnValue = true;
154 }
155 }
156
157 return returnValue;
158}
159
160bool
161PluginManager::registerPlugin(std::unique_ptr<Plugin>& plugin)
162{
163 // Here we already know that Plugin is of type DLPlugin with a valid init symbol
164 const auto& initFunc = plugin->getInitFunction();
165 DLPlugin* pluginPtr = static_cast<DLPlugin*>(plugin.get());
167
168 pluginPtr->apiContext_ = this;
170 pluginPtr->api_.registerObjectFactory = registerObjectFactory_;
171
172 // Implements JAMI_PluginAPI.invokeService().
173 // Must be C accessible.
174 pluginPtr->api_.invokeService = [](const JAMI_PluginAPI* api, const char* name, void* data) {
175 auto plugin = static_cast<DLPlugin*>(api->context);
176 auto manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
177 if (!manager) {
178 JAMI_ERR() << "invokeService called with null plugin API";
179 return -1;
180 }
181
182 return manager->invokeService(plugin, name, data);
183 };
184
185 // Implements JAMI_PluginAPI.manageComponents().
186 // Must be C accessible.
187 pluginPtr->api_.manageComponent = [](const JAMI_PluginAPI* api, const char* name, void* data) {
188 auto plugin = static_cast<DLPlugin*>(api->context);
189 if (!plugin) {
190 JAMI_ERR() << "createComponent called with null context";
191 return -1;
192 }
193 auto manager = reinterpret_cast<PluginManager*>(plugin->apiContext_);
194 if (!manager) {
195 JAMI_ERR() << "createComponent called with null plugin API";
196 return -1;
197 }
198 return manager->manageComponent(plugin, name, data);
199 };
200
201 try {
202 exitFunc = initFunc(&pluginPtr->api_);
203 } catch (const std::runtime_error& e) {
204 JAMI_ERR() << e.what();
205 }
206
207 if (!exitFunc) {
208 JAMI_ERR() << "Plugin: init failed";
209 return false;
210 }
211
212 exitFunc_[pluginPtr->getPath()] = exitFunc;
213 return true;
214}
215
216bool
217PluginManager::registerService(const std::string& name, ServiceFunction&& func)
218{
219 services_[name] = std::forward<ServiceFunction>(func);
220 return true;
221}
222
223void
224PluginManager::unRegisterService(const std::string& name)
225{
226 services_.erase(name);
227}
228
230PluginManager::invokeService(const DLPlugin* plugin, const std::string& name, void* data)
231{
232 // Search if desired service exists
233 const auto& iterFunc = services_.find(name);
234 if (iterFunc == services_.cend()) {
235 JAMI_ERR() << "Services not found: " << name;
236 return -1;
237 }
238
239 const auto& func = iterFunc->second;
240
241 try {
242 // Call service with data
243 return func(plugin, data);
244 } catch (const std::runtime_error& e) {
245 JAMI_ERR() << e.what();
246 return -1;
247 }
248}
249
251PluginManager::manageComponent(const DLPlugin* plugin, const std::string& name, void* data)
252{
253 const auto& iter = componentsLifeCycleManagers_.find(name);
254 if (iter == componentsLifeCycleManagers_.end()) {
255 JAMI_ERR() << "Component lifecycle manager not found: " << name;
256 return -1;
257 }
258
259 const auto& componentLifecycleManager = iter->second;
260
261 try {
262 int32_t r = componentLifecycleManager.takeComponentOwnership(data, mtx_);
263 if (r == 0) {
264 pluginComponentsMap_[plugin->getPath()].emplace_back(name, data);
265 }
266 return r;
267 } catch (const std::runtime_error& e) {
268 JAMI_ERR() << e.what();
269 return -1;
270 }
271}
272
273bool
275{
276 if (!type)
277 return false;
278
279 if (!factoryData.create || !factoryData.destroy)
280 return false;
281
282 // Strict compatibility on ABI
283 if (factoryData.version.abi != pluginApi_.version.abi)
284 return false;
285
286 // Backward compatibility on API
287 if (factoryData.version.api < pluginApi_.version.api)
288 return false;
289
290 const std::string key(type);
291 auto deleter = [factoryData](void* o) {
292 factoryData.destroy(o, factoryData.closure);
293 };
294 ObjectFactory factory = {factoryData, deleter};
295
296 // Wildcard registration
297 if (key == "*") {
298 wildCardVec_.push_back(factory);
299 return true;
300 }
301
302 // Fails on duplicate for exactMatch map
303 if (exactMatchMap_.find(key) != exactMatchMap_.end())
304 return false;
305
306 exactMatchMap_[key] = factory;
307 return true;
308}
309
310bool
312 ComponentFunction&& takeOwnership,
313 ComponentFunction&& destroyComponent)
314{
315 componentsLifeCycleManagers_[name] = {std::forward<ComponentFunction>(takeOwnership),
316 std::forward<ComponentFunction>(destroyComponent)};
317 return true;
318}
319
320std::unique_ptr<void, PluginManager::ObjectDeleter>
321PluginManager::createObject(const std::string& type)
322{
323 if (type == "*")
324 return {nullptr, nullptr};
325
327 /*.pluginApi = */ &pluginApi_,
328 /*.type = */ type.c_str(),
329 };
330
331 // Try to find an exact match
332 const auto& factoryIter = exactMatchMap_.find(type);
333 if (factoryIter != exactMatchMap_.end()) {
334 const auto& factory = factoryIter->second;
335 auto object = factory.data.create(&op, factory.data.closure);
336 if (object)
337 return {object, factory.deleter};
338 }
339
340 // Try to find a wildcard match
341 for (const auto& factory : wildCardVec_) {
342 auto object = factory.data.create(&op, factory.data.closure);
343 if (object) {
344 // Promote registration to exactMatch_
345 // (but keep also wildcard registration for other object types)
347 if (res < 0) {
348 JAMI_ERR() << "failed to register object " << op.type;
349 return {nullptr, nullptr};
350 }
351
352 return {object, factory.deleter};
353 }
354 }
355
356 return {nullptr, nullptr};
357}
358
360PluginManager::registerObjectFactory_(const JAMI_PluginAPI* api, const char* type, void* data)
361{
362 auto manager = reinterpret_cast<PluginManager*>(api->context);
363 if (!manager) {
364 JAMI_ERR() << "registerObjectFactory called with null plugin API";
365 return -1;
366 }
367
368 if (!data) {
369 JAMI_ERR() << "registerObjectFactory called with null factory data";
370 return -1;
371 }
372
373 const auto factory = reinterpret_cast<JAMI_PluginObjectFactory*>(data);
374 return manager->registerObjectFactory(type, *factory) ? 0 : -1;
375}
376} // namespace jami
This class is used after a plugin library is successfully loaded.
bool registerComponentManager(const std::string &name, ComponentFunction &&takeOwnership, ComponentFunction &&destroyComponent)
Registers a component manager that will have two functions, one to take ownership of the component an...
std::vector< std::string > getLoadedPlugins() const
Returns vector with loaded plugins' libraries paths.
bool unload(const std::string &path)
Unloads the plugin.
void unRegisterService(const std::string &name)
Unregister a service from the Plugin System.
bool checkLoadedPlugin(const std::string &rootPath) const
Returns True if plugin is loaded.
bool registerObjectFactory(const char *type, const JAMI_PluginObjectFactory &factory)
Function called from plugin implementation register a new object factory.
bool registerService(const std::string &name, ServiceFunction &&func)
Register a new service in the Plugin System.
bool load(const std::string &path)
Load a dynamic plugin by filename.
static Plugin * load(const std::string &path, std::string &error)
Load plugin's library.
#define JAMI_PLUGIN_ABI_VERSION
Definition jamiplugin.h:33
void(* JAMI_PluginExitFunc)(void)
Definition jamiplugin.h:104
#define JAMI_PLUGIN_API_VERSION
Definition jamiplugin.h:37
#define JAMI_ERR(...)
Definition logger.h:218
void emitSignal(Args... args)
Definition ring_signal.h:64
This structure is filled by the PluginManager.
Definition jamiplugin.h:93
JAMI_PluginVersion version
Definition jamiplugin.h:94
This structure is filled by plugin.
Definition jamiplugin.h:78
JAMI_PluginCreateFunc parameter.
Definition jamiplugin.h:62