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
|