LCOV - code coverage report
Current view: top level - cppthread - plugins.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 344 388 88.7 %
Date: 2021-08-21 09:27:22 Functions: 43 48 89.6 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13