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