Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Implementation of backend
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 :
11 : #include <backend.hpp>
12 : #include <backends.hpp>
13 :
14 :
15 : #include <helper/keyhelper.hpp>
16 : #include <kdbmodule.h>
17 : #include <kdbplugin.h>
18 : #include <kdbprivate.h>
19 :
20 : #include <kdbease.h> // for ckdb::elektraArrayIncName
21 :
22 : #include <algorithm>
23 :
24 : #include <cassert>
25 : #include <kdb.hpp>
26 :
27 :
28 : using namespace std;
29 :
30 :
31 : namespace kdb
32 : {
33 :
34 :
35 : namespace tools
36 : {
37 :
38 2523 : BackendInterface::~BackendInterface ()
39 : {
40 2523 : }
41 :
42 1983 : MountBackendInterface::~MountBackendInterface ()
43 : {
44 1983 : }
45 :
46 1985 : SerializeInterface::~SerializeInterface ()
47 : {
48 1985 : }
49 :
50 : /** Creates a new empty backend.
51 : *
52 : * */
53 13554 : Backend::Backend () : plugins ()
54 : {
55 1506 : }
56 :
57 :
58 11394 : Backend::~Backend ()
59 : {
60 2358 : }
61 :
62 0 : Backend::Backend (Backend && other)
63 : : getplugins (other.getplugins), setplugins (other.setplugins), errorplugins (other.errorplugins), mp (other.mp),
64 0 : configFile (other.configFile), modules (other.modules), config (other.config), plugins (std::move (other.plugins))
65 : {
66 0 : }
67 :
68 0 : Backend & Backend::operator= (Backend && other)
69 : {
70 0 : plugins = std::move (other.plugins);
71 0 : getplugins = other.getplugins;
72 0 : setplugins = other.setplugins;
73 0 : errorplugins = other.errorplugins;
74 0 : mp = other.mp;
75 0 : configFile = other.configFile;
76 0 : modules = other.modules;
77 0 : config = other.config;
78 0 : return *this;
79 : }
80 :
81 : /**
82 : * @brief Sets the mountpoint for the backend
83 : *
84 : * @throw MountpointInvalidException
85 : * @throw MountpointAlreadyInUseException
86 : *
87 : * @param mountpoint the key name will be used as mountpoint.
88 : * It is allowed to pass a key with a KEY_CASCADING_NAME
89 : *
90 : * @param mountConf needs to include the keys below
91 : * system/elektra/mountpoints
92 : */
93 1036 : void Backend::setMountpoint (Key mountpoint, KeySet mountConf)
94 : {
95 3108 : Backends::BackendInfoVector info = Backends::getBackendInfo (mountConf);
96 2072 : std::string namesAsString;
97 2072 : std::vector<std::string> alreadyUsedMountpoints;
98 4480 : for (Backends::BackendInfoVector::const_iterator it = info.begin (); it != info.end (); ++it)
99 : {
100 168 : std::string const & name = it->mountpoint;
101 168 : if (name == "/")
102 : {
103 90 : alreadyUsedMountpoints.push_back ("spec");
104 90 : alreadyUsedMountpoints.push_back ("dir");
105 90 : alreadyUsedMountpoints.push_back ("user");
106 90 : alreadyUsedMountpoints.push_back ("system");
107 : }
108 150 : else if (name.at (0) == '/')
109 : {
110 330 : alreadyUsedMountpoints.push_back (Key ("dir" + name, KEY_END).getName ());
111 330 : alreadyUsedMountpoints.push_back (Key ("user" + name, KEY_END).getName ());
112 330 : alreadyUsedMountpoints.push_back (Key ("system" + name, KEY_END).getName ());
113 : }
114 :
115 : // always add name itself, too
116 168 : alreadyUsedMountpoints.push_back (name);
117 :
118 168 : namesAsString += name;
119 168 : namesAsString += " ";
120 : }
121 :
122 : // STEP 0: check for null key
123 1036 : if (!mountpoint)
124 : {
125 8 : throw MountpointAlreadyInUseException ("Null mountpoint not allowed");
126 : }
127 :
128 2068 : std::string smp = mountpoint.getName ();
129 :
130 : // STEP 1: check for empty name
131 1034 : if (smp.empty ())
132 : {
133 24 : throw MountpointAlreadyInUseException ("Empty mountpoint not allowed");
134 : }
135 :
136 : // STEP 2: check for wrong namespace (proc)
137 3084 : if (mountpoint.getNamespace () == "proc")
138 : {
139 30 : throw MountpointAlreadyInUseException ("proc mountpoint not allowed");
140 : }
141 :
142 : // STEP 3: check for name match
143 1018 : if (smp == "/")
144 : {
145 128 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), "/") != alreadyUsedMountpoints.end ())
146 : {
147 : throw MountpointAlreadyInUseException (
148 8 : "Root mountpoint not possible, because the root mountpoint already exists.\n");
149 : }
150 60 : Key specmp ("spec", KEY_END);
151 150 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), specmp.getName ()) !=
152 30 : alreadyUsedMountpoints.end ())
153 : {
154 0 : throw MountpointAlreadyInUseException ("Root mountpoint not possible, because spec mountpoint already exists.\n");
155 : }
156 60 : Key dkmp ("dir", KEY_END);
157 150 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), dkmp.getName ()) !=
158 30 : alreadyUsedMountpoints.end ())
159 : {
160 0 : throw MountpointAlreadyInUseException ("Root mountpoint not possible, because dir mountpoint already exists.\n");
161 : }
162 60 : Key ukmp ("user", KEY_END);
163 150 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), ukmp.getName ()) !=
164 30 : alreadyUsedMountpoints.end ())
165 : {
166 0 : throw MountpointAlreadyInUseException ("Root mountpoint not possible, because user mountpoint already exists.\n");
167 : }
168 60 : Key skmp ("system", KEY_END);
169 150 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), skmp.getName ()) !=
170 30 : alreadyUsedMountpoints.end ())
171 : {
172 0 : throw MountpointAlreadyInUseException ("Root mountpoint not possible, because system mountpoint already exists.\n");
173 : }
174 : }
175 986 : else if (smp.at (0) == '/')
176 : {
177 1204 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), smp) != alreadyUsedMountpoints.end ())
178 : {
179 3 : throw MountpointAlreadyInUseException ("Cascading mountpoint " + smp +
180 5 : " not possible, because cascading mountpoint " + smp + " already exists.\n");
181 : }
182 900 : Key dkmp ("dir" + smp, KEY_END);
183 1500 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), dkmp.getName ()) !=
184 300 : alreadyUsedMountpoints.end ())
185 : {
186 12 : throw MountpointAlreadyInUseException ("Cascading mountpoint " + smp +
187 12 : " not possible, because dir mountpoint already exists.\n");
188 : }
189 888 : Key ukmp ("user" + smp, KEY_END);
190 1480 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), ukmp.getName ()) !=
191 296 : alreadyUsedMountpoints.end ())
192 : {
193 12 : throw MountpointAlreadyInUseException ("Cascading mountpoint " + smp +
194 12 : " not possible, because user mountpoint already exists.\n");
195 : }
196 876 : Key skmp ("system" + smp, KEY_END);
197 1460 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), skmp.getName ()) !=
198 292 : alreadyUsedMountpoints.end ())
199 : {
200 12 : throw MountpointAlreadyInUseException ("Cascading mountpoint " + smp +
201 12 : " not possible, because system mountpoint already exists.\n");
202 : }
203 : }
204 : else
205 : {
206 2055 : Key kmp (smp, KEY_END);
207 685 : if (!kmp.isValid ()) throw MountpointInvalidException ();
208 3425 : if (std::find (alreadyUsedMountpoints.begin (), alreadyUsedMountpoints.end (), kmp.getName ()) !=
209 685 : alreadyUsedMountpoints.end ())
210 : {
211 270 : throw MountpointAlreadyInUseException (std::string ("Mountpoint ") + smp +
212 180 : " is one of the already used names: " + namesAsString);
213 : }
214 : }
215 :
216 : // TODO STEP 4: check if mounted below system/elektra
217 2874 : Key elektraCheck (mountpoint.dup ());
218 958 : helper::removeNamespace (elektraCheck);
219 2874 : if (elektraCheck.isBelowOrSame (Key ("/elektra", KEY_END)))
220 : {
221 : throw MountpointAlreadyInUseException (
222 204 : std::string ("Mountpoint ") + smp +
223 102 : " is below the reserved names /elektra because it would cause inconsistencies in this or future versions");
224 : }
225 :
226 : // everything worked, swap it
227 1848 : std::swap (this->mp, smp);
228 924 : }
229 :
230 : /**
231 : * @brief Backend Config to add to
232 : *
233 : * @param ks the config to add, should be below system/
234 : */
235 245 : void Backend::setBackendConfig (KeySet const & ks)
236 : {
237 490 : config.append (ks);
238 245 : }
239 :
240 :
241 : /**@pre: resolver needs to be loaded first
242 : * Will check the filename and use it as configFile for this backend.
243 : * @throw FileNotValidException if filename is not valid
244 : * @throw MissingSymbol if plugin does not implement 'checkfile' */
245 730 : void Backend::useConfigFile (std::string file)
246 : {
247 : typedef int (*checkFilePtr) (const char *);
248 730 : checkFilePtr checkFileFunction = nullptr;
249 :
250 2926 : for (auto & elem : plugins)
251 : {
252 : try
253 : {
254 3680 : checkFileFunction = reinterpret_cast<checkFilePtr> (elem->getSymbol ("checkfile"));
255 730 : break;
256 : }
257 6 : catch (MissingSymbol const & ms)
258 : {
259 : }
260 : }
261 :
262 730 : if (!checkFileFunction)
263 : {
264 0 : throw MissingSymbol ("No resolver with checkfile found");
265 : }
266 :
267 :
268 730 : int res = checkFileFunction (file.c_str ());
269 :
270 730 : if (res == -1) throw FileNotValidException ();
271 :
272 1460 : configFile = file;
273 730 : }
274 :
275 :
276 2318 : void Backend::tryPlugin (PluginSpec const & spec)
277 : {
278 4628 : PluginPtr plugin = modules.load (spec);
279 :
280 2310 : errorplugins.tryPlugin (*plugin.get ());
281 2310 : getplugins.tryPlugin (*plugin.get ());
282 2310 : setplugins.tryPlugin (*plugin.get ());
283 :
284 :
285 11470 : for (auto & elem : plugins)
286 : {
287 8936 : if (plugin->getFullName () == elem->getFullName ()) throw PluginAlreadyInserted (plugin->getFullName ());
288 : }
289 :
290 :
291 4618 : plugins.push_back (std::move (plugin));
292 2309 : }
293 :
294 :
295 : /**
296 : * Add a plugin that can be loaded, meets all
297 : * constraints.
298 : *
299 : * @note that this does not mean that the backend
300 : * validates after it is added. It only means that
301 : * the situation is not getting worse.
302 : *
303 : * @throw PluginCheckException or its subclasses if it was not possible
304 : * to load the plugin
305 : *
306 : * For validation @see validated().
307 : */
308 2318 : void Backend::addPlugin (PluginSpec const & plugin)
309 : {
310 4636 : KeySet fullPluginConfig = plugin.getConfig ();
311 6954 : fullPluginConfig.append (plugin.getConfig ()); // add previous configs
312 2318 : tryPlugin (plugin);
313 6927 : errorplugins.addPlugin (*plugins.back ());
314 6927 : getplugins.addPlugin (*plugins.back ());
315 6927 : setplugins.addPlugin (*plugins.back ());
316 :
317 9236 : KeySet toAdd = plugins.back ()->getNeededConfig ();
318 4618 : config.append (toAdd);
319 2309 : }
320 :
321 :
322 : /**
323 : * @return true if backend is validated
324 : * @return false if more plugins are needed to be valided
325 : */
326 84 : bool Backend::validated () const
327 : {
328 84 : bool ret = true;
329 :
330 :
331 84 : if (!errorplugins.validated ()) ret = false;
332 84 : if (!getplugins.validated ()) ret = false;
333 84 : if (!setplugins.validated ()) ret = false;
334 :
335 :
336 84 : return ret;
337 : }
338 :
339 0 : void Backend::status (std::ostream & os) const
340 : {
341 0 : if (validated ())
342 : {
343 0 : os << "No error, everything validated" << std::endl;
344 : }
345 : else
346 : {
347 0 : os << "Backend is not validated" << std::endl;
348 0 : if (!errorplugins.validated ())
349 : {
350 0 : os << "Error Plugins are not validated" << std::endl;
351 : }
352 :
353 0 : if (!getplugins.validated ())
354 : {
355 0 : os << "Get Plugins are not validated" << std::endl;
356 : }
357 :
358 0 : if (!setplugins.validated ())
359 : {
360 0 : os << "Set Plugins are not validated" << std::endl;
361 : }
362 : }
363 0 : errorplugins.status (os);
364 0 : }
365 :
366 : /**
367 : * @brief Prints the current status
368 : *
369 : * @param os stream to print to
370 : * @param b backend to get status from
371 : *
372 : * @return ref to stream
373 : */
374 0 : std::ostream & operator<< (std::ostream & os, Backend const & b)
375 : {
376 0 : b.status (os);
377 0 : return os;
378 : }
379 :
380 :
381 : /**
382 : * @pre name and mountpoint set
383 : * Add plugin serialization into keyset ret.
384 : *
385 : * Only can be done once!
386 : * (see firstRef in Plugin)
387 : * */
388 599 : void Backend::serialize (kdb::KeySet & ret)
389 : {
390 : assert (!mp.empty ());
391 1198 : Key backendRootKey (Backends::mountpointsPath, KEY_END);
392 599 : backendRootKey.addBaseName (mp);
393 2995 : backendRootKey.setString ("This is a configuration for a backend, see subkeys for more information");
394 599 : ret.append (backendRootKey);
395 :
396 :
397 1198 : if (mp == "/")
398 : {
399 168 : ret.append (*Key (backendRootKey.getName () + "/mountpoint", KEY_VALUE, "/", KEY_COMMENT,
400 : "The mount point stores the location where the backend should be mounted.\n"
401 : "This is the root mountpoint.\n",
402 24 : KEY_END));
403 : }
404 1150 : else if (mp.at (0) == '/')
405 : {
406 1584 : ret.append (*Key (backendRootKey.getName () + "/mountpoint", KEY_VALUE, mp.c_str (), KEY_COMMENT,
407 : "The mount point stores the location where the backend should be mounted.\n"
408 : "This is a cascading mountpoint.\n"
409 : "That means it is both mounted to dir, user and system.",
410 198 : KEY_END));
411 : }
412 : else
413 : {
414 3016 : ret.append (*Key (backendRootKey.getName () + "/mountpoint", KEY_VALUE, mp.c_str (), KEY_COMMENT,
415 : "The mount point stores the location where the backend should be mounted.\n"
416 : "This is a normal mount point.\n",
417 377 : KEY_END));
418 : }
419 :
420 2995 : const string configBasePath = Backends::getBasePath (mp) + "/config";
421 2396 : ret.append (Key (configBasePath, KEY_END));
422 :
423 1198 : config.rewind ();
424 1797 : Key common = config.next ();
425 1198 : Key oldParent ("system", KEY_END);
426 1797 : Key newParent (configBasePath, KEY_END);
427 :
428 3360 : for (KeySet::iterator i = config.begin (); i != config.end (); ++i)
429 : {
430 2410 : Key k (i->dup ());
431 1446 : ret.append (kdb::tools::helper::rebaseKey (k, oldParent, newParent));
432 : }
433 :
434 :
435 599 : errorplugins.serialise (backendRootKey, ret);
436 599 : getplugins.serialise (backendRootKey, ret);
437 599 : setplugins.serialise (backendRootKey, ret);
438 :
439 4792 : ret.append (*Key (backendRootKey.getName () + "/config/path", KEY_VALUE, configFile.c_str (), KEY_COMMENT,
440 599 : "The path for this backend. Note that plugins can override that with more specific configuration.", KEY_END));
441 599 : }
442 :
443 4 : void PluginAdder::addPlugin (PluginSpec const & spec)
444 : {
445 8 : PluginPtr plugin = modules.load (spec);
446 4 : if (!plugin)
447 : {
448 0 : throw NoPlugin (spec.getName ());
449 : }
450 12 : std::shared_ptr<Plugin> sharedPlugin = std::move (plugin);
451 :
452 36 : std::istringstream ss (sharedPlugin->lookupInfo ("placements"));
453 4 : std::string placement;
454 36 : while (ss >> placement)
455 : {
456 140 : if (sharedPlugin->lookupInfo ("stacking") == "" && placement == "postgetstorage")
457 : {
458 : // reverse postgetstorage, except stacking is set
459 8 : plugins[placement].push_front (sharedPlugin);
460 : }
461 : else
462 : {
463 20 : plugins[placement].push_back (sharedPlugin);
464 : }
465 : }
466 4 : }
467 :
468 : namespace
469 : {
470 182 : void append (std::string placement, std::string & where, std::string checkPlacement)
471 : {
472 182 : if (placement == checkPlacement)
473 : {
474 13 : if (where.empty ())
475 : {
476 : where = placement;
477 : }
478 : else
479 : {
480 4 : where += " ";
481 : where += placement;
482 : }
483 : }
484 182 : }
485 : } // namespace
486 :
487 32 : struct Placements
488 : {
489 : std::string get;
490 : std::string set;
491 : std::string error;
492 :
493 14 : void addPlacement (std::string placement)
494 : {
495 84 : append (placement, error, "prerollback");
496 84 : append (placement, error, "rollback");
497 84 : append (placement, error, "postrollback");
498 :
499 84 : append (placement, get, "getresolver");
500 84 : append (placement, get, "pregetstorage");
501 84 : append (placement, get, "getstorage");
502 84 : append (placement, get, "postgetstorage");
503 :
504 84 : append (placement, set, "setresolver");
505 84 : append (placement, set, "presetstorage");
506 84 : append (placement, set, "setstorage");
507 84 : append (placement, set, "precommit");
508 84 : append (placement, set, "commit");
509 84 : append (placement, set, "postcommit");
510 14 : }
511 : };
512 :
513 : namespace
514 : {
515 12 : Key g (Key placements, std::string name, std::string value)
516 : {
517 24 : Key x (placements.dup ());
518 12 : x.addBaseName (name);
519 36 : x.setString (value);
520 12 : return x;
521 : }
522 :
523 4 : void serializeConf (kdb::KeySet & ret, Key config, KeySet const & pluginConfig)
524 : {
525 4 : if (pluginConfig.size () != 0)
526 : {
527 0 : ret.append (config);
528 0 : for (auto const & key : pluginConfig)
529 : {
530 0 : Key k (key.dup ());
531 0 : helper::removeNamespace (k);
532 0 : ret.append (Key (config.getName () + k.getName (), KEY_VALUE, key.getString ().c_str (), KEY_END));
533 : }
534 : }
535 4 : }
536 : } // namespace
537 :
538 2 : void GlobalPlugins::serialize (kdb::KeySet & ret)
539 : {
540 : // transform to suitable data structure
541 4 : std::map<std::shared_ptr<Plugin>, Placements> pp;
542 16 : for (auto const & placements : plugins)
543 : {
544 44 : for (auto const & plugin : placements.second)
545 : {
546 126 : std::istringstream ss (plugin->lookupInfo ("status"));
547 28 : std::string status;
548 14 : bool isglobal = false;
549 184 : while (ss >> status)
550 : {
551 78 : if (status == "global") isglobal = true;
552 : }
553 :
554 14 : if (!isglobal)
555 : {
556 0 : throw NoGlobalPlugin (plugin->name ());
557 : }
558 :
559 42 : pp[plugin].addPlacement (placements.first);
560 : }
561 : }
562 :
563 6 : ret.append (Key ("system/elektra/globalplugins", KEY_VALUE, "", KEY_END));
564 6 : ret.append (Key ("system/elektra/globalplugins/postcommit", KEY_VALUE, "list", KEY_END));
565 6 : ret.append (Key ("system/elektra/globalplugins/postcommit/user", KEY_VALUE, "list", KEY_END));
566 6 : ret.append (Key ("system/elektra/globalplugins/postcommit/user/placements", KEY_VALUE, "", KEY_END));
567 4 : ret.append (Key ("system/elektra/globalplugins/postcommit/user/placements/set", KEY_VALUE, "presetstorage precommit postcommit",
568 2 : KEY_END));
569 : ret.append (
570 6 : Key ("system/elektra/globalplugins/postcommit/user/placements/get", KEY_VALUE, "pregetstorage postgetstorage", KEY_END));
571 6 : ret.append (Key ("system/elektra/globalplugins/postcommit/user/placements/error", KEY_VALUE, "prerollback postrollback", KEY_END));
572 6 : ret.append (Key ("system/elektra/globalplugins/postcommit/user/plugins", KEY_VALUE, "", KEY_END));
573 4 : Key i ("system/elektra/globalplugins/postcommit/user/plugins/#0", KEY_END);
574 10 : for (auto const & plugin : pp)
575 : {
576 12 : i.setString (plugin.first->name ());
577 16 : ret.append (i.dup ());
578 12 : Key placements (i.dup ());
579 16 : placements.addBaseName ("placements");
580 4 : ret.append (placements);
581 :
582 40 : ret.append (g (placements, "get", plugin.second.get));
583 40 : ret.append (g (placements, "set", plugin.second.set));
584 40 : ret.append (g (placements, "error", plugin.second.error));
585 :
586 20 : serializeConf (ret, Key (i.getName () + "/config", KEY_VALUE, "", KEY_END), plugin.first->getConfig ());
587 4 : ckdb::elektraArrayIncName (*i);
588 : }
589 6 : ret.append (Key ("system/elektra/globalplugins/postrollback", KEY_VALUE, "list", KEY_END));
590 6 : ret.append (Key ("system/elektra/globalplugins/precommit", KEY_VALUE, "list", KEY_END));
591 6 : ret.append (Key ("system/elektra/globalplugins/pregetstorage", KEY_VALUE, "list", KEY_END));
592 6 : ret.append (Key ("system/elektra/globalplugins/postgetstorage", KEY_VALUE, "list", KEY_END));
593 6 : ret.append (Key ("system/elektra/globalplugins/presetstorage", KEY_VALUE, "list", KEY_END));
594 6 : ret.append (Key ("system/elektra/globalplugins/prerollback", KEY_VALUE, "list", KEY_END));
595 2 : }
596 :
597 0 : void ImportExportBackend::status (std::ostream & os) const
598 : {
599 0 : if (plugins.empty ())
600 0 : os << "no plugin added" << std::endl;
601 0 : else if (plugins.find ("setstorage") == plugins.end ())
602 0 : os << "no storage plugin added" << std::endl;
603 : else
604 0 : os << "everything ok" << std::endl;
605 0 : }
606 :
607 0 : void ImportExportBackend::importFromFile (KeySet & ks, Key const & parentKey) const
608 : {
609 0 : Key key = parentKey;
610 0 : std::vector<std::string> placements;
611 0 : placements.push_back ("getresolver");
612 0 : placements.push_back ("pregetstorage");
613 0 : placements.push_back ("getstorage");
614 0 : placements.push_back ("postgetstorage");
615 0 : for (auto const & placement : placements)
616 : {
617 0 : auto currentPlugins = plugins.find (placement);
618 0 : if (currentPlugins == plugins.end ()) continue;
619 0 : for (auto const & plugin : currentPlugins->second)
620 : {
621 0 : plugin->get (ks, key);
622 : }
623 : }
624 0 : }
625 :
626 0 : void ImportExportBackend::exportToFile (KeySet const & cks, Key const & parentKey) const
627 : {
628 0 : KeySet ks = cks;
629 0 : Key key = parentKey;
630 0 : std::vector<std::string> placements;
631 0 : placements.push_back ("setresolver");
632 0 : placements.push_back ("presetstorage");
633 0 : placements.push_back ("setstorage");
634 0 : placements.push_back ("precommit");
635 0 : placements.push_back ("commit");
636 0 : placements.push_back ("postcommit");
637 0 : for (auto const & placement : placements)
638 : {
639 0 : auto currentPlugins = plugins.find (placement);
640 0 : if (currentPlugins == plugins.end ()) continue;
641 0 : for (auto const & plugin : currentPlugins->second)
642 : {
643 0 : plugin->set (ks, key);
644 : }
645 : }
646 0 : }
647 : } // namespace tools
648 : } // namespace kdb
|