LCOV - code coverage report
Current view: top level - src/libs/tools/src - backendbuilder.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 218 237 92.0 %
Date: 2019-09-12 12:28:41 Functions: 30 34 88.2 %

          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

Generated by: LCOV version 1.13