Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Implementation of backend builder
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 :
11 : #include <backend.hpp>
12 : #include <backendbuilder.hpp>
13 : #include <backendparser.hpp>
14 : #include <backends.hpp>
15 : #include <plugindatabase.hpp>
16 : #include <pluginspec.hpp>
17 :
18 :
19 : #include <helper/keyhelper.hpp>
20 : #include <kdbmodule.h>
21 : #include <kdbplugin.h>
22 : #include <kdbprivate.h>
23 :
24 : #include <algorithm>
25 : #include <functional>
26 : #include <set>
27 : #include <unordered_set>
28 :
29 : #include <cassert>
30 : #include <kdb.hpp>
31 : #include <kdbmeta.h>
32 :
33 :
34 : using namespace std;
35 :
36 :
37 : namespace kdb
38 : {
39 :
40 :
41 : namespace tools
42 : {
43 :
44 :
45 2720 : BackendBuilderInit::BackendBuilderInit () : pluginDatabase (make_shared<ModulesPluginDatabase> ()), backendFactory ("backend")
46 : {
47 340 : }
48 :
49 :
50 492 : BackendBuilderInit::BackendBuilderInit (PluginDatabasePtr const & plugins) : pluginDatabase (plugins), backendFactory ("backend")
51 : {
52 82 : }
53 :
54 0 : BackendBuilderInit::BackendBuilderInit (BackendFactory const & bf)
55 0 : : pluginDatabase (make_shared<ModulesPluginDatabase> ()), backendFactory (bf)
56 : {
57 0 : }
58 :
59 0 : BackendBuilderInit::BackendBuilderInit (PluginDatabasePtr const & plugins, BackendFactory const & bf)
60 0 : : pluginDatabase (plugins), backendFactory (bf)
61 : {
62 0 : }
63 :
64 0 : BackendBuilderInit::BackendBuilderInit (BackendFactory const & bf, PluginDatabasePtr const & plugins)
65 0 : : pluginDatabase (plugins), backendFactory (bf)
66 : {
67 0 : }
68 :
69 :
70 424 : BackendBuilder::BackendBuilder (BackendBuilderInit const & bbi)
71 3392 : : pluginDatabase (bbi.getPluginDatabase ()), backendFactory (bbi.getBackendFactory ())
72 : {
73 424 : }
74 :
75 :
76 2691 : BackendBuilder::~BackendBuilder ()
77 : {
78 539 : }
79 :
80 1815 : MountBackendBuilder::MountBackendBuilder (BackendBuilderInit const & bbi) : BackendBuilder (bbi)
81 : {
82 363 : }
83 :
84 : /**
85 : * @brief Makes sure that ordering constraints are fulfilled.
86 : *
87 : * @pre a sorted list except of the last element to be inserted
88 : * @post the last element will be moved to a place where it does not produce an order violation
89 : *
90 : * @note its still possible that an order violation is present in the case
91 : * of order violation in the other direction (no cycle detection).
92 : */
93 1204 : void BackendBuilder::sort ()
94 : {
95 2408 : KeySet deps;
96 1204 : size_t i = 0;
97 7571 : for (auto const & ps : toAdd)
98 : {
99 11020 : Key dep ("/" + ps.getName (), KEY_END);
100 11020 : if (ps.getName () != ps.getRefName ())
101 : {
102 248 : dep.addBaseName (ps.getRefName ());
103 : }
104 2755 : deps.append (dep);
105 2755 : dep.set<size_t> (i);
106 11020 : dep.setMeta<size_t> ("order", i);
107 2755 : ++i;
108 : }
109 :
110 2408 : std::unordered_set<std::string> addedDeps;
111 7571 : for (auto const & ps : toAdd)
112 : {
113 16530 : std::stringstream ss (pluginDatabase->lookupInfo (ps, "ordering"));
114 2755 : std::string order;
115 6110 : while (ss >> order)
116 : {
117 900 : if (addedDeps.find (order) != addedDeps.end ())
118 : {
119 : continue;
120 : }
121 :
122 292 : addedDeps.insert (order);
123 :
124 : // check if dependency is relevant (occurs in KeySet)
125 3528 : for (auto const & self : deps)
126 : {
127 884 : const size_t jumpSlash = 1;
128 1768 : std::string n = self.getName ();
129 7072 : std::string name (n.begin () + jumpSlash, n.end ());
130 :
131 884 : bool hasProvides = false;
132 : /* TODO: should also take care of provides
133 : implementation below would self-conflict on multiple same providers
134 : std::string provides = pluginDatabase->lookupInfo (PluginSpec(name), "provides");
135 : std::istringstream ss2 (provides);
136 : std::string provide;
137 : while (ss2 >> provide)
138 : {
139 : if (provide == name)
140 : {
141 : hasProvides = true;
142 : }
143 : }
144 : */
145 :
146 2496 : if ((name.length () >= order.length () && std::equal (order.begin (), order.end (), name.begin ())) ||
147 : hasProvides)
148 : {
149 : // is relevant, add this instance of dep to every other key
150 : // add reverse dep of every key to self
151 882 : for (auto const & k : deps)
152 : {
153 309 : if (k == self) continue;
154 884 : ckdb::elektraMetaArrayAdd (*self, "dep", (k.getName ()).c_str ());
155 : }
156 : }
157 : }
158 : }
159 : }
160 :
161 : // now sort by the given topology
162 2408 : std::vector<ckdb::Key *> ordered;
163 1204 : ordered.resize (deps.size ());
164 2408 : int ret = elektraSortTopology (deps.getKeySet (), &ordered[0]);
165 1204 : if (ret == 0) throw CyclicOrderingViolation ();
166 1204 : if (ret == -1) throw std::logic_error ("elektraSortTopology was used wrongly");
167 :
168 2408 : PluginSpecVector copy (toAdd);
169 :
170 : // now swap everything in toAdd as we have the indizes given in ordered
171 1204 : i = 0;
172 7571 : for (auto const & o : ordered)
173 : {
174 11020 : toAdd[i] = copy[atoi (ckdb::keyString (o))];
175 2755 : ++i;
176 : }
177 1204 : }
178 :
179 91 : void BackendBuilder::needMetadata (std::string addMetadata)
180 : {
181 182 : std::istringstream is (addMetadata);
182 91 : std::string md;
183 467 : while (is >> md)
184 : {
185 190 : std::string nd;
186 190 : Key k (md.c_str (), KEY_META_NAME, KEY_END);
187 816 : for (auto && elem : k)
188 : {
189 354 : if (!elem.empty () && elem[0] == '#')
190 : {
191 : // reduce array entries to #
192 : nd += '#';
193 : }
194 : else
195 : {
196 : nd += elem;
197 : }
198 177 : nd += "/";
199 : }
200 :
201 95 : if (!nd.empty ())
202 : {
203 : // remove last "/"
204 190 : nd = nd.substr (0, nd.size () - 1);
205 95 : metadata.insert (nd);
206 : }
207 : // ignore if it does not work! (i.e. metadata already present)
208 : }
209 91 : }
210 :
211 : /**
212 : * @brief Collect what is needed
213 : *
214 : * @param [out] needs are added here
215 : */
216 471 : void BackendBuilder::collectNeeds (std::vector<std::string> & needs) const
217 : {
218 2975 : for (auto const & ps : toAdd)
219 : {
220 6546 : std::stringstream ss (pluginDatabase->lookupInfo (ps, "needs"));
221 1091 : std::string need;
222 2776 : while (ss >> need)
223 : {
224 297 : needs.push_back (need);
225 : }
226 : }
227 471 : }
228 :
229 : /**
230 : * @brief Collect what is recommended
231 : *
232 : * @param [out] needs are added here
233 : */
234 471 : void BackendBuilder::collectRecommends (std::vector<std::string> & recommends) const
235 : {
236 2975 : for (auto const & ps : toAdd)
237 : {
238 6546 : std::stringstream ss (pluginDatabase->lookupInfo (ps, "recommends"));
239 1091 : std::string r;
240 2442 : while (ss >> r)
241 : {
242 130 : recommends.push_back (r);
243 : }
244 : }
245 471 : }
246 :
247 942 : void BackendBuilder::removeProvided (std::vector<std::string> & needs) const
248 : {
249 5950 : for (auto const & ps : toAdd)
250 : {
251 : // remove the needed plugins that are already inserted
252 17456 : needs.erase (std::remove (needs.begin (), needs.end (), ps.getName ()), needs.end ());
253 :
254 : // remove what is already provided
255 10910 : std::string provides = pluginDatabase->lookupInfo (ps, "provides");
256 4364 : std::istringstream ss (provides);
257 2182 : std::string toRemove;
258 14576 : while (ss >> toRemove)
259 : {
260 23828 : needs.erase (std::remove (needs.begin (), needs.end (), toRemove), needs.end ());
261 : }
262 : }
263 942 : }
264 :
265 :
266 471 : void BackendBuilder::removeMetadata (std::set<std::string> & needsMetadata) const
267 : {
268 2975 : for (auto const & ps : toAdd)
269 : {
270 : // remove metadata that already is provided
271 5455 : std::string md = pluginDatabase->lookupInfo (ps, "metadata");
272 2182 : std::istringstream ss (md);
273 1091 : std::string toRemove;
274 2870 : while (ss >> toRemove)
275 : {
276 : needsMetadata.erase (toRemove);
277 : }
278 : }
279 471 : }
280 :
281 : namespace
282 : {
283 471 : void removeMissing (std::vector<std::string> & recommendedPlugins, std::vector<std::string> const & missingPlugins)
284 : {
285 1884 : for (auto const & mp : missingPlugins)
286 : {
287 0 : recommendedPlugins.erase (std::remove (recommendedPlugins.begin (), recommendedPlugins.end (), mp));
288 : }
289 471 : }
290 :
291 : std::string removeArray (std::string s)
292 : {
293 : /*
294 : std::regex e ("#_*[0-9]*");
295 : std::string result;
296 : std::regex_replace (std::back_inserter(result), s.begin(), s.end(), e, "#");
297 : return result;
298 : */
299 25 : return s;
300 : }
301 :
302 : /*
303 : TEST(Backend, x)
304 : {
305 : EXPECT_EQ(removeArray("should/be/unchanged"), "should/be/unchanged");
306 : EXPECT_EQ(removeArray("should/be/#_12"), "should/be/#");
307 : EXPECT_EQ(removeArray("should/be/#__200"), "should/be/#");
308 : EXPECT_EQ(removeArray("should/#_20/abc/#__200"), "should/#/abc/#");
309 : EXPECT_EQ(removeArray("should/#_20/abc/#__204"), "should/#/abc/#");
310 : EXPECT_EQ(removeArray("should/_20/abc/__204"), "should/_20/abc/__204");
311 : }
312 : */
313 : } // namespace
314 :
315 : /**
316 : * @brief resolve all needs that were not resolved by adding plugins.
317 : *
318 : * @warning Must only be used once after all plugins/recommends are added.
319 : *
320 : * @return the missing recommended plugins
321 : * @retval empty if addRecommends was false
322 : *
323 : * @see addPlugin()
324 : */
325 300 : std::vector<std::string> BackendBuilder::resolveNeeds (bool addRecommends)
326 : {
327 : // load dependency-plugins immediately
328 1745 : for (auto const & ps : toAdd)
329 : {
330 3270 : auto plugins = parseArguments (pluginDatabase->lookupInfo (ps, "plugins"));
331 2182 : for (auto const & plugin : plugins)
332 : {
333 2 : addPlugin (plugin);
334 : }
335 : }
336 :
337 : std::vector<std::string> missingRecommends;
338 :
339 471 : do
340 : {
341 471 : collectNeeds (neededPlugins);
342 471 : collectRecommends (recommendedPlugins);
343 :
344 471 : removeProvided (neededPlugins);
345 471 : removeProvided (recommendedPlugins);
346 471 : removeMissing (recommendedPlugins, missingRecommends);
347 471 : removeMetadata (metadata);
348 :
349 : // leftover in needs(Metadata) is what is still needed
350 : // lets add first one:
351 942 : if (!neededPlugins.empty ())
352 : {
353 1805 : addPlugin (PluginSpec (neededPlugins[0]));
354 1444 : neededPlugins.erase (neededPlugins.begin ());
355 : }
356 220 : else if (!metadata.empty ())
357 : {
358 125 : std::string first = (*metadata.begin ());
359 100 : first = removeArray (first);
360 25 : addPlugin (pluginDatabase->lookupMetadata (first));
361 50 : metadata.erase (first);
362 : }
363 170 : else if (!recommendedPlugins.empty () && addRecommends)
364 : {
365 85 : PluginSpec rp (recommendedPlugins[0]);
366 17 : if (pluginDatabase->status (rp) != PluginDatabase::missing)
367 : {
368 17 : addPlugin (rp);
369 : }
370 : else
371 : {
372 0 : missingRecommends.push_back (recommendedPlugins[0]);
373 : }
374 68 : recommendedPlugins.erase (recommendedPlugins.begin ());
375 : }
376 1586 : } while (!neededPlugins.empty () || !metadata.empty () || (!recommendedPlugins.empty () && addRecommends));
377 :
378 300 : return missingRecommends;
379 : }
380 :
381 518 : void BackendBuilder::needPlugin (std::string name)
382 : {
383 1036 : std::stringstream ss (name);
384 518 : std::string n;
385 2128 : while (ss >> n)
386 : {
387 546 : neededPlugins.push_back (n);
388 : }
389 518 : }
390 :
391 24 : void BackendBuilder::recommendPlugin (std::string name)
392 : {
393 48 : std::stringstream ss (name);
394 24 : std::string n;
395 124 : while (ss >> n)
396 : {
397 38 : recommendedPlugins.push_back (n);
398 : }
399 24 : }
400 :
401 : /**
402 : * @brief Add a plugin.
403 : *
404 : * @pre Needs to be a unique new name (use refname if you want to add the same module multiple times)
405 : *
406 : * Will automatically resolve virtual plugins to actual plugins.
407 : *
408 : * Also calls the checkconf function if provided by the plugin. The checkconf function has the
409 : * following signature: int checkconf (Key * errorKey, KeySet * config) and allows a plugin to
410 : * verify its configuration at mount time.
411 : *
412 : * @see resolveNeeds()
413 : * @param plugin
414 : */
415 1206 : void BackendBuilder::addPlugin (PluginSpec const & plugin)
416 : {
417 : typedef int (*checkConfPtr) (ckdb::Key *, ckdb::KeySet *);
418 :
419 6375 : for (auto & p : toAdd)
420 : {
421 4653 : if (p.getFullName () == plugin.getFullName ())
422 : {
423 0 : throw PluginAlreadyInserted (plugin.getFullName ());
424 : }
425 : }
426 :
427 2412 : PluginSpec newPlugin = plugin;
428 :
429 : // if the plugin is actually a provider use it (otherwise we will get our name back):
430 3618 : PluginSpec provides = pluginDatabase->lookupProvides (plugin.getName ());
431 4824 : if (provides.getName () != newPlugin.getName ())
432 : {
433 : // keep our config and refname
434 104 : newPlugin.setName (provides.getName ());
435 104 : newPlugin.appendConfig (provides.getConfig ());
436 : }
437 :
438 : // call plugin's checkconf function (if provided)
439 : // this enables a plugin to verify its configuration at mount time
440 4824 : checkConfPtr checkConfFunction = reinterpret_cast<checkConfPtr> (pluginDatabase->getSymbol (newPlugin, "checkconf"));
441 1206 : if (checkConfFunction)
442 : {
443 30 : ckdb::Key * errorKey = ckdb::keyNew (nullptr);
444 :
445 : // merge plugin config and backend config together
446 90 : ckdb::KeySet * pluginConfig = newPlugin.getConfig ().dup ();
447 60 : ckdb::ksAppend (pluginConfig, backendConf.getKeySet ());
448 :
449 : // call the plugin's checkconf function
450 30 : int checkResult = checkConfFunction (errorKey, pluginConfig);
451 30 : if (checkResult == -1)
452 : {
453 2 : ckdb::ksDel (pluginConfig);
454 6 : throw PluginConfigInvalid (errorKey);
455 : }
456 28 : else if (checkResult == 1)
457 : {
458 : // separate plugin config from the backend config
459 21 : ckdb::Key * backendParent = ckdb::keyNew ("system/", KEY_END);
460 21 : ckdb::KeySet * newBackendConfig = ckdb::ksCut (pluginConfig, backendParent);
461 :
462 : // take over the new configuration
463 42 : KeySet modifiedPluginConfig = KeySet (pluginConfig);
464 42 : KeySet modifiedBackendConfig = KeySet (newBackendConfig);
465 :
466 42 : newPlugin.setConfig (modifiedPluginConfig);
467 21 : setBackendConfig (modifiedBackendConfig);
468 :
469 21 : ckdb::keyDel (backendParent);
470 : }
471 : else
472 : {
473 7 : ckdb::ksDel (pluginConfig);
474 : }
475 28 : ckdb::keyDel (errorKey);
476 : }
477 :
478 1204 : toAdd.push_back (newPlugin);
479 1204 : sort ();
480 1204 : }
481 :
482 2 : void BackendBuilder::remPlugin (PluginSpec const & plugin)
483 : {
484 : using namespace std::placeholders;
485 : PluginSpecFullName cmp;
486 14 : toAdd.erase (std::remove_if (toAdd.begin (), toAdd.end (), std::bind (cmp, plugin, _1)));
487 2 : }
488 :
489 543 : void BackendBuilder::fillPlugins (BackendInterface & b) const
490 : {
491 3469 : for (auto const & plugin : toAdd)
492 : {
493 1298 : b.addPlugin (plugin);
494 : }
495 542 : }
496 :
497 301 : void BackendBuilder::setBackendConfig (KeySet const & ks)
498 : {
499 301 : backendConf = ks;
500 301 : }
501 :
502 10 : KeySet BackendBuilder::getBackendConfig ()
503 : {
504 20 : return backendConf;
505 : }
506 :
507 2 : GlobalPluginsBuilder::GlobalPluginsBuilder (BackendBuilderInit const & bbi) : BackendBuilder (bbi)
508 : {
509 2 : }
510 :
511 2 : void GlobalPluginsBuilder::serialize (kdb::KeySet & ret)
512 : {
513 4 : GlobalPlugins gp;
514 2 : fillPlugins (gp);
515 4 : return gp.serialize (ret);
516 : }
517 :
518 : /**
519 : * @brief Below this path is the configuration for global plugins
520 : */
521 : const char * GlobalPluginsBuilder::globalPluginsPath = "system/elektra/globalplugins";
522 :
523 :
524 0 : void MountBackendBuilder::status (std::ostream & os) const
525 : {
526 : try
527 : {
528 0 : MountBackendInterfacePtr b = getBackendFactory ().create ();
529 0 : fillPlugins (*b);
530 0 : return b->status (os);
531 : }
532 0 : catch (std::exception const & pce)
533 : {
534 0 : os << "Could not successfully add plugin: " << pce.what () << std::endl;
535 : }
536 : }
537 :
538 74 : bool MountBackendBuilder::validated () const
539 : {
540 : try
541 : {
542 222 : MountBackendInterfacePtr b = getBackendFactory ().create ();
543 74 : fillPlugins (*b);
544 74 : return b->validated ();
545 : }
546 0 : catch (...)
547 : {
548 : return false;
549 : }
550 : }
551 :
552 271 : void MountBackendBuilder::setMountpoint (Key mountpoint_, KeySet mountConf_)
553 : {
554 542 : mountpoint = mountpoint_;
555 271 : mountConf = mountConf_;
556 :
557 813 : MountBackendInterfacePtr mbi = getBackendFactory ().create ();
558 1355 : mbi->setMountpoint (mountpoint, mountConf);
559 267 : }
560 :
561 33 : std::string MountBackendBuilder::getMountpoint () const
562 : {
563 33 : return mountpoint.getName ();
564 : }
565 :
566 278 : void MountBackendBuilder::setBackendConfig (KeySet const & ks)
567 : {
568 278 : BackendBuilder::setBackendConfig (ks);
569 278 : }
570 :
571 267 : void MountBackendBuilder::useConfigFile (std::string file)
572 : {
573 534 : configfile = file;
574 :
575 761 : MountBackendInterfacePtr b = getBackendFactory ().create ();
576 267 : bool checkPossible = false;
577 1307 : for (auto const & p : *this)
578 : {
579 1434 : if ("resolver" == getPluginDatabase ()->lookupInfo (p, "provides"))
580 : {
581 227 : checkPossible = true;
582 : }
583 : }
584 :
585 307 : if (!checkPossible) return;
586 227 : fillPlugins (*b);
587 681 : b->useConfigFile (configfile);
588 : }
589 :
590 22 : std::string MountBackendBuilder::getConfigFile () const
591 : {
592 44 : return configfile;
593 : }
594 :
595 240 : void MountBackendBuilder::serialize (kdb::KeySet & ret)
596 : {
597 720 : MountBackendInterfacePtr mbi = getBackendFactory ().create ();
598 240 : fillPlugins (*mbi);
599 1195 : mbi->setMountpoint (mountpoint, mountConf);
600 239 : mbi->setBackendConfig (backendConf);
601 717 : mbi->useConfigFile (configfile);
602 239 : mbi->serialize (ret);
603 239 : }
604 : } // namespace tools
605 : } // namespace kdb
|