Fawkes API
Fawkes Development Version
|
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