Fawkes API  Fawkes Development Version
manager.cpp
00001 
00002 /***************************************************************************
00003  *  manager.cpp - Fawkes plugin manager
00004  *
00005  *  Created: Wed Nov 15 23:31:55 2006 (on train to Cologne)
00006  *  Copyright  2006-2009  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version. A runtime exception applies to
00014  *  this software (see LICENSE.GPL_WRE file mentioned below for details).
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU Library General Public License for more details.
00020  *
00021  *  Read the full text in the LICENSE.GPL_WRE file in the doc directory.
00022  */
00023 
00024 #include <plugin/manager.h>
00025 #include <plugin/listener.h>
00026 #include <plugin/loader.h>
00027 
00028 #include <core/plugin.h>
00029 #include <core/threading/thread_collector.h>
00030 #include <core/threading/thread_initializer.h>
00031 #include <core/threading/mutex_locker.h>
00032 #include <core/exception.h>
00033 #include <logging/liblogger.h>
00034 #ifdef HAVE_INOTIFY
00035 #  include <utils/system/fam_thread.h>
00036 #endif
00037 #include <config/config.h>
00038 #include <utils/system/dynamic_module/module_manager.h>
00039 
00040 #include <algorithm>
00041 #include <cstring>
00042 #include <cstdlib>
00043 #include <cerrno>
00044 
00045 #include <sys/types.h>
00046 #include <dirent.h>
00047 
00048 namespace fawkes {
00049 #if 0 /* just to make Emacs auto-indent happy */
00050 }
00051 #endif
00052 
00053 /// @cond INTERNALS
00054 class plname_eq
00055 {
00056 public:
00057   plname_eq(std::string name) {
00058     __name = name;
00059   }
00060   bool operator()(Plugin *plugin)
00061   {
00062     return (__name == plugin->name());
00063   }
00064 private:
00065   std::string __name;
00066 };
00067 /// @endcond INTERNALS
00068 
00069 /** @class PluginManager <plugin/manager.h>
00070  * Fawkes Plugin Manager.
00071  * This class provides a manager for the plugins used in fawkes. It can
00072  * load and unload modules.
00073  *
00074  * @author Tim Niemueller
00075  */
00076 
00077 /** Constructor.
00078  * @param thread_collector thread manager plugin threads will be added to
00079  * and removed from appropriately.
00080  * @param config Fawkes configuration
00081  * @param meta_plugin_prefix Path prefix for meta plugins
00082  * @param module_flags flags to use to open plugin modules
00083  * @param init_cache true to initialize the plugin cache, false to skip this
00084  * step. Note that some functions like transmitting a list of available plugins
00085  * is unavailable until the cache has been initialized. You can defer
00086  * initialization of the cache if required.
00087  */
00088 PluginManager::PluginManager(ThreadCollector *thread_collector,
00089                              Configuration *config,
00090                              const char *meta_plugin_prefix,
00091                              Module::ModuleFlags module_flags,
00092                              bool init_cache)
00093   : ConfigurationChangeHandler(meta_plugin_prefix)
00094 {
00095   __mutex = new Mutex();
00096   this->thread_collector = thread_collector;
00097   plugin_loader = new PluginLoader(PLUGINDIR, config);
00098   plugin_loader->get_module_manager()->set_open_flags(module_flags);
00099   next_plugin_id = 1;
00100   __config = config;
00101   __meta_plugin_prefix = meta_plugin_prefix;
00102 
00103   if (init_cache) {
00104     init_pinfo_cache();
00105   }
00106 
00107   __config->add_change_handler(this);
00108 
00109 #ifdef HAVE_INOTIFY
00110   __fam_thread = new FamThread();
00111   RefPtr<FileAlterationMonitor> fam = __fam_thread->get_fam();
00112   fam->add_filter("^[^.].*\\."SOEXT"$");
00113   fam->add_listener(this);
00114   fam->watch_dir(PLUGINDIR);
00115   __fam_thread->start();
00116 #else
00117   LibLogger::log_warn("PluginManager", "File alteration monitoring not available, "
00118                                         "cannot detect changed plugins on disk.");
00119 #endif
00120 }
00121 
00122 
00123 /** Destructor. */
00124 PluginManager::~PluginManager()
00125 {
00126 #ifdef HAVE_INOTIFY
00127   __fam_thread->cancel();
00128   __fam_thread->join();
00129   delete __fam_thread;
00130 #endif
00131   __config->rem_change_handler(this);
00132   __pinfo_cache.lock();
00133   __pinfo_cache.clear();
00134   __pinfo_cache.unlock();
00135   // Unload all plugins
00136   for (rpit = plugins.rbegin(); rpit != plugins.rend(); ++rpit) {
00137     thread_collector->force_remove((*rpit)->threads());
00138     plugin_loader->unload(*rpit);
00139   }
00140   plugins.clear();
00141   plugin_ids.clear();
00142   delete plugin_loader;
00143   delete __mutex;
00144 }
00145 
00146 
00147 /** Set flags to open modules with.
00148  * @param flags flags to pass to modules when opening them
00149  */
00150 void
00151 PluginManager::set_module_flags(Module::ModuleFlags flags)
00152 {
00153   plugin_loader->get_module_manager()->set_open_flags(flags);
00154 }
00155 
00156 
00157 /** Initialize plugin info cache. */
00158 void
00159 PluginManager::init_pinfo_cache()
00160 {
00161   __pinfo_cache.lock();
00162 
00163   DIR *plugin_dir;
00164   struct dirent* dirp;
00165   const char *file_ext = "."SOEXT;
00166 
00167   if ( NULL == (plugin_dir = opendir(PLUGINDIR)) ) {
00168     throw Exception(errno, "Plugin directory %s could not be opened", PLUGINDIR);
00169   }
00170 
00171   for (unsigned int i = 0; NULL != (dirp = readdir(plugin_dir)); ++i) {
00172     char *file_name   = dirp->d_name;
00173     char *pos         = strstr(file_name, file_ext);
00174     std::string plugin_name = std::string(file_name).substr(0, strlen(file_name) - strlen(file_ext));
00175     if (NULL != pos) {
00176       try {
00177         __pinfo_cache.push_back(make_pair(plugin_name,
00178                                           plugin_loader->get_description(plugin_name.c_str())));
00179       } catch (Exception &e) {
00180         LibLogger::log_warn("PluginManager", "Could not get description of plugin %s, "
00181                             "exception follows", plugin_name.c_str());
00182         LibLogger::log_warn("PluginManager", e);
00183       }
00184     }
00185   }
00186 
00187   closedir(plugin_dir);
00188 
00189   try {
00190     Configuration::ValueIterator *i = __config->search(__meta_plugin_prefix.c_str());
00191     while (i->next()) {
00192       if (i->is_string()) {
00193         std::string p = std::string(i->path()).substr(__meta_plugin_prefix.length());
00194         std::string s = std::string("Meta: ") + i->get_string();
00195 
00196         __pinfo_cache.push_back(make_pair(p, s));
00197       }
00198     }
00199     delete i;
00200   } catch (Exception &e) {
00201   }
00202 
00203   __pinfo_cache.sort();
00204   __pinfo_cache.unlock();
00205 }
00206 
00207 /** Generate list of all available plugins.
00208  * @return list of plugins that are available, each plugin is represented by
00209  * a pair of strings. The first string is the plugin name, the second is its
00210  * description.
00211  */
00212 std::list<std::pair<std::string, std::string> >
00213 PluginManager::get_available_plugins()
00214 {
00215   std::list<std::pair<std::string, std::string> > rv;
00216 
00217   std::list<std::pair<std::string, std::string> >::iterator i;
00218   for (i = __pinfo_cache.begin(); i != __pinfo_cache.end(); ++i) {
00219     rv.push_back(*i);
00220   }
00221 
00222   return rv;
00223 }
00224 
00225 /** Get list of loaded plugins.
00226  * @return list of names of real and meta plugins currently loaded
00227  */
00228 std::list<std::string>
00229 PluginManager::get_loaded_plugins()
00230 {
00231   std::list<std::string> rv;
00232 
00233   plugins.lock();
00234   for (pit = plugins.begin(); pit != plugins.end(); ++pit) {
00235     rv.push_back((*pit)->name());
00236   }
00237   plugins.unlock();
00238   __meta_plugins.lock();
00239   for (__mpit = __meta_plugins.begin(); __mpit != __meta_plugins.end(); ++__mpit) {
00240     rv.push_back(__mpit->first);
00241   }
00242   __meta_plugins.unlock();
00243 
00244   return rv;
00245 }
00246 
00247 
00248 /** Check if plugin is loaded.
00249  * @param plugin_name plugin to check if it is loaded
00250  * @return true if the plugin is currently loaded, false otherwise
00251  */
00252 bool
00253 PluginManager::is_loaded(const char *plugin_name)
00254 {
00255   if (plugin_loader->is_loaded(plugin_name)) {
00256     return true;
00257   } else {
00258     // Could still be a meta plugin
00259     return (__meta_plugins.find(plugin_name) != __meta_plugins.end());
00260   }
00261 }
00262 
00263 
00264 /** Parse a list of plugin types.
00265  * Takes a comma-separated list of plugins and parses them into the individual
00266  * plugin names.
00267  * @param plugin_type_list string containing a comma-separated list of plugin types
00268  * @return parsed list of plugin types
00269  */
00270 std::list<std::string>
00271 PluginManager::parse_plugin_list(const char *plugin_list)
00272 {
00273   std::list<std::string> rv;
00274 
00275   char *plugins = strdup(plugin_list);
00276   char *saveptr;
00277   char *plugin;
00278 
00279   plugin = strtok_r(plugins, ",", &saveptr);
00280   while ( plugin ) {
00281     rv.push_back(plugin);
00282     plugin = strtok_r(NULL, ",", &saveptr);
00283   }
00284   free(plugins);
00285 
00286   return rv;
00287 }
00288 
00289 
00290 /** Load plugin.
00291  * The loading is interrupted if any of the plugins does not load properly.
00292  * The already loaded plugins are *not* unloaded, but kept.
00293  * @param plugin_list string containing a comma-separated list of plugins
00294  * to load. The plugin list can contain meta plugins.
00295  */
00296 void
00297 PluginManager::load(const char *plugin_list)
00298 {
00299   std::list<std::string> pp = parse_plugin_list(plugin_list);
00300 
00301   for (std::list<std::string>::iterator i = pp.begin(); i != pp.end(); ++i) {
00302     if ( i->length() == 0 ) continue;
00303 
00304     bool try_real_plugin = true;
00305     if ( __meta_plugins.find(*i) == __meta_plugins.end() ) {
00306       std::string meta_plugin = __meta_plugin_prefix + *i;
00307       try {
00308         std::string pset = __config->get_string(meta_plugin.c_str());
00309         if (pset.length() == 0) {
00310           throw Exception("Refusing to load an empty meta plugin");
00311         }
00312         //printf("Going to load meta plugin %s (%s)\n", i->c_str(), pset.c_str());
00313         __meta_plugins.lock();
00314         // Setting has to happen here, so that a meta plugin will not cause an
00315         // endless loop if it references itself!
00316         __meta_plugins[*i] = pset;
00317         __meta_plugins.unlock();
00318         try {
00319           LibLogger::log_info("PluginManager", "Loading plugins %s for meta plugin %s",
00320                               pset.c_str(), i->c_str());
00321           load(pset.c_str());
00322           notify_loaded(i->c_str());
00323         } catch (Exception &e) {
00324           e.append("Could not initialize meta plugin %s, aborting loading.", i->c_str());
00325           __meta_plugins.erase_locked(*i);
00326           throw;
00327         }
00328 
00329         try_real_plugin = false;
00330       } catch (ConfigEntryNotFoundException &e) {
00331         // no meta plugin defined by that name
00332         //printf("No meta plugin defined with the name %s\n", i->c_str());
00333         try_real_plugin = true;
00334       }
00335     }
00336 
00337     if (try_real_plugin &&
00338         (find_if(plugins.begin(), plugins.end(), plname_eq(*i)) == plugins.end()))
00339     {
00340       try {
00341         //printf("Going to load real plugin %s\n", i->c_str());
00342         Plugin *plugin = plugin_loader->load(i->c_str());
00343         plugins.lock();
00344         try {
00345           thread_collector->add(plugin->threads());
00346           plugins.push_back(plugin);
00347           plugin_ids[*i] = next_plugin_id++;
00348           notify_loaded(i->c_str());
00349         } catch (CannotInitializeThreadException &e) {
00350           e.prepend("Plugin >>> %s <<< could not be initialized, unloading", i->c_str());
00351           plugins.unlock();
00352           plugin_loader->unload(plugin);
00353           throw;
00354         }
00355         plugins.unlock();
00356       } catch (Exception &e) {
00357         MutexLocker lock(__meta_plugins.mutex());
00358         if ( __meta_plugins.find(*i) == __meta_plugins.end() ) {
00359           // only throw exception if no meta plugin with that name has
00360           // already been loaded
00361           throw;
00362         }
00363       }
00364     }
00365   }
00366 }
00367 
00368 
00369 /** Unload plugin.
00370  * Note that this method does not allow to pass a list of plugins, but it will
00371  * only accept a single plugin at a time.
00372  * @param plugin_name plugin to unload, can be a meta plugin.
00373  */
00374 void
00375 PluginManager::unload(const char *plugin_name)
00376 {
00377   MutexLocker lock(plugins.mutex());
00378   if ( (pit = find_if(plugins.begin(), plugins.end(), plname_eq(plugin_name)))
00379        != plugins.end()) {
00380     try {
00381       thread_collector->remove((*pit)->threads());
00382       plugin_loader->unload(*pit);
00383       plugins.erase(pit);
00384       plugin_ids.erase(plugin_name);
00385       notify_unloaded(plugin_name);
00386       // find all meta plugins that required this module, this can no longer
00387       // be considered loaded
00388       __meta_plugins.lock();
00389       __mpit = __meta_plugins.begin();
00390       while (__mpit != __meta_plugins.end()) {
00391         std::list<std::string> pp = parse_plugin_list(__mpit->second.c_str());
00392 
00393         bool erase = false;
00394         for (std::list<std::string>::iterator i = pp.begin(); i != pp.end(); ++i) {
00395           if ( *i == plugin_name ) {
00396             erase = true;
00397             break;
00398           }
00399         }
00400         if ( erase ) {
00401           LockMap< std::string, std::string >::iterator tmp = __mpit;
00402           ++__mpit;
00403           notify_unloaded(tmp->first.c_str());
00404           __meta_plugins.erase(tmp);
00405         } else {
00406           ++__mpit;
00407         }
00408       }
00409       __meta_plugins.unlock();
00410 
00411     } catch (Exception &e) {
00412       LibLogger::log_error("PluginManager", "Could not finalize one or more threads of plugin %s, NOT unloading plugin", plugin_name);
00413       throw;
00414     }
00415   } else if (__meta_plugins.find(plugin_name) != __meta_plugins.end()) {
00416     std::list<std::string> pp = parse_plugin_list(__meta_plugins[plugin_name].c_str());
00417 
00418     for (std::list<std::string>::reverse_iterator i = pp.rbegin(); i != pp.rend(); ++i) {
00419       if ( i->length() == 0 ) continue;
00420       if ((find_if(plugins.begin(), plugins.end(), plname_eq(*i)) == plugins.end())
00421            && (__meta_plugins.find(*i) != __meta_plugins.end()) ) {
00422         continue;
00423       }
00424 
00425       __meta_plugins.erase_locked(*i);
00426       LibLogger::log_info("PluginManager", "UNloading plugin %s for meta plugin %s",
00427                           i->c_str(), plugin_name);
00428       unload(i->c_str());
00429     }
00430   }
00431 }
00432 
00433 
00434 void
00435 PluginManager::config_tag_changed(const char *new_tag)
00436 {
00437 }
00438 
00439 void
00440 PluginManager::config_value_changed(const Configuration::ValueIterator *v)
00441 {
00442   if (v->is_string()) {
00443     __pinfo_cache.lock();
00444     std::string p = std::string(v->path()).substr(__meta_plugin_prefix.length());
00445     std::string s = std::string("Meta: ") + v->get_string();
00446     std::list<std::pair<std::string, std::string> >::iterator i;
00447     bool found = false;
00448     for (i = __pinfo_cache.begin(); i != __pinfo_cache.end(); ++i) {
00449       if (p == i->first) {
00450         i->second = s;
00451         found = true;
00452         break;
00453       }
00454     }
00455     if (! found) {
00456       __pinfo_cache.push_back(make_pair(p, s));
00457     }
00458     __pinfo_cache.unlock();
00459   }
00460 }
00461 
00462 void
00463 PluginManager::config_comment_changed(const Configuration::ValueIterator *v)
00464 {
00465 }
00466 
00467 void
00468 PluginManager::config_value_erased(const char *path)
00469 {
00470   __pinfo_cache.lock();
00471   std::string p = std::string(path).substr(__meta_plugin_prefix.length());
00472   std::list<std::pair<std::string, std::string> >::iterator i;
00473   for (i = __pinfo_cache.begin(); i != __pinfo_cache.end(); ++i) {
00474     if (p == i->first) {
00475       __pinfo_cache.erase(i);
00476       break;
00477     }
00478   }
00479   __pinfo_cache.unlock();
00480 }
00481 
00482 
00483 void
00484 PluginManager::fam_event(const char *filename, unsigned int mask)
00485 {
00486   const char *file_ext = "."SOEXT;
00487 
00488   const char *pos = strstr(filename, file_ext);
00489   std::string p = std::string(filename).substr(0, strlen(filename) - strlen(file_ext));
00490   if (NULL != pos) {
00491     __pinfo_cache.lock();
00492     bool found = false;
00493     std::list<std::pair<std::string, std::string> >::iterator i;
00494     for (i = __pinfo_cache.begin(); i != __pinfo_cache.end(); ++i) {
00495       if (p == i->first) {
00496         found = true;
00497         if ((mask & FAM_DELETE) || (mask & FAM_MOVED_FROM)) {
00498           __pinfo_cache.erase(i);
00499         } else {
00500           try {
00501             i->second = plugin_loader->get_description(p.c_str());
00502           } catch (Exception &e) {
00503             LibLogger::log_warn("PluginManager", "Could not get possibly modified "
00504                                 "description of plugin %s, exception follows",
00505                                 p.c_str());
00506             LibLogger::log_warn("PluginManager", e);
00507           }
00508         }
00509         break;
00510       }
00511     }
00512     if (! found &&
00513         !(mask & FAM_ISDIR) &&
00514         ((mask & FAM_MODIFY) || (mask & FAM_MOVED_TO) || (mask & FAM_CREATE))) {
00515       if (plugin_loader->is_loaded(p.c_str())) {
00516         LibLogger::log_info("PluginManager", "Plugin %s changed on disk, but is "
00517                             "loaded, no new info can be loaded, keeping old.",
00518                             p.c_str());
00519       }
00520       try {
00521         std::string s = plugin_loader->get_description(p.c_str());
00522         __pinfo_cache.push_back(make_pair(p, s));
00523       } catch (Exception &e) {
00524         LibLogger::log_warn("PluginManager", "Could not get possibly modified "
00525                             "description of plugin %s, exception follows",
00526                             p.c_str());
00527         LibLogger::log_warn("PluginManager", e);
00528       }
00529     }
00530 
00531     __pinfo_cache.sort();
00532     __pinfo_cache.unlock();
00533   }
00534 }
00535 
00536 
00537 /** Add listener.
00538  * Listeners are notified of plugin load and unloda events.
00539  * @param listener listener to add
00540  */
00541 void
00542 PluginManager::add_listener(PluginManagerListener *listener)
00543 {
00544   __listeners.lock();
00545   __listeners.push_back(listener);
00546   __listeners.sort();
00547   __listeners.unique();
00548   __listeners.unlock();
00549 }
00550 
00551 /** Remove listener.
00552  * @param listener listener to remove
00553  */
00554 void
00555 PluginManager::remove_listener(PluginManagerListener *listener)
00556 {
00557   __listeners.remove_locked(listener);
00558 }
00559 
00560 void
00561 PluginManager::notify_loaded(const char *plugin_name)
00562 {
00563   __listeners.lock();
00564   for (__lit = __listeners.begin(); __lit != __listeners.end(); ++__lit) {
00565     try {
00566       (*__lit)->plugin_loaded(plugin_name);
00567     } catch (Exception &e) {
00568       LibLogger::log_warn("PluginManager", "PluginManagerListener threw exception "
00569                           "during notification of plugin loaded, exception follows.");
00570       LibLogger::log_warn("PluginManager", e);
00571     }
00572   }
00573   __listeners.unlock();
00574 }
00575 
00576 void
00577 PluginManager::notify_unloaded(const char *plugin_name)
00578 {
00579   __listeners.lock();
00580   for (__lit = __listeners.begin(); __lit != __listeners.end(); ++__lit) {
00581     try {
00582       (*__lit)->plugin_unloaded(plugin_name);
00583     } catch (Exception &e) {
00584       LibLogger::log_warn("PluginManager", "PluginManagerListener threw exception "
00585                           "during notification of plugin unloaded, exception follows.");
00586       LibLogger::log_warn("PluginManager", e);
00587     }
00588   }
00589   __listeners.unlock();
00590 }
00591 
00592 
00593 /** Lock plugin manager.
00594  * This is an utility method that you can use for mutual access to the plugin
00595  * manager. The mutex is not used internally, but meant to be used from
00596  * callers.
00597  */
00598 void
00599 PluginManager::lock()
00600 {
00601   __mutex->lock();
00602 }
00603 
00604 
00605 /** Try to lock plugin manager.
00606  * This is an utility method that you can use for mutual access to the plugin
00607  * manager. The mutex is not used internally, but meant to be used from
00608  * callers.
00609  * @return true if the lock was acquired, false otherwise
00610  */
00611 bool
00612 PluginManager::try_lock()
00613 {
00614   return __mutex->try_lock();
00615 }
00616 
00617 /** Unlock plugin manager. */
00618 void
00619 PluginManager::unlock()
00620 {
00621   __mutex->unlock();
00622 }
00623 
00624 } // end namespace fawkes