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

Generated on 1 Mar 2011 for Fawkes API by  doxygen 1.6.1