Line data Source code
1 : // Copyright (c) 2013-2021 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/cppthread
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : // self
21 : //
22 : #include "cppthread/plugins.h"
23 :
24 : #include "cppthread/log.h"
25 : #include "cppthread/guard.h"
26 :
27 :
28 : // snapdev lib
29 : //
30 : #include <snapdev/glob_to_list.h>
31 : #include <snapdev/join_strings.h>
32 : #include <snapdev/not_used.h>
33 : #include <snapdev/tokenize_string.h>
34 :
35 :
36 : // C lib
37 : //
38 : #include <dlfcn.h>
39 :
40 : // last include
41 : //
42 : #include <snapdev/poison.h>
43 :
44 :
45 :
46 :
47 : namespace cppthread
48 : {
49 :
50 :
51 : namespace detail
52 : {
53 :
54 :
55 : /** \brief The global Plugin Repository.
56 : *
57 : * A plugin is always considered global, as far as the dlopen() function is
58 : * concerned, loading the exact same .so multiple times has no effect the
59 : * second, third, etc. time (it just increments a reference counter).
60 : *
61 : * So on our end we have to have a global table of plugins. If the same plugin
62 : * is to be loaded
63 : *
64 : * \note
65 : * The plugin_repository is a singleton.
66 : */
67 1 : class plugin_repository
68 : {
69 : public:
70 : static plugin_repository & instance();
71 : plugin::pointer_t get_plugin(plugin_names::filename_t const & filename);
72 : void register_plugin(plugin::pointer_t p);
73 :
74 : private:
75 : mutex f_mutex = mutex();
76 : plugin::map_t f_plugins = plugin::map_t(); // WARNING: this map is sorted by filename
77 : plugin_names::filename_t f_register_filename = plugin_names::filename_t();
78 : };
79 :
80 :
81 :
82 : /** \brief Retrieve an instance of the plugin repository.
83 : *
84 : * Each plugin can be loaded only once, but it can be referenced in multiple
85 : * plugin_collection. So what we do is use a singleton, the plugin_repository,
86 : * which does the actual load of the plugin through the get_plugin(). If
87 : * the plugin is already loaded, then it get returned immediately. If not
88 : * there, then we use the dlopen() function to load it. At that point, the
89 : * plugin itself will register itself.
90 : *
91 : * This function returns a pointer to the singleton so we can access the
92 : * get_plugin() to retrieve a plugin and the register_plugin() from the
93 : * factory to register the plugin in this singleton.
94 : *
95 : * \return The plugin_repository reference.
96 : */
97 2 : plugin_repository & plugin_repository::instance()
98 : {
99 2 : static mutex g_mutex;
100 :
101 4 : guard lock(g_mutex);
102 :
103 : static plugin_repository * g_plugin_repository = nullptr;
104 :
105 2 : if(g_plugin_repository == nullptr)
106 : {
107 1 : g_plugin_repository = new plugin_repository;
108 : }
109 :
110 4 : return *g_plugin_repository;
111 : }
112 :
113 :
114 : /** \brief Get a pointer to the specified plugin.
115 : *
116 : * This function returns a pointer to the plugin found in \p filename.
117 : *
118 : * If the dlopen() function fails, then the function returns a null pointer
119 : * in which case the function generates an error in the log. The two main
120 : * reasons for the load to fail are: (1) the file is not a valid plugin
121 : * and (2) the plugin has unsatisfied link dependencies, however, on that
122 : * second point, the linker is used lazily so in most cases you detect
123 : * those errors later when you call functions in your plugins.
124 : *
125 : * \param[in] filename The name of the file that corresponds to a plugin.
126 : *
127 : * \return The pointer to the plugin.
128 : */
129 1 : plugin::pointer_t plugin_repository::get_plugin(plugin_names::filename_t const & filename)
130 : {
131 2 : guard lock(f_mutex);
132 :
133 : // first check whether it was already loaded, if so, just return the
134 : // existing plugin (no need to re-load it)
135 : //
136 1 : auto it(f_plugins.find(filename));
137 1 : if(it != f_plugins.end())
138 : {
139 0 : return it->second;
140 : }
141 :
142 : // TBD: Use RTLD_NOW instead of RTLD_LAZY in DEBUG mode
143 : // so we discover missing symbols would be nice, only
144 : // that would require loading in the correct order...
145 : // (see dlopen() call below)
146 : //
147 :
148 : // load the plugin; the plugin will "register itself" through its factory
149 : //
150 : // we want to plugin filename save in the plugin object itself at the time
151 : // we register it so we save it here and pick it up at the time the
152 : // registration function gets called
153 : //
154 1 : f_register_filename = filename;
155 1 : void const * const h(dlopen(filename.c_str(), RTLD_LAZY | RTLD_GLOBAL));
156 1 : if(h == nullptr)
157 : {
158 0 : int const e(errno);
159 0 : log << log_level_t::error
160 0 : << "cannot load plugin file \""
161 0 : << filename
162 0 : << "\" (errno: "
163 0 : << e
164 0 : << ", "
165 0 : << dlerror()
166 0 : << ")"
167 0 : << end;
168 0 : return plugin::pointer_t();
169 : }
170 1 : f_register_filename.clear();
171 : //SNAP_LOG_ERROR("note: registering plugin: \"")(name)("\"");
172 :
173 1 : return f_plugins[filename];
174 : }
175 :
176 :
177 : /** \brief Register the plugin in our global repository.
178 : *
179 : * Since plugins can be loaded once with dlopen() and then reused any number
180 : * of times, we register the plugins in a global repositiory. This function
181 : * is the one used to actually register the plugin.
182 : *
183 : * When loading a new plugin you should call the
184 : * plugin_repository::get_plugin() with the plugin filename. If that plugin
185 : * was already loaded, then that pointer is returned and this registration
186 : * function is never called.
187 : *
188 : * However, if the plugin was not yet loaded, we call the dlopen() which
189 : * creates a plugin specific factory, which in turn calls the
190 : * plugin_factory::register_plugin() function, which finally calls this
191 : * very function to register the plugin in the global repository.
192 : *
193 : * The plugin_factory::register_plugin() is responsible for verifying that
194 : * the plugin name is valid.
195 : */
196 1 : void plugin_repository::register_plugin(plugin::pointer_t p)
197 : {
198 1 : p->f_filename = f_register_filename;
199 :
200 1 : f_plugins[f_register_filename] = p;
201 1 : }
202 :
203 :
204 :
205 :
206 :
207 :
208 : } // detail namespace
209 :
210 :
211 :
212 :
213 :
214 :
215 :
216 :
217 : /** \brief Initialize the plugin factory.
218 : *
219 : * This function creates a factory with a definition and an instance of the
220 : * plugin that the factory is expected to hold. This pointer is created
221 : * at the time the plugin specific factory is created on a dlopen() call.
222 : *
223 : * Later you can retrieve the plugin instance using the instance() function.
224 : * However, this is private to the plugin .cpp file. If you want a pointer
225 : * to the plugin, the plugin_collection::get_plugin_by_name() is probably
226 : * going to work better for you. Just make sure that the plugin_collection
227 : * is available through your user data (see plugin_collection::set_data()
228 : * for more details about that).
229 : *
230 : * \param[in] definition The definition of the plugin.
231 : * \param[in] instance The instance of the plugin.
232 : */
233 1 : plugin_factory::plugin_factory(plugin_definition const & definition, std::shared_ptr<plugin> instance)
234 : : f_definition(definition)
235 1 : , f_plugin(instance)
236 : {
237 1 : }
238 :
239 :
240 : /** \brief Verify that the plugin is ready for deletion.
241 : *
242 : * Whenever we unload the plugin (using dlclose()), the factor gets destroyed
243 : * and the plugin is expected to be ready for destruction which means no other
244 : * object still hold a reference to it.
245 : */
246 1 : plugin_factory::~plugin_factory()
247 : {
248 : // TODO: at this time this isn't working and it's not going to work any
249 : // time soon, I'm afraid.
250 : //
251 : //if(f_plugin.use_count() != 1)
252 : //{
253 : // std::cerr
254 : // << "error: the plugin is still in use by objects other than the plugin factory, which is invalid at the time we are destroying the plugin."
255 : // << std::endl;
256 : // std::terminate();
257 : //}
258 1 : }
259 :
260 :
261 : /** \brief Return a reference to the plugin definition.
262 : *
263 : * Whenever you create a plugin, you have to create a plugin definition
264 : * which is a structure with various parameter such as the plugin version,
265 : * name, icon, description and dependencies.
266 : *
267 : * This function returns a reference to that definition.
268 : *
269 : * \return A reference to the plugin definition.
270 : */
271 12 : plugin_definition const & plugin_factory::definition() const
272 : {
273 12 : return f_definition;
274 : }
275 :
276 :
277 : /** \brief Retrieve a copy instance of the plugin.
278 : *
279 : * This function returns a copy of the plugin instance that the factory
280 : * allocated on construction. This is a shared pointer. Since the factory
281 : * can be destroyed by calling the dlclose() function, we can then detect
282 : * that the dlclose() was called too soon (i.e. that some other objects
283 : * still hold a reference to the plugin.)
284 : *
285 : * \return A shared pointer to the plugin managed by this plugin factory.
286 : */
287 1 : std::shared_ptr<plugin> plugin_factory::instance() const
288 : {
289 1 : return f_plugin;
290 : }
291 :
292 :
293 : /** \brief Regiter the specified plugin.
294 : *
295 : * This function gets called by the plugin factory of each plugin that gets
296 : * loaded by the dlopen() function.
297 : *
298 : * The function first verifies that the plugin name is a match. That test
299 : * should pretty much never fail.
300 : *
301 : * The function then calls the plugin_repository::register_plugin() function
302 : * which actually adds the plugin to the global list of plugins which allows
303 : * us to reference the same plugin in different collections.
304 : *
305 : * \param[in] name The expected plugin name.
306 : * \param[in] p The pointer to the plugin being registered.
307 : */
308 1 : void plugin_factory::register_plugin(char const * name, plugin::pointer_t p)
309 : {
310 : // `name` comes from the CPPTHREAD_PLUGIN_END macro
311 : // `p->name()` comes from the CPPTHREAD_PLUGIN_START macro
312 : //
313 1 : if(name != p->name())
314 : {
315 : // as long as you use the supplied macro, this should never ever
316 : // occur since the allocation of the plugin would fail if the
317 : // name do not match; that being said, you can attempt to create
318 : // two plugins in a single file and that is a case we do not support
319 : // and would result in such an error
320 : //
321 : throw cppthread_name_mismatch( // LCOV_EXCL_LINE
322 : "registering plugin named \"" // LCOV_EXCL_LINE
323 : + p->name() // LCOV_EXCL_LINE
324 : + "\" (from the plugin definition -- CPPTHREAD_PLUGIN_START), but expected \"" // LCOV_EXCL_LINE
325 : + name // LCOV_EXCL_LINE
326 : + "\" (from the plugin factor definition -- CPPTHREAD_PLUGIN_END)."); // LCOV_EXCL_LINE
327 : }
328 :
329 1 : detail::plugin_repository::instance().register_plugin(p);
330 1 : }
331 :
332 :
333 :
334 :
335 :
336 :
337 :
338 :
339 :
340 :
341 :
342 :
343 :
344 :
345 :
346 :
347 : /** \class plugin
348 : * \brief The plugin class to manage the plugin parameters.
349 : *
350 : * Each plugin gets loaded with the dlopen() system function. The plugin
351 : * is created by the plugin factory.
352 : *
353 : * The plugin includes a description which is expected to be created with
354 : * the CPPTHREAD_PLUGIN_START() macro. This will automatically verify that
355 : * the parameters are mostly correct at compile time.
356 : *
357 : * The end of the description happens by adding the CPPTHREAD_PLUGIN_END()
358 : * macro which is then followed by your own plugin functions.
359 : */
360 :
361 :
362 :
363 :
364 : /** \brief Initialize the plugin with its factory.
365 : *
366 : * This constructor saves the plugin factory pointer. This is used by the
367 : * various functions returning plugin definition parameters.
368 : *
369 : * \param[in] factory The factory that created this plugin.
370 : */
371 1 : plugin::plugin(plugin_factory const & factory)
372 1 : : f_factory(factory)
373 : {
374 1 : }
375 :
376 :
377 : /** \brief For the virtual table.
378 : *
379 : * We want the plugin class to have a virtual table so we have a virtual
380 : * destructor.
381 : *
382 : * \note
383 : * At the moment this destructor is never called. We'll want to look into
384 : * a proper way to dlclose() plugins at some point.
385 : */
386 : plugin::~plugin() // LCOV_EXCL_LINE
387 : { // LCOV_EXCL_LINE
388 : } // LCOV_EXCL_LINE
389 :
390 :
391 : /** \brief Return the version of the plugin.
392 : *
393 : * This function returns the version of the plugin. This can be used to
394 : * verify that you have the correct version for a certain feature.
395 : *
396 : * \return A version structure which includes a major, minor and build version.
397 : */
398 1 : version_t plugin::version() const
399 : {
400 1 : return f_factory.definition().f_version;
401 : }
402 :
403 :
404 : /** \brief The last modification parameter.
405 : *
406 : * This function returns a timestamp representing the last time the plugin
407 : * was built.
408 : */
409 0 : time_t plugin::last_modification() const
410 : {
411 0 : return f_factory.definition().f_last_modification;
412 : }
413 :
414 :
415 : /** \brief The name of the plugin.
416 : *
417 : * This function returns the name of the plugin.
418 : *
419 : * \note
420 : * When registering the plugin, the code verifies that this parameter returns
421 : * the same name as the named used to load the plugin. In other words, we
422 : * can trust this parameter.
423 : *
424 : * \return The name of the plugin.
425 : */
426 2 : std::string plugin::name() const
427 : {
428 2 : return f_factory.definition().f_name;
429 : }
430 :
431 :
432 : /** \brief The filename of this plugin.
433 : *
434 : * This function returns the full path to the file that was loaded to make
435 : * this plugin work. This is the exact path that was used with the dlopen()
436 : * function.
437 : *
438 : * \note
439 : * Whenever the plugin registers itself via the plugin_repository class,
440 : * the plugin_repository takes that chance to save the filename to the
441 : * plugin class. This path is one to one the one used with the dlopen()
442 : * function call.
443 : *
444 : * \return The path to the plugin file.
445 : */
446 1 : std::string plugin::filename() const
447 : {
448 1 : return f_filename;
449 : }
450 :
451 :
452 : /** \brief A brief description.
453 : *
454 : * This function returns the brief description for this plugin.
455 : *
456 : * \return A brief description of the plugin.
457 : */
458 1 : std::string plugin::description() const
459 : {
460 1 : return f_factory.definition().f_description;
461 : }
462 :
463 :
464 : /** \brief A URI to a document explaining how to use this plugin.
465 : *
466 : * This function returns a URI which one can use to send the user to a website
467 : * where the user can read about this plugin.
468 : *
469 : * \return A URI to this plugin help page(s).
470 : */
471 1 : std::string plugin::help_uri() const
472 : {
473 1 : return f_factory.definition().f_help_uri;
474 : }
475 :
476 :
477 : /** \brief The icon representing this plugin.
478 : *
479 : * The function returns a filename, a resource name, or a URL to a file
480 : * representing this plugin. In other words, the Logo representing this
481 : * plugin.
482 : *
483 : * \return The filename, resource name, or URL to an image.
484 : */
485 1 : std::string plugin::icon() const
486 : {
487 1 : return f_factory.definition().f_icon;
488 : }
489 :
490 :
491 : /** \brief Return a list of tags.
492 : *
493 : * This function returns a list of tags that categorizes the plugin in various
494 : * ways. For example, all plugins that deal with emails can use the tag
495 : * "email".
496 : *
497 : * \return A set of strings representing categories or tags.
498 : */
499 1 : string_set_t plugin::categorization_tags() const
500 : {
501 1 : return f_factory.definition().f_categorization_tags;
502 : }
503 :
504 :
505 : /** \brief List of dependencies.
506 : *
507 : * This function returns a list of dependencies that this plugin needs to
508 : * run properly. You do not have to specify all the dependencies when you
509 : * want to load a plugin. The plugin_collection::load_plugins() will
510 : * automatically add those dependencies as it finds them.
511 : *
512 : * \return The list of plugin names that need to be loaded for this plugin
513 : * to work properly.
514 : */
515 2 : string_set_t plugin::dependencies() const
516 : {
517 2 : return f_factory.definition().f_dependencies;
518 : }
519 :
520 :
521 : /** \bfief List of conflicts.
522 : *
523 : * This function returns a set of strings with names of plugins that are in
524 : * conflict with this plugin. For example, you may create two plugins so
525 : * send emails and installing both would mean that emails would be sent
526 : * twice. Using this makes sure that you can't actually load both plugins
527 : * simultaneously (if that happens, then an error occurs and the load
528 : * fails).
529 : *
530 : * \return A list of plugin names that are in conflict with this plugin.
531 : */
532 2 : string_set_t plugin::conflicts() const
533 : {
534 2 : return f_factory.definition().f_conflicts;
535 : }
536 :
537 :
538 : /** \brief List of suggestions.
539 : *
540 : * This function returns a set of strings with various suggestions of other
541 : * plugins that add functionality to this plugin.
542 : *
543 : * \return The list of suggestions for this plugin.
544 : */
545 1 : string_set_t plugin::suggestions() const
546 : {
547 1 : return f_factory.definition().f_suggestions;
548 : }
549 :
550 :
551 : /** \brief Give the plugin a change to properly initialize itself.
552 : *
553 : * The order in which plugins are loaded is generally just alphabetical
554 : * which in most cases is not going to cut it well when initializing them.
555 : * Instead, the library offers a dependency list in each plugin so plugins
556 : * that are depended on can be initialized first (i.e. if A depends on B,
557 : * then B gets initialized first).
558 : *
559 : * The bootstrap() is that function that gets called once all the plugins
560 : * were loaded. This gives you the ability to properly initialize your
561 : * plugins.
562 : *
563 : * \param[in] data The user data as defined with the
564 : * plugin_collection::set_data() function.
565 : *
566 : * \sa plugin_collection::set_data()
567 : */
568 1 : void plugin::bootstrap(void * data)
569 : {
570 1 : snap::NOT_USED(data);
571 1 : }
572 :
573 :
574 :
575 :
576 :
577 :
578 :
579 :
580 :
581 :
582 :
583 :
584 :
585 :
586 :
587 :
588 :
589 :
590 :
591 : /** \class plugin_paths
592 : * \brief The list of paths.
593 : *
594 : * The plugin_paths holds a list of paths that are used to search the
595 : * plugins. By default this list is empty which is viewed as a list
596 : * of having just "." as the path by the plugin_names::find_plugins().
597 : *
598 : * To add plugin paths, use the push(), add(), and set() functions to
599 : * see how to add new paths. In most cases, the set() function is ideal
600 : * if you read a list of paths from a configuration file.
601 : *
602 : * The set() function accepts a list of paths separated by colon (:)
603 : * characters. It is often used from an environment variable or a
604 : * parameter in a configuration file.
605 : */
606 :
607 :
608 :
609 : /** \brief Get the number of paths defined in this set of paths.
610 : *
611 : * This function returns the number of paths this set has.
612 : */
613 32 : std::size_t plugin_paths::size() const
614 : {
615 32 : return f_paths.size();
616 : }
617 :
618 :
619 : /** \brief Get the path at the specified index.
620 : *
621 : * This function retrieves the path defined at \p idx.
622 : *
623 : * \param[in] idx The index of the path you are trying to retrieve.
624 : *
625 : * \return The path defined at index \p idx. If \p idx is too large, then
626 : * this function returns an empty string.
627 : */
628 82 : std::string plugin_paths::at(std::size_t idx) const
629 : {
630 82 : if(idx >= f_paths.size())
631 : {
632 32 : return std::string();
633 : }
634 :
635 50 : return f_paths[idx];
636 : }
637 :
638 :
639 : /** \brief Change the allow-redirect flag.
640 : *
641 : * This function is used to switch the allow-redirect flag to true or false.
642 : * By default the flag is false.
643 : *
644 : * Setting the flag to true means that a user can define a relative path
645 : * outside of the current path (i.e. which starts with "../").
646 : *
647 : * \remarks
648 : * Most often, paths for plugin locations are full root paths so this flag
649 : * doesn't apply to those. Also, the canonicalization checks in memory
650 : * strings only. It will not verify that the path is not going outside of
651 : * the current path through softlink files.
652 : *
653 : * \param[in] allow Whether to allow redirects in paths.
654 : *
655 : * \sa get_allow_redirects()
656 : * \sa canonicalize()
657 : */
658 51 : void plugin_paths::set_allow_redirects(bool allow)
659 : {
660 51 : f_allow_redirects = allow;
661 51 : }
662 :
663 :
664 : /** \brief Check whether redirects are allowed or not.
665 : *
666 : * This function returns true if redirects are allowed, false otherwise.
667 : *
668 : * When canonicalizing a path, a ".." outside of the current directory
669 : * is viewed as a redirect. These are not allowed by default for obvious
670 : * security reasons. When false and such a path is detected, the
671 : * canonicalize() function throws an error.
672 : *
673 : * \return true when redirects are allowed.
674 : *
675 : * \sa set_allow_redirects()
676 : */
677 3 : bool plugin_paths::get_allow_redirects() const
678 : {
679 3 : return f_allow_redirects;
680 : }
681 :
682 :
683 : /** \brief Canonicalize the input path.
684 : *
685 : * This function canonicalize the input path so that two paths referencing
686 : * the same files can easily be compared against each other.
687 : *
688 : * \note
689 : * We do not test the current local system (for one reason, the path may
690 : * not refer to a local file), so we will not detect soft links and relative
691 : * versus full path equivalents.
692 : *
693 : * \exception cppthread_invalid_error
694 : * The input \p path cannot be an empty string. Also, when the
695 : * allow-redirects flag (see the set_allow_redirects() function) is
696 : * set to false (the default), then the exception is raised if the path
697 : * starts with "../".
698 : *
699 : * \param[in] path The path to be converted.
700 : *
701 : * \return The canonicalized path.
702 : *
703 : * \sa push()
704 : */
705 116 : plugin_paths::path_t plugin_paths::canonicalize(path_t const & path)
706 : {
707 116 : if(path.empty())
708 : {
709 3 : throw cppthread_invalid_error("path cannot be an empty string.");
710 : }
711 :
712 113 : bool const is_root(path[0] == '/');
713 :
714 : // canonicalize the path (exactly one "/" between each segment)
715 : //
716 226 : std::vector<std::string> segments;
717 113 : snap::tokenize_string(segments, path, "/", true);
718 :
719 113 : if(segments.empty())
720 : {
721 : return is_root
722 : ? std::string("/")
723 30 : : std::string(".");
724 : }
725 :
726 485 : for(std::size_t idx(0); idx < segments.size(); ++idx)
727 : {
728 407 : if(segments[idx] == ".")
729 : {
730 20 : segments.erase(segments.begin() + idx);
731 20 : --idx;
732 : }
733 387 : else if(segments[idx] == "..")
734 : {
735 83 : if(idx > 0
736 83 : && segments[idx - 1] != "..")
737 : {
738 54 : segments.erase(segments.begin() + idx);
739 54 : --idx;
740 54 : segments.erase(segments.begin() + idx);
741 54 : --idx;
742 : }
743 29 : else if(idx == 0
744 25 : && is_root)
745 : {
746 12 : segments.erase(segments.begin());
747 12 : --idx;
748 : }
749 17 : else if(!f_allow_redirects)
750 : {
751 : throw cppthread_invalid_error(
752 : "the path \""
753 10 : + path
754 15 : + "\" going outside of the allowed range.");
755 : }
756 : }
757 : }
758 :
759 78 : if(segments.empty())
760 : {
761 : return is_root
762 : ? std::string("/")
763 2 : : std::string(".");
764 : }
765 :
766 : return is_root
767 196 : ? '/' + snap::join_strings(segments, "/")
768 152 : : snap::join_strings(segments, "/");
769 : }
770 :
771 :
772 : /** \brief Add one path to this set of paths.
773 : *
774 : * This function is used to add a path to this set of paths.
775 : *
776 : * Before adding the new path, we make sure that it is not already defined
777 : * in the existing set. Adding the same path more than once is not useful.
778 : * Only the first instance would be useful and the second would generate
779 : * a waste of time.
780 : *
781 : * In many cases, you will want to use the add() function instead as it
782 : * is capable to add many paths separated by colons all at once.
783 : *
784 : * \note
785 : * The function calls canonicalize() on the input path. If the path is
786 : * considered invalid, then an exception is raised.
787 : *
788 : * \param[in] path The path to be added.
789 : *
790 : * \sa add()
791 : * \sa canonicalize()
792 : */
793 33 : void plugin_paths::push(path_t const & path)
794 : {
795 66 : path_t const canonicalized(canonicalize(path));
796 33 : auto it(std::find(f_paths.begin(), f_paths.end(), canonicalized));
797 33 : if(it == f_paths.end())
798 : {
799 31 : f_paths.push_back(canonicalized);
800 : }
801 33 : }
802 :
803 :
804 : /** \brief Erase the specified path from this list.
805 : *
806 : * This function searches for the specified \p path and remove it from the
807 : * list. If not present in the list, then nothing happens.
808 : *
809 : * \param[in] path The path to be removed.
810 : */
811 4 : void plugin_paths::erase(std::string const & path)
812 : {
813 4 : auto it(std::find(f_paths.begin(), f_paths.end(), path));
814 4 : if(it != f_paths.end())
815 : {
816 3 : f_paths.erase(it);
817 : }
818 4 : }
819 :
820 :
821 : /** \brief Add one set of paths to this set of paths.
822 : *
823 : * This function is used to add a set of colon separated paths defined in
824 : * one string. The function automatically separate each path at the colon
825 : * and adds the resulting paths to this object using the add() function.
826 : *
827 : * \note
828 : * The function further removes blanks (space, tab, newline, carriage
829 : * return) at the start and end of each path. Such characters should not
830 : * be supported at those locations by any sensible file systems anyway.
831 : *
832 : * \param[in] path The path to be added.
833 : *
834 : * \sa add()
835 : */
836 10 : void plugin_paths::add(std::string const & set)
837 : {
838 20 : std::vector<std::string> paths;
839 10 : snap::tokenize_string(paths, set, ":", true, {' ', '\t', '\r', '\n'});
840 38 : for(auto const & p : paths)
841 : {
842 28 : push(p);
843 : }
844 10 : }
845 :
846 :
847 :
848 :
849 :
850 :
851 :
852 :
853 :
854 :
855 :
856 :
857 :
858 :
859 :
860 : /** \class plugin_names
861 : * \brief Manage a list of plugins to be loaded.
862 : *
863 : * Whenever you start a plugin system, you need to specify the list of
864 : * plugins you want to load. This is done by adding names to a
865 : * plugin_names object.
866 : *
867 : * In order to generate a list of plugin_names, you must first setup
868 : * a plugin_paths which defines the location where the plugins are
869 : * searched, which this class does each time a plugin name is added
870 : * to the object (see push(), add() and find_plugins() for details).
871 : *
872 : * See the plugin_collection class for more information and an example
873 : * on how to load plugins for a system.
874 : */
875 :
876 :
877 :
878 :
879 : /** \brief Initialize a plugin_names object.
880 : *
881 : * The list of plugins has to be defined in a plugin_names object. It is done
882 : * this way because the list of plugin_paths becomes read-only once in the
883 : * list of paths of the plugin_names object. We actually make a deep copy of
884 : * the paths so we can be sure you can't add more paths later.
885 : *
886 : * The \p script_names parameter is used to determine whether the name
887 : * validation should prevent a plugin from using a reserved keyword (as
888 : * per ECMAScript). This is useful if you plan to have plugins used in
889 : * scripts and in there the plugins can be referenced by name.
890 : *
891 : * \param[in] paths The list of paths to use to search the plugins.
892 : * \param[in] prevent_script_names true if the plugin names are going to
893 : * be used in scripts.
894 : */
895 10 : plugin_names::plugin_names(plugin_paths const & paths, bool prevent_script_names)
896 : : f_paths(paths)
897 10 : , f_prevent_script_names(prevent_script_names)
898 : {
899 10 : }
900 :
901 :
902 : /** \brief Validate the name of a plugin.
903 : *
904 : * Plugin names are limited to the following regular expression:
905 : *
906 : * \code
907 : * [A-Za-z_][A-Za-z0-9_]*
908 : * \endcode
909 : *
910 : * Further, the names can't be reserved keywords (as per ECMAScript) when the
911 : * \p script_names parameter of the constructor was set to true. So plugins
912 : * naming a keyword are rejected.
913 : *
914 : * \param[in] name The string to verify as a plugin name.
915 : *
916 : * \return true if the name is considered valid.
917 : */
918 322 : bool plugin_names::validate(name_t const & name)
919 : {
920 322 : if(name.length() == 0)
921 : {
922 4 : return false;
923 : }
924 :
925 636 : if(name[0] != '_'
926 162 : && (name[0] < 'a' || name[0] > 'z')
927 378 : && (name[0] < 'A' || name[0] > 'Z'))
928 : {
929 8 : return false;
930 : }
931 :
932 893 : for(auto c : name)
933 : {
934 717 : if(c != '_'
935 560 : && (c < 'a' || c > 'z')
936 211 : && (c < 'A' || c > 'Z')
937 159 : && (c < '0' || c > '9'))
938 : {
939 134 : return false;
940 : }
941 : }
942 :
943 : // the name is considered to be a valid word, make sure it isn't an
944 : // ECMAScript reserved keyword if the user asked to prevent script names
945 : //
946 352 : if(f_prevent_script_names
947 176 : && is_emcascript_reserved(name))
948 : {
949 38 : return false;
950 : }
951 :
952 138 : return true;
953 : }
954 :
955 :
956 : /** \brief Check whether the input word is an ECMAScript reserved keyword.
957 : *
958 : * This function quickly checks whether the input \p word is considered a
959 : * reserved keyword by ECMAScript.
960 : *
961 : * The reserved keywords in ECMAScript 2022 are:
962 : *
963 : * \code
964 : * await break case catch class const continue debugger default delete do
965 : * else enum export extends false finally for function if import in
966 : * instanceof new null return super switch this throw true try typeof
967 : * var void while with yield
968 : * \endcode
969 : *
970 : * \param[in] word The word to be checked.
971 : *
972 : * \return true if \p word is a reserved keyword.
973 : */
974 104 : bool plugin_names::is_emcascript_reserved(std::string const & word)
975 : {
976 104 : switch(word[0])
977 : {
978 2 : case 'a':
979 2 : if(word == "await")
980 : {
981 1 : return true;
982 : }
983 1 : break;
984 :
985 2 : case 'b':
986 2 : if(word == "break")
987 : {
988 1 : return true;
989 : }
990 1 : break;
991 :
992 6 : case 'c':
993 12 : if(word == "case"
994 5 : || word == "catch"
995 4 : || word == "class"
996 3 : || word == "const"
997 8 : || word == "continue")
998 : {
999 5 : return true;
1000 : }
1001 1 : break;
1002 :
1003 5 : case 'd':
1004 10 : if(word == "debugger"
1005 4 : || word == "default"
1006 3 : || word == "delete"
1007 7 : || word == "do")
1008 : {
1009 4 : return true;
1010 : }
1011 1 : break;
1012 :
1013 5 : case 'e':
1014 10 : if(word == "else"
1015 4 : || word == "enum"
1016 3 : || word == "export"
1017 7 : || word == "extends")
1018 : {
1019 4 : return true;
1020 : }
1021 1 : break;
1022 :
1023 5 : case 'f':
1024 10 : if(word == "false"
1025 4 : || word == "finally"
1026 3 : || word == "for"
1027 7 : || word == "function")
1028 : {
1029 4 : return true;
1030 : }
1031 1 : break;
1032 :
1033 5 : case 'i':
1034 10 : if(word == "if"
1035 4 : || word == "import"
1036 3 : || word == "in"
1037 7 : || word == "instanceof")
1038 : {
1039 4 : return true;
1040 : }
1041 1 : break;
1042 :
1043 3 : case 'n':
1044 6 : if(word == "new"
1045 3 : || word == "null")
1046 : {
1047 2 : return true;
1048 : }
1049 1 : break;
1050 :
1051 2 : case 'r':
1052 2 : if(word == "return")
1053 : {
1054 1 : return true;
1055 : }
1056 1 : break;
1057 :
1058 3 : case 's':
1059 6 : if(word == "super"
1060 3 : || word == "switch")
1061 : {
1062 2 : return true;
1063 : }
1064 1 : break;
1065 :
1066 6 : case 't':
1067 12 : if(word == "this"
1068 5 : || word == "throw"
1069 4 : || word == "true"
1070 3 : || word == "try"
1071 8 : || word == "typeof")
1072 : {
1073 5 : return true;
1074 : }
1075 1 : break;
1076 :
1077 3 : case 'v':
1078 6 : if(word == "var"
1079 3 : || word == "void")
1080 : {
1081 2 : return true;
1082 : }
1083 1 : break;
1084 :
1085 3 : case 'w':
1086 6 : if(word == "while"
1087 3 : || word == "with")
1088 : {
1089 2 : return true;
1090 : }
1091 1 : break;
1092 :
1093 2 : case 'y':
1094 2 : if(word == "yield")
1095 : {
1096 1 : return true;
1097 : }
1098 1 : break;
1099 :
1100 : }
1101 :
1102 66 : return false;
1103 : }
1104 :
1105 :
1106 :
1107 : /** \brief Convert a bare name in a filename.
1108 : *
1109 : * In most cases, your users will want to specify a bare name as a plugin
1110 : * name and this library is then responsible for finding the corresponding
1111 : * plugin on disk using the specified paths.
1112 : *
1113 : * This function transforms such a a bare name in a filename. It goes through
1114 : * the list of paths (see the plugin_names() constructor) and stops once the
1115 : * first matching plugin filename was found.
1116 : *
1117 : * If no such plugin is found, the function returns an empty string. Whether
1118 : * to generate an error on such is your responsibility.
1119 : *
1120 : * \note
1121 : * This function is used by add_name() which adds the name and the path in
1122 : * the list of plugin names.
1123 : *
1124 : * \param[in] name A bare plugin name.
1125 : *
1126 : * \return The full filename matching this bare plugin name or an empty string.
1127 : */
1128 23 : plugin_names::filename_t plugin_names::to_filename(name_t const & name)
1129 : {
1130 58 : auto check = [&name](plugin_paths::path_t const & path)
1131 176 : {
1132 : // "path/<name>.so"
1133 : //
1134 70 : filename_t filename(path);
1135 35 : filename += name;
1136 35 : filename += ".so";
1137 35 : if(access(filename.c_str(), R_OK | X_OK) == 0)
1138 : {
1139 2 : return filename;
1140 : }
1141 :
1142 : // "path/lib<name>.so"
1143 : //
1144 33 : filename = path;
1145 33 : filename += "lib";
1146 33 : filename += name;
1147 33 : filename += ".so";
1148 33 : if(access(filename.c_str(), R_OK | X_OK) == 0)
1149 : {
1150 5 : return filename;
1151 : }
1152 :
1153 : // "path/<name>/<name>.so"
1154 : //
1155 28 : filename = path;
1156 28 : filename += name;
1157 28 : filename += '/';
1158 28 : filename += name;
1159 28 : filename += ".so";
1160 28 : if(access(filename.c_str(), R_OK | X_OK) == 0)
1161 : {
1162 2 : return filename;
1163 : }
1164 :
1165 : // "path/<name>/lib<name>.so"
1166 : //
1167 26 : filename = path;
1168 26 : filename += name;
1169 26 : filename += "/lib";
1170 26 : filename += name;
1171 26 : filename += ".so";
1172 26 : if(access(filename.c_str(), R_OK | X_OK) == 0)
1173 : {
1174 2 : return filename;
1175 : }
1176 :
1177 24 : return filename_t();
1178 23 : };
1179 :
1180 23 : std::size_t const max(f_paths.size());
1181 41 : for(std::size_t idx(0); idx < max; ++idx)
1182 : {
1183 43 : plugin_paths::path_t path(f_paths.at(idx));
1184 25 : path += '/';
1185 43 : filename_t const filename(check(path));
1186 25 : if(!filename.empty())
1187 : {
1188 7 : return filename;
1189 : }
1190 : }
1191 :
1192 16 : if(max == 0)
1193 : {
1194 : // if not paths were supplied, try the local folder
1195 : //
1196 10 : return check("./");
1197 : }
1198 :
1199 6 : return filename_t();
1200 : }
1201 :
1202 :
1203 : /** \brief Add the name of a plugin to be loaded.
1204 : *
1205 : * The function adds a plugin name which the load function will load once
1206 : * it gets called. Only plugins that have their name added to this list will
1207 : * be loaded.
1208 : *
1209 : * To fill the list with all the available plugins in all the paths you
1210 : * added, you can use the find_plugins() function instead.
1211 : *
1212 : * \warning
1213 : * This function assumes that you are done calling the add_path() function.
1214 : *
1215 : * \note
1216 : * Adding the same name multiple times is allowed and does not create any
1217 : * side effects. Only the first instance of the name is kept.
1218 : *
1219 : * \exception cppthread_invalid_error
1220 : * The names are run through the validate_name() function to make sure they
1221 : * are compatible with scripts. Also, we prevent the adding of the reserved
1222 : * name called "server".
1223 : *
1224 : * \param[in] name The name or filename of a plugin to be loaded.
1225 : */
1226 8 : void plugin_names::push(name_t const & name)
1227 : {
1228 16 : name_t n;
1229 16 : filename_t fn;
1230 :
1231 8 : std::string::size_type pos(name.rfind('/'));
1232 8 : if(pos != std::string::npos)
1233 : {
1234 : // we already received a path, extract the name and avoid calling
1235 : // to_filename()
1236 : //
1237 4 : ++pos;
1238 8 : if(name[pos + 0] == 'l'
1239 4 : && name[pos + 1] == 'i'
1240 8 : && name[pos + 2] == 'b')
1241 : {
1242 4 : pos += 3;
1243 : }
1244 4 : name_t::size_type l(name.length());
1245 4 : if(l >= 3
1246 4 : && name[l - 1] == 'o'
1247 4 : && name[l - 2] == 's'
1248 8 : && name[l - 3] == '.')
1249 : {
1250 4 : l -= 3;
1251 : }
1252 4 : n = name.substr(pos, l - pos);
1253 4 : if(!validate(n))
1254 : {
1255 : throw cppthread_invalid_error(
1256 : "invalid plugin name in \""
1257 2 : + n
1258 3 : + "\" (from path \""
1259 3 : + name
1260 3 : + "\").");
1261 : }
1262 3 : fn = name;
1263 : }
1264 : else
1265 : {
1266 4 : if(!validate(name))
1267 : {
1268 : throw cppthread_invalid_error(
1269 : "invalid plugin name in \""
1270 2 : + name
1271 3 : + "\".");
1272 : }
1273 3 : fn = to_filename(name);
1274 3 : if(fn.empty())
1275 : {
1276 : throw cppthread_not_found(
1277 : "plugin named \""
1278 2 : + name
1279 3 : + "\" not found in any of the specified paths.");
1280 : }
1281 2 : n = name;
1282 : }
1283 :
1284 5 : if(n == "server")
1285 : {
1286 1 : throw cppthread_invalid_error("the name \"server\" is reserved for the main running process.");
1287 : }
1288 :
1289 4 : f_names[n] = fn;
1290 4 : }
1291 :
1292 :
1293 : /** \brief Add a comma separated list of names.
1294 : *
1295 : * If you offer your users a way to define a list of names separated by
1296 : * commas, this function is exactly what you need to add all those names
1297 : * in one go.
1298 : *
1299 : * The names are also trimmed of blanks (spaces, tables, newline, and
1300 : * carriage returns are all removed).
1301 : *
1302 : * \param[in] set A comma separated list of plugin names.
1303 : */
1304 1 : void plugin_names::add(std::string const & set)
1305 : {
1306 2 : std::vector<std::string> names;
1307 1 : snap::tokenize_string(names, set, ",", true, {' ', '\t', '\r', '\n'});
1308 2 : for(auto const & n : names)
1309 : {
1310 1 : push(n);
1311 : }
1312 1 : }
1313 :
1314 :
1315 : /** \brief Retrieve the map of name/filename.
1316 : *
1317 : * This function is used to retrieve a copy of the map storing the plugin
1318 : * name and filename pairs. This represents the complete list of plugins
1319 : * to be loaded.
1320 : *
1321 : * \note
1322 : * When the find_plugins() function was called to generate the list of
1323 : * name/filename pairs, then it is known that the filename does exist on
1324 : * disk. However, this doesn't mean the plugin can be loaded (or even that
1325 : * the file is indeed a cppthread compatible plugin).
1326 : *
1327 : * \note
1328 : * The push() function, when called with a full filename, will extract the
1329 : * name from that filename and save that directly to the map. In other words,
1330 : * it does not verify whether the file exists.
1331 : *
1332 : * \return The map of name/filename pairs.
1333 : */
1334 8 : plugin_names::names_t plugin_names::names() const
1335 : {
1336 8 : return f_names;
1337 : }
1338 :
1339 :
1340 : /** \brief Read all the available plugins in the specified paths.
1341 : *
1342 : * There are two ways that this class can be used:
1343 : *
1344 : * * First, the user specifies an exact list of names. In that case, you want
1345 : * to use the add_name() function to add all the names from your list.
1346 : *
1347 : * * Second, the user adds the plugins to a directory and the idea is to
1348 : * have all of them loaded. In this second case, you use the find_plugins()
1349 : * which runs glob() in those paths to retrieve all the plugins that can
1350 : * be loaded.
1351 : *
1352 : * Note that this function only finds the plugins. It doesn't load them. To
1353 : * then load all of these plugins, use the load_plugins() function.
1354 : *
1355 : * The function accepts a prefix and a suffix which are used to search for
1356 : * the plugins. These are expected to be used when you are looking for a
1357 : * set of plugins that make use of such prefix/suffix to group said plugins
1358 : * in some way. For example, you could have output plugins that use a prefix
1359 : * such as:
1360 : *
1361 : * \code
1362 : * output_bare
1363 : * output_decorated
1364 : * output_html
1365 : * output_json
1366 : * \endcode
1367 : *
1368 : * So calling this function with the prefix set to `output_` will only load
1369 : * these four plugins and ignore the others (i.e. the `input_`, for example).
1370 : *
1371 : * \warning
1372 : * You must call the add_path() function with all the paths that you want
1373 : * to support before calling this function.
1374 : *
1375 : * \param[in] prefix The prefix used to search the plugins.
1376 : * \param[in] suffix The suffix used to search the plugins.
1377 : */
1378 2 : void plugin_names::find_plugins(name_t const & prefix, name_t const & suffix)
1379 : {
1380 4 : snap::glob_to_list<std::vector<std::string>> glob;
1381 :
1382 2 : std::size_t max(f_paths.size());
1383 8 : for(std::size_t idx(0); idx < max; ++idx)
1384 : {
1385 : glob.read_path<
1386 : snap::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS
1387 6 : , snap::glob_to_list_flag_t::GLOB_FLAG_PERIOD>(f_paths.at(idx) + "/" + prefix + "*" + suffix + ".so");
1388 : glob.read_path<
1389 : snap::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS
1390 6 : , snap::glob_to_list_flag_t::GLOB_FLAG_PERIOD>(f_paths.at(idx) + "/*/" + prefix + "*" + suffix + ".so");
1391 : }
1392 :
1393 4 : for(auto n : glob)
1394 : {
1395 2 : push(n);
1396 : }
1397 2 : }
1398 :
1399 :
1400 :
1401 :
1402 :
1403 :
1404 :
1405 :
1406 :
1407 :
1408 :
1409 :
1410 :
1411 :
1412 :
1413 :
1414 : /** \class plugin_collection
1415 : * \brief Handle a collection of plugins.
1416 : *
1417 : * In order to create a plugin_collection, you first need to have a list
1418 : * of plugin names (and filenames), a.k.a. a plugin_names object.
1419 : *
1420 : * In order to create a plugin_names object, you first need to create a
1421 : * plugin_paths object. These are lists of paths where the plugin files
1422 : * are searched before being loaded.
1423 : *
1424 : * The order is important and prevents you from modifying the paths while
1425 : * adding names, and modifying the paths and names which building the
1426 : * collection of plugins.
1427 : *
1428 : * \code
1429 : * plugin_paths p;
1430 : * p.add("/usr/local/lib/snaplogger/plugins:/usr/lib/snaplogger/plugins");
1431 : *
1432 : * plugin_names n(p, true);
1433 : * n.add("network, cloud-system");
1434 : * // or: n.find_plugins(); to read all the plugins
1435 : *
1436 : * plugin_collection c(n);
1437 : * c.set_data(&my_app);
1438 : * c.load_plugins();
1439 : * \endcode
1440 : *
1441 : * The plugin_names makes a deep copy of the plugin_paths.
1442 : *
1443 : * The plugin_collection makes a deep copy of the plugin_names (meaning that
1444 : * it also makes a new copy of the plugin_paths).
1445 : */
1446 :
1447 :
1448 :
1449 : /** \brief Initialize a collection of plugins.
1450 : *
1451 : * The input parameter is a list of plugin names that can be loaded with
1452 : * the load_plugins(). Before calling the load_plugins() function, you
1453 : * may want to call the set_data() function in order to define the pointer
1454 : * that will be passed to the bootstrap() function.
1455 : *
1456 : * The f_names is a copy of your \p names parameter. That copy may be modified
1457 : * if some of the plugins have dependencies that were not listed in the input
1458 : * \p names object. In other words, if A depends on B, you only need to
1459 : * specify A as the plugin you want to load. The load_plugins() function
1460 : * will automatically know that it has to then load B.
1461 : *
1462 : * \param[in] names A list of plugins to be loaded.
1463 : *
1464 : * \sa set_data();
1465 : * \sa load_plugins();
1466 : */
1467 1 : plugin_collection::plugin_collection(plugin_names const & names)
1468 1 : : f_names(names)
1469 : {
1470 1 : }
1471 :
1472 :
1473 : /** \brief Set user data for when the bootstrap function gets called.
1474 : *
1475 : * If you need a reference back to another object from your plugins, set
1476 : * it in here. In the Snap! environment, we use the snap_child object.
1477 : * That way all the plugins have access to everything in the server.
1478 : *
1479 : * \param[in] data Your data pointer.
1480 : *
1481 : * \sa plugin::bootstrap()
1482 : */
1483 1 : void plugin_collection::set_data(void * data)
1484 : {
1485 1 : f_data = data;
1486 1 : }
1487 :
1488 :
1489 : /** \brief Load all the plugins in this collection.
1490 : *
1491 : * When you create a collection, you pass a list of names (via the
1492 : * plugin_names object). This function uses that list to load all the
1493 : * corresponding plugins from disk.
1494 : *
1495 : * The constructor of the plugins will be called and you can do local
1496 : * initialization as required at that point. Do not attempt to initialize
1497 : * your system just yet. Instead, this function will also call the
1498 : * plugin::bootstrap() function with your user data (see set_data() for
1499 : * details about that parameter). In that function, you can start deep
1500 : * initialization that involves other plugins, such as dependencies.
1501 : *
1502 : * The order in which the bootstrap() functions will be called is well
1503 : * defined via the list of ordered plugins. The order makes use of the
1504 : * plugin dependency list (see plugin::dependencies() for details) and
1505 : * the name of the plugin. If two plugins do not depend on each other,
1506 : * then they get sorted alphabeticall (so A always comes before B unless
1507 : * A depends on B, then B would be initialized first).
1508 : *
1509 : * The order in which the plugins get initialized is very important if
1510 : * you use the signal system since it means that the order in which signals
1511 : * get processed depends on the order in which the plugins were sorted.
1512 : * At the moment, the one drawback is that all the signals of one plugin
1513 : * get added at the same time (via the bootstrap() function) which means
1514 : * that signal A:S1 will not happen before B:S1 if A is initialized first.
1515 : * In our experence, it has been pretty reliable in most cases. We had a
1516 : * very few cases were something would not happen quite in order and we
1517 : * had to add another message to fix the issue (i.e. if A:S1 happens
1518 : * before B:S1 but you need A to react after B:S1 made some changes to
1519 : * the state, you can have a second message A:S2 sent afterward, and
1520 : * B:S2 can be ignored).
1521 : *
1522 : * \return true if the loading worked on all the plugins, false otherwise.
1523 : */
1524 1 : bool plugin_collection::load_plugins()
1525 : {
1526 2 : guard lock(f_mutex);
1527 :
1528 : // TODO/TBD? add a "server" plugin which represents the main process
1529 : //f_plugins.insert("server", server.get());
1530 :
1531 1 : detail::plugin_repository & repository(detail::plugin_repository::instance());
1532 1 : bool changed(true);
1533 1 : bool good(true);
1534 2 : while(changed)
1535 : {
1536 1 : changed = false;
1537 :
1538 2 : plugin_names::names_t names(f_names.names());
1539 2 : for(auto const & name_filename : names)
1540 : {
1541 : // the main process is considered to be the "server" plugin and it
1542 : // will eventually be added to the list under that name, so we can't
1543 : // allow this name here
1544 : //
1545 : // Note: this should not happen since we don't allow the addition of
1546 : // the "server" name to the list of names (see plugin_names::push()
1547 : // for details)
1548 : //
1549 1 : if(name_filename.first == "server")
1550 : {
1551 : log << log_level_t::error // LCOV_EXCL_LINE
1552 : << "a plugin cannot be called \"server\"." // LCOV_EXCL_LINE
1553 : << end; // LCOV_EXCL_LINE
1554 : good = false; // LCOV_EXCL_LINE
1555 : continue; // LCOV_EXCL_LINE
1556 : }
1557 :
1558 2 : plugin::pointer_t p(repository.get_plugin(name_filename.second));
1559 1 : if(p == nullptr)
1560 : {
1561 0 : log << cppthread::log_level_t::fatal
1562 0 : << "plugin \""
1563 0 : << name_filename.first
1564 0 : << "\" not found."
1565 0 : << end;
1566 0 : good = false;
1567 0 : continue;
1568 : }
1569 :
1570 2 : string_set_t const conflicts1(p->conflicts());
1571 1 : for(auto & op : f_plugins_by_name)
1572 : {
1573 : // the conflicts can be indicated in either direction so we
1574 : // have to test both unless one is true then we do not have to
1575 : // test the other
1576 : //
1577 0 : bool in_conflict(conflicts1.find(op.first) != conflicts1.end());
1578 0 : if(!in_conflict)
1579 : {
1580 0 : string_set_t const conflicts2(op.second->conflicts());
1581 0 : in_conflict = conflicts2.find(op.first) != conflicts2.end();
1582 : }
1583 0 : if(in_conflict)
1584 : {
1585 0 : log << cppthread::log_level_t::fatal
1586 0 : << "plugin \""
1587 0 : << op.first
1588 0 : << "\" is in conflict with \""
1589 0 : << name_filename.first
1590 0 : << "\"."
1591 0 : << end;
1592 0 : good = false;
1593 0 : continue;
1594 : }
1595 : }
1596 :
1597 2 : string_set_t const dependencies(p->dependencies());
1598 1 : for(auto & d : dependencies)
1599 : {
1600 0 : if(names.find(d) == names.end())
1601 : {
1602 0 : f_names.push(d);
1603 0 : changed = true;
1604 : }
1605 : }
1606 :
1607 1 : f_plugins_by_name[name_filename.first] = p;
1608 : }
1609 : }
1610 :
1611 : // set the f_ordered_plugins with the default order as alphabetical,
1612 : // although we check dependencies to properly reorder as expected
1613 : // by what each plugin tells us what its dependencies are
1614 : //
1615 2 : for(auto const & p : f_plugins_by_name)
1616 : {
1617 1 : auto it(std::find_if(
1618 : f_ordered_plugins.begin(),
1619 : f_ordered_plugins.end(),
1620 0 : [&p](auto const & plugin)
1621 0 : {
1622 0 : return plugin->dependencies().find(p.first) != plugin->dependencies().end();
1623 1 : }));
1624 1 : if(it != f_ordered_plugins.end())
1625 : {
1626 0 : f_ordered_plugins.insert(it, p.second);
1627 : }
1628 : else
1629 : {
1630 1 : f_ordered_plugins.push_back(p.second);
1631 : }
1632 : }
1633 :
1634 : // bootstrap() functions have to be called in order to get all the
1635 : // signals registered in order! (YES!!! This one for() loop makes
1636 : // all the signals work as expected by making sure they are in a
1637 : // very specific order)
1638 : //
1639 2 : for(auto const & p : f_ordered_plugins)
1640 : {
1641 1 : p->bootstrap(f_data);
1642 : }
1643 :
1644 2 : return good;
1645 : }
1646 :
1647 :
1648 : /** \brief Check whether a given plugin is already loaded.
1649 : *
1650 : * This function checks to see whether the named plugin was loaded. If so
1651 : * the function returns true.
1652 : *
1653 : * In your code or even scripts, this function can be very useful to check
1654 : * whether a plugin is available before trying to access its functionality.
1655 : * So in other words, you can make certain plugins optional and still have
1656 : * a fully functional system, just with less capabilities.
1657 : *
1658 : * \param[in] name The name of the plugin to check for.
1659 : *
1660 : * \return True if the plugin is found, false otherwise.
1661 : */
1662 0 : bool plugin_collection::is_loaded(std::string const & name) const
1663 : {
1664 0 : return f_plugins_by_name.find(name) != f_plugins_by_name.end();
1665 : }
1666 :
1667 :
1668 :
1669 :
1670 :
1671 :
1672 :
1673 :
1674 6 : } // namespace cppthread
1675 : // vim: ts=4 sw=4 et
|