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
|