Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Implementation of PluginDatabase(s)
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include <plugindatabase.hpp>
11 :
12 : #include <modules.hpp>
13 :
14 : #include <set>
15 :
16 : #include <algorithm>
17 : #include <helper/keyhelper.hpp>
18 : #include <kdbconfig.h>
19 : #include <kdblogger.h>
20 : #include <kdbmacros.h>
21 :
22 : #ifdef HAVE_GLOB
23 : #include <glob.h>
24 : #endif
25 :
26 : namespace kdb
27 : {
28 :
29 : namespace tools
30 : {
31 :
32 : class ModulesPluginDatabase::Impl
33 : {
34 : public:
35 : Impl ()
36 442 : {
37 : }
38 : ~Impl ()
39 442 : {
40 : }
41 : Modules modules;
42 : };
43 :
44 1326 : ModulesPluginDatabase::ModulesPluginDatabase () : impl (new ModulesPluginDatabase::Impl ())
45 : {
46 442 : }
47 :
48 442 : ModulesPluginDatabase::~ModulesPluginDatabase ()
49 : {
50 442 : }
51 :
52 28 : std::vector<std::string> ModulesPluginDatabase::listAllPlugins () const
53 : {
54 28 : std::vector<std::string> ret;
55 : #ifdef ELEKTRA_SHARED
56 : #ifdef HAVE_GLOB
57 : std::set<std::string> toIgnore = {
58 : "proposal", "core", "ease", "meta", "plugin", "full", "kdb", "static",
59 588 : };
60 : glob_t pglob;
61 28 : if (glob (BUILTIN_PLUGIN_FOLDER "/libelektra-*", GLOB_NOSORT, NULL, &pglob) == 0)
62 : {
63 : ELEKTRA_LOG ("has glob %zd", pglob.gl_pathc);
64 0 : for (size_t i = 0; i < pglob.gl_pathc; ++i)
65 : {
66 0 : std::string fn (pglob.gl_pathv[i]);
67 0 : size_t start = fn.find_last_of ('-');
68 0 : if (start == std::string::npos) continue; // ignore wrong file
69 0 : std::string name = fn.substr (start + 1);
70 0 : size_t end = name.find_first_of ('.');
71 0 : name = name.substr (0, end);
72 0 : if (end == std::string::npos) continue; // ignore wrong file
73 0 : if (toIgnore.find (name) != toIgnore.end ()) continue; // ignore
74 0 : ret.push_back (name);
75 : }
76 0 : globfree (&pglob);
77 : }
78 : #endif
79 28 : if (!ret.empty ())
80 : {
81 0 : std::sort (ret.begin (), ret.end ());
82 : return ret;
83 : }
84 : // if we did not find plugins, return buildinPlugins
85 : // (even if they might be wrong for ELEKTRA_SHARED)
86 : #endif
87 84 : std::string buildinPlugins = ELEKTRA_PLUGINS;
88 56 : std::istringstream ss (buildinPlugins);
89 28 : std::string plugin;
90 6048 : while (getline (ss, plugin, ';'))
91 : {
92 2996 : ret.push_back (plugin);
93 : }
94 : // remove duplicates:
95 56 : std::sort (ret.begin (), ret.end ());
96 196 : ret.erase (std::unique (ret.begin (), ret.end ()), ret.end ());
97 : return ret;
98 : }
99 :
100 :
101 : namespace
102 : {
103 :
104 54 : bool hasProvides (PluginDatabase const & pd, std::string which)
105 : {
106 108 : std::vector<std::string> allPlugins = pd.listAllPlugins ();
107 :
108 278 : for (auto const & plugin : allPlugins)
109 : {
110 696 : std::istringstream ss (pd.lookupInfo (
111 348 : PluginSpec (
112 : plugin,
113 464 : KeySet (5, *Key ("system/module", KEY_VALUE, "this plugin was loaded without a config", KEY_END), KS_END)),
114 410 : "provides"));
115 62 : std::string provide;
116 284 : while (ss >> provide)
117 : {
118 80 : if (provide == which)
119 : {
120 108 : return true;
121 : }
122 : }
123 : }
124 : return false;
125 : }
126 : } // namespace
127 :
128 :
129 : // TODO: directly use data from CONTRACT.ini
130 127260 : const std::map<std::string, int> PluginDatabase::statusMap = {
131 : // clang-format off
132 : {"default", 64000},
133 : {"recommended", 32000},
134 : {"productive", 8000},
135 : {"maintained", 4000},
136 : {"reviewed", 4000},
137 : {"conformant", 2000},
138 : {"compatible", 2000},
139 : {"coverage", 2000},
140 : {"specific", 1000},
141 :
142 : {"unittest", 1000},
143 : {"shelltest", 1000},
144 : {"tested", 500},
145 : {"nodep", 250},
146 : {"libc", 250},
147 : {"configurable", 50},
148 : {"final", 50},
149 : {"global", 1},
150 : {"readonly", 0},
151 : {"writeonly", 0},
152 : {"preview", -50},
153 : {"memleak", -250},
154 : {"experimental", -500},
155 : {"difficult", -500},
156 : {"limited", -750},
157 : {"unfinished", -1000},
158 : {"old", -1000},
159 : {"nodoc", -1000},
160 : {"concept", -2000},
161 : {"orphan", -4000},
162 : {"obsolete", -4000},
163 : {"discouraged", -32000},
164 :
165 : // clang-format on
166 : };
167 :
168 :
169 111 : int PluginDatabase::calculateStatus (std::string statusString)
170 : {
171 111 : int ret = 0;
172 222 : std::istringstream ss (statusString);
173 111 : std::string status;
174 558 : while (ss >> status)
175 : {
176 168 : auto it = statusMap.find (status);
177 168 : if (it != statusMap.end ())
178 : {
179 136 : ret += it->second;
180 : }
181 : else
182 : {
183 : try
184 : {
185 18 : ret += stoi (status);
186 : }
187 14 : catch (std::invalid_argument const &)
188 : {
189 : }
190 : }
191 : }
192 222 : return ret;
193 : }
194 :
195 1053 : PluginDatabase::Status ModulesPluginDatabase::status (PluginSpec const & spec) const
196 : {
197 2106 : PluginPtr plugin;
198 : try
199 : {
200 2106 : KeySet conf = spec.getConfig ();
201 3159 : conf.append (Key ("system/module", KEY_VALUE, "this plugin was loaded for the status", KEY_END));
202 3159 : plugin = impl->modules.load (spec.getName (), conf);
203 1053 : return real;
204 : }
205 0 : catch (...)
206 : {
207 0 : if (hasProvides (*this, spec.getName ()))
208 : {
209 : return provides;
210 : }
211 : else
212 : {
213 0 : return missing;
214 : }
215 : }
216 : }
217 :
218 9358 : std::string ModulesPluginDatabase::lookupInfo (PluginSpec const & spec, std::string const & which) const
219 : {
220 46790 : PluginPtr plugin = impl->modules.load (spec.getName (), spec.getConfig ());
221 74864 : return plugin->lookupInfo (which);
222 : }
223 :
224 1048 : PluginDatabase::func_t ModulesPluginDatabase::getSymbol (PluginSpec const & spec, std::string const & which) const
225 : {
226 : try
227 : {
228 5240 : PluginPtr plugin = impl->modules.load (spec.getName (), spec.getConfig ());
229 3144 : return plugin->getSymbol (which);
230 : }
231 1030 : catch (...)
232 : {
233 : return NULL;
234 : }
235 : }
236 :
237 25 : PluginSpec ModulesPluginDatabase::lookupMetadata (std::string const & which) const
238 : {
239 50 : std::vector<std::string> allPlugins = listAllPlugins ();
240 50 : std::map<int, PluginSpec> foundPlugins;
241 :
242 50 : std::string errors;
243 : // collect possible plugins
244 1097 : for (auto const & plugin : allPlugins)
245 : {
246 : try
247 : {
248 : // TODO remove /module hack
249 5982 : std::istringstream ss (lookupInfo (
250 2991 : PluginSpec (plugin,
251 1994 : KeySet (5,
252 1994 : *Key ("system/module", KEY_VALUE, "this plugin was loaded without a config", KEY_END),
253 : KS_END)),
254 3988 : "metadata"));
255 997 : std::string metadata;
256 3352 : while (ss >> metadata)
257 : {
258 714 : if (metadata == which)
259 : {
260 210 : int s = calculateStatus (lookupInfo (
261 175 : PluginSpec (plugin, KeySet (5,
262 70 : *Key ("system/module", KEY_VALUE,
263 : "this plugin was loaded without a config", KEY_END),
264 : KS_END)),
265 70 : "status"));
266 245 : foundPlugins.insert (std::make_pair (s, PluginSpec (plugin)));
267 : break;
268 : }
269 : }
270 : }
271 0 : catch (std::exception const & e)
272 : {
273 0 : errors += e.what ();
274 0 : errors += ",";
275 : } // assume not loaded
276 : }
277 :
278 25 : if (foundPlugins.empty ())
279 : {
280 0 : if (!errors.empty ())
281 0 : throw NoPlugin ("No plugin that provides " + which + " could be found, got errors: " + errors);
282 : else
283 0 : throw NoPlugin ("No plugin that provides " + which + " could be found");
284 : }
285 :
286 : // the largest element of the map contains the best-suited plugin:
287 75 : return foundPlugins.rbegin ()->second;
288 : }
289 :
290 1207 : PluginSpec ModulesPluginDatabase::lookupProvides (std::string const & which) const
291 : {
292 : // check if plugin with provider name exists:
293 6035 : if (status (PluginSpec (which)) == real)
294 : {
295 4620 : return PluginSpec (which);
296 : }
297 :
298 52 : std::map<int, PluginSpec> foundPlugins;
299 : try
300 : {
301 156 : foundPlugins = lookupAllProvidesWithStatus (which);
302 : }
303 0 : catch (kdb::tools::NoPlugin const & e)
304 : {
305 0 : throw;
306 : }
307 :
308 : // the largest element of the map contains the best-suited plugin:
309 104 : return foundPlugins.rbegin ()->second;
310 : }
311 :
312 54 : std::map<int, PluginSpec> ModulesPluginDatabase::lookupAllProvidesWithStatus (std::string const & which) const
313 : {
314 108 : std::string errors;
315 108 : std::vector<std::string> allPlugins = listAllPlugins ();
316 54 : std::map<int, PluginSpec> foundPlugins;
317 600 : for (auto const & plugin : allPlugins)
318 : {
319 : // TODO: make sure (non)-equal plugins (i.e. with same/different contract) are handled correctly
320 : try
321 : {
322 : PluginSpec spec = PluginSpec (
323 : plugin,
324 2686 : KeySet (5, *Key ("system/module", KEY_VALUE, "this plugin was loaded without a config", KEY_END), KS_END));
325 :
326 : // lets see if there is a plugin named after the required provider
327 384 : if (plugin == which)
328 : {
329 10 : int s = calculateStatus (lookupInfo (spec, "status"));
330 14 : foundPlugins.insert (std::make_pair (s, PluginSpec (plugin)));
331 : continue; // we are done with this plugin
332 : }
333 :
334 : // TODO: support for generic plugins with config
335 2292 : std::istringstream ss (lookupInfo (spec, "provides"));
336 382 : std::string provide;
337 1576 : while (ss >> provide)
338 : {
339 406 : if (provide == which)
340 : {
341 330 : int s = calculateStatus (lookupInfo (spec, "status"));
342 462 : foundPlugins.insert (std::make_pair (s, PluginSpec (plugin)));
343 : }
344 : }
345 : }
346 0 : catch (std::exception const & e)
347 : {
348 0 : errors += e.what ();
349 0 : errors += ",";
350 : } // assume not loaded
351 : }
352 :
353 54 : if (foundPlugins.empty ())
354 : {
355 0 : if (!errors.empty ())
356 0 : throw NoPlugin ("No plugin that provides " + which + " could be found, got errors: " + errors);
357 : else
358 0 : throw NoPlugin ("No plugin that provides " + which + " could be found");
359 : }
360 :
361 54 : return foundPlugins;
362 : }
363 :
364 1 : std::vector<PluginSpec> ModulesPluginDatabase::lookupAllProvides (std::string const & which) const
365 : {
366 : try
367 : {
368 2 : const std::map<int, PluginSpec> foundPlugins = lookupAllProvidesWithStatus (which);
369 :
370 : // we found some plugins, lets convert the map into a vector
371 2 : std::vector<PluginSpec> plugins;
372 1 : plugins.reserve (foundPlugins.size ());
373 : std::for_each (foundPlugins.begin (), foundPlugins.end (),
374 6 : [&plugins](const std::map<int, PluginSpec>::value_type & elem) { plugins.push_back (elem.second); });
375 1 : return plugins;
376 : }
377 0 : catch (kdb::tools::NoPlugin const & e)
378 : {
379 : // if no plugins were found, return an empty vector
380 0 : return std::vector<PluginSpec> ();
381 : }
382 : }
383 :
384 :
385 : class PluginVariantDatabase::VariantImpl
386 : {
387 : public:
388 12 : explicit VariantImpl (const KeySet & conf) : pluginconf (conf)
389 : {
390 : }
391 : ~VariantImpl ()
392 12 : {
393 : }
394 : KeySet pluginconf;
395 : };
396 :
397 6 : PluginVariantDatabase::PluginVariantDatabase (const KeySet & conf)
398 18 : : ModulesPluginDatabase (), variantImpl (new PluginVariantDatabase::VariantImpl (conf))
399 : {
400 6 : }
401 :
402 12 : PluginVariantDatabase::~PluginVariantDatabase ()
403 : {
404 6 : }
405 :
406 6 : std::vector<std::string> PluginVariantDatabase::listAllPlugins () const
407 : {
408 6 : std::vector<std::string> plugins (ModulesPluginDatabase::listAllPlugins ());
409 6 : plugins.erase (std::remove_if (plugins.begin (), plugins.end (),
410 1284 : [this](const std::string & elem) {
411 1284 : Key k ("system/elektra/plugins", KEY_END);
412 642 : k.addBaseName (elem);
413 2568 : k.addBaseName ("disable");
414 2568 : Key res = this->variantImpl->pluginconf.lookup (k);
415 1934 : return res && res.getString () == "1";
416 18 : }),
417 24 : plugins.end ());
418 6 : return plugins;
419 : }
420 :
421 4 : std::vector<PluginSpec> PluginVariantDatabase::getPluginVariants (PluginSpec const & whichplugin) const
422 : {
423 12 : PluginPtr plugin = this->impl->modules.load (whichplugin);
424 16 : KeySet ksSysconf (this->variantImpl->pluginconf);
425 8 : KeySet ksGenconf;
426 :
427 : // read plugin variants via genconf
428 : try
429 : {
430 18 : auto funcGenconf = reinterpret_cast<void (*) (ckdb::KeySet *, ckdb::Key *)> (plugin->getSymbol ("genconf"));
431 2 : funcGenconf (ksGenconf.getKeySet (), 0);
432 : }
433 4 : catch (kdb::tools::MissingSymbol const & e)
434 : {
435 : // no genconf, but maybe sysconf variants
436 4 : KeySet placeholder;
437 2 : return this->getPluginVariantsFromSysconf (whichplugin, ksSysconf, placeholder);
438 : }
439 :
440 : // get plugin variants from genconf, but also consider sysconf for disable/override
441 2 : return this->getPluginVariantsFromGenconf (whichplugin, ksGenconf, ksSysconf);
442 : }
443 :
444 4 : std::vector<PluginSpec> PluginVariantDatabase::getPluginVariantsFromSysconf (PluginSpec const & whichplugin, KeySet const & sysconf,
445 : KeySet const & genconfToIgnore) const
446 : {
447 4 : std::vector<PluginSpec> result;
448 :
449 8 : KeySet ksSysconf (sysconf);
450 :
451 : // first find possible variants
452 8 : Key kVariantBase ("system/elektra/plugins", KEY_END);
453 8 : kVariantBase.addBaseName (whichplugin.getName ());
454 16 : kVariantBase.addBaseName ("variants");
455 :
456 16 : KeySet ksPluginVariantSysconf (ksSysconf.cut (kVariantBase));
457 8 : KeySet ksToIterate (ksPluginVariantSysconf);
458 12 : for (auto kCurrent : ksToIterate)
459 : {
460 0 : Key kCurrentTest (kVariantBase);
461 0 : kCurrentTest.addBaseName (kCurrent.getBaseName ());
462 0 : if (kCurrentTest == kCurrent)
463 : {
464 0 : PluginSpec variant (whichplugin);
465 0 : KeySet ksVariantConfToAdd;
466 :
467 : // new base for plugin conf
468 0 : Key kVariantPluginConf ("system/", KEY_END);
469 :
470 : // add system conf for plugin variant
471 0 : Key kVariantSysconf (this->buildVariantSysconfKey (whichplugin, kCurrent.getBaseName (), "config"));
472 0 : this->addKeysBelowKeyToConf (kVariantSysconf, ksPluginVariantSysconf, kVariantPluginConf, ksVariantConfToAdd);
473 :
474 : // check if the variant was disabled : system/elektra/plugins/simpleini/variants/space/disable
475 0 : Key kDisable = sysconf.lookup (this->buildVariantSysconfKey (whichplugin, kCurrent.getBaseName (), "disable"));
476 0 : if (kDisable && kDisable.getString () == "1")
477 : {
478 0 : continue; // skip this variant
479 : }
480 :
481 : // check if the variant is in the genconfToIgnore list
482 0 : Key kGenconfVariant (kVariantPluginConf);
483 0 : kGenconfVariant.addBaseName (kCurrent.getBaseName ());
484 0 : Key kIgnore = genconfToIgnore.lookup (kGenconfVariant);
485 0 : if (kIgnore)
486 : {
487 0 : continue; // this variant was added by genconf already
488 : }
489 :
490 0 : if (ksVariantConfToAdd.size () == 0)
491 : {
492 : continue; // no config means no variant
493 : }
494 :
495 0 : variant.appendConfig (ksVariantConfToAdd);
496 0 : result.push_back (variant);
497 : }
498 : }
499 :
500 4 : return result;
501 : }
502 :
503 2 : std::vector<PluginSpec> PluginVariantDatabase::getPluginVariantsFromGenconf (PluginSpec const & whichplugin, KeySet const & genconf,
504 : KeySet const & sysconf) const
505 : {
506 2 : std::vector<PluginSpec> result;
507 :
508 4 : KeySet ksToIterate (genconf);
509 6336 : for (auto kCurrent : ksToIterate)
510 : {
511 8440 : Key kCurrentTest (kCurrent.getNamespace () + "/", KEY_END);
512 4220 : kCurrentTest.addBaseName (kCurrent.getBaseName ()); // e.g. system/space
513 2110 : if (kCurrentTest == kCurrent)
514 : {
515 844 : PluginSpec variant (whichplugin);
516 844 : KeySet ksVariantConfToAdd;
517 :
518 : // new base for plugin conf
519 844 : Key kVariantPluginConf ("system/", KEY_END);
520 :
521 : // take variant config from genconf and transform it to proper plugin conf,
522 : // e.g. system/space/config/format -> system/format
523 844 : Key kVariantConf (kCurrentTest);
524 1688 : kVariantConf.addBaseName ("config"); // e.g. system/space/config
525 422 : this->addKeysBelowKeyToConf (kVariantConf, genconf, kVariantPluginConf, ksVariantConfToAdd);
526 :
527 : // TODO plugin infos
528 :
529 : // check if the variant was disabled : system/elektra/plugins/simpleini/variants/space/disable
530 3798 : Key kDisable = sysconf.lookup (this->buildVariantSysconfKey (whichplugin, kCurrent.getBaseName (), "disable"));
531 844 : if (kDisable && kDisable.getString () == "1")
532 : {
533 0 : continue; // skip this variant
534 : }
535 :
536 : // check if an override is available : system/elektra/plugins/simpleini/variants/space/override
537 3798 : Key kOverride = sysconf.lookup (this->buildVariantSysconfKey (whichplugin, kCurrent.getBaseName (), "override"));
538 844 : if (kOverride && kOverride.getString () == "1")
539 : {
540 : // first delete config from genconf entirely
541 0 : ksVariantConfToAdd.clear ();
542 0 : Key kVariantSysconf (this->buildVariantSysconfKey (whichplugin, kCurrent.getBaseName (), "config"));
543 0 : this->addKeysBelowKeyToConf (kVariantSysconf, sysconf, kVariantPluginConf, ksVariantConfToAdd);
544 : }
545 :
546 422 : if (ksVariantConfToAdd.size () == 0)
547 : {
548 : continue; // no config means no variant
549 : }
550 :
551 844 : variant.appendConfig (ksVariantConfToAdd);
552 422 : result.push_back (variant);
553 : }
554 : }
555 :
556 4 : std::vector<PluginSpec> resFromSysconf (this->getPluginVariantsFromSysconf (whichplugin, sysconf, genconf));
557 8 : result.insert (result.end (), resFromSysconf.begin (), resFromSysconf.end ());
558 :
559 2 : return result;
560 : }
561 :
562 844 : Key PluginVariantDatabase::buildVariantSysconfKey (PluginSpec const & whichplugin, std::string const & variant,
563 : const std::string attr) const
564 : {
565 844 : Key result ("system/elektra/plugins", KEY_END);
566 1688 : result.addBaseName (whichplugin.getName ());
567 3376 : result.addBaseName ("variants");
568 844 : result.addBaseName (variant);
569 844 : result.addBaseName (attr);
570 844 : return result;
571 : }
572 :
573 422 : void PluginVariantDatabase::addKeysBelowKeyToConf (Key const & below, KeySet const & conf, Key const & newbase, KeySet & targetconf) const
574 : {
575 844 : KeySet confCp (conf);
576 1688 : KeySet ksVariantSysConf = confCp.cut (below);
577 2954 : for (auto kVariantCurrent : ksVariantSysConf)
578 : {
579 844 : if (!kVariantCurrent.isBelow (below)) continue;
580 1266 : targetconf.append (helper::rebaseKey (kVariantCurrent, below, newbase));
581 : }
582 422 : }
583 :
584 :
585 122 : std::vector<std::string> MockPluginDatabase::listAllPlugins () const
586 : {
587 122 : std::vector<std::string> plugins;
588 742 : for (auto const & plugin : data)
589 : {
590 1128 : plugins.push_back (plugin.first.getName ());
591 : }
592 122 : return plugins;
593 : }
594 :
595 172 : PluginDatabase::Status MockPluginDatabase::status (PluginSpec const & spec) const
596 : {
597 344 : auto it = data.find (spec);
598 344 : if (it != data.end ())
599 : {
600 : return real;
601 : }
602 :
603 108 : if (hasProvides (*this, spec.getName ()))
604 : {
605 : return provides;
606 : }
607 :
608 0 : return missing;
609 : }
610 :
611 :
612 1238 : std::string MockPluginDatabase::lookupInfo (PluginSpec const & spec, std::string const & which) const
613 : {
614 2476 : auto it = data.find (spec);
615 2476 : if (it != data.end ())
616 : {
617 2476 : return it->second[which];
618 : }
619 :
620 0 : return "";
621 : }
622 :
623 158 : PluginDatabase::func_t MockPluginDatabase::getSymbol (PluginSpec const & spec ELEKTRA_UNUSED, std::string const & which) const
624 : {
625 158 : if (which == "checkconf")
626 : {
627 158 : return reinterpret_cast<func_t> (checkconf);
628 : }
629 : return NULL;
630 : }
631 :
632 12 : void MockPluginDatabase::setCheckconfFunction (const MockPluginDatabase::checkConfPtr newCheckconf)
633 : {
634 12 : checkconf = newCheckconf;
635 12 : }
636 : } // namespace tools
637 10908 : } // namespace kdb
|