pion-net  4.0.9
common/src/PionPlugin.cpp
00001 // -----------------------------------------------------------------------
00002 // pion-common: a collection of common libraries used by the Pion Platform
00003 // -----------------------------------------------------------------------
00004 // Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
00005 //
00006 // Distributed under the Boost Software License, Version 1.0.
00007 // See http://www.boost.org/LICENSE_1_0.txt
00008 //
00009 
00010 #include <boost/filesystem.hpp>
00011 #include <boost/filesystem/operations.hpp>
00012 #include <boost/thread/mutex.hpp>
00013 #include <pion/PionConfig.hpp>
00014 #include <pion/PionPlugin.hpp>
00015 
00016 #ifdef PION_WIN32
00017     #include <windows.h>
00018 #else
00019     #include <dlfcn.h>
00020 #endif
00021 
00022 
00023 namespace pion {    // begin namespace pion
00024     
00025 // static members of PionPlugin
00026     
00027 const std::string           PionPlugin::PION_PLUGIN_CREATE("pion_create_");
00028 const std::string           PionPlugin::PION_PLUGIN_DESTROY("pion_destroy_");
00029 #ifdef PION_WIN32
00030     const std::string           PionPlugin::PION_PLUGIN_EXTENSION(".dll");
00031 #else
00032     const std::string           PionPlugin::PION_PLUGIN_EXTENSION(".so");
00033 #endif
00034 const std::string           PionPlugin::PION_CONFIG_EXTENSION(".conf");
00035 std::vector<std::string>    PionPlugin::m_plugin_dirs;
00036 PionPlugin::PluginMap       PionPlugin::m_plugin_map;
00037 boost::mutex                PionPlugin::m_plugin_mutex;
00038 PionPlugin::StaticEntryPointList    *PionPlugin::m_entry_points_ptr = NULL;
00039 
00040     
00041 // PionPlugin member functions
00042     
00043 void PionPlugin::checkCygwinPath(boost::filesystem::path& final_path,
00044                                  const std::string& start_path)
00045 {
00046 #if defined(PION_WIN32) && defined(PION_CYGWIN_DIRECTORY)
00047     // try prepending PION_CYGWIN_DIRECTORY if not complete
00048     if (! final_path.is_complete() && final_path.has_root_directory()) {
00049         final_path = boost::filesystem::path(std::string(PION_CYGWIN_DIRECTORY) + start_path);
00050     }
00051 #endif
00052 }
00053 
00054 void PionPlugin::addPluginDirectory(const std::string& dir)
00055 {
00056     boost::filesystem::path plugin_path = boost::filesystem::system_complete(dir);
00057     checkCygwinPath(plugin_path, dir);
00058     if (! boost::filesystem::exists(plugin_path) )
00059         throw DirectoryNotFoundException(dir);
00060     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00061     m_plugin_dirs.push_back(plugin_path.string());
00062 }
00063 
00064 void PionPlugin::resetPluginDirectories(void)
00065 {
00066     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00067     m_plugin_dirs.clear();
00068 }
00069 
00070 void PionPlugin::open(const std::string& plugin_name)
00071 {
00072     std::string plugin_file;
00073 
00074     if (!findPluginFile(plugin_file, plugin_name))
00075         throw PluginNotFoundException(plugin_name);
00076         
00077     openFile(plugin_file);
00078 }
00079 
00080 void PionPlugin::openFile(const std::string& plugin_file)
00081 {
00082     releaseData();  // make sure we're not already pointing to something
00083     
00084     // use a temporary object first since openPlugin() may throw
00085     PionPluginData plugin_data(getPluginName(plugin_file));
00086     
00087     // check to see if we already have a matching shared library
00088     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00089     PluginMap::iterator itr = m_plugin_map.find(plugin_data.m_plugin_name);
00090     if (itr == m_plugin_map.end()) {
00091         // no plug-ins found with the same name
00092         
00093         // open up the shared library using our temporary data object
00094         openPlugin(plugin_file, plugin_data);   // may throw
00095         
00096         // all is good -> insert it into the plug-in map
00097         m_plugin_data = new PionPluginData(plugin_data);
00098         m_plugin_map.insert( std::make_pair(m_plugin_data->m_plugin_name,
00099                                             m_plugin_data) );
00100     } else {
00101         // found an existing plug-in with the same name
00102         m_plugin_data = itr->second;
00103     }
00104     
00105     // increment the number of references
00106     ++ m_plugin_data->m_references;
00107 }
00108 
00109 void PionPlugin::openStaticLinked(const std::string& plugin_name,
00110                                   void *create_func,
00111                                   void *destroy_func)
00112 {
00113     releaseData();  // make sure we're not already pointing to something
00114 
00115     // check to see if we already have a matching shared library
00116     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00117     PluginMap::iterator itr = m_plugin_map.find(plugin_name);
00118     if (itr == m_plugin_map.end()) {
00119         // no plug-ins found with the same name
00120 
00121         // all is good -> insert it into the plug-in map
00122         m_plugin_data = new PionPluginData(plugin_name);
00123         m_plugin_data->m_lib_handle = NULL; // this will indicate that we are using statically linked plug-in
00124         m_plugin_data->m_create_func = create_func;
00125         m_plugin_data->m_destroy_func = destroy_func;
00126         m_plugin_map.insert(std::make_pair(m_plugin_data->m_plugin_name,
00127                                            m_plugin_data));
00128     } else {
00129         // found an existing plug-in with the same name
00130         m_plugin_data = itr->second;
00131     }
00132 
00133     // increment the number of references
00134     ++ m_plugin_data->m_references;
00135 }
00136 
00137 void PionPlugin::releaseData(void)
00138 {
00139     if (m_plugin_data != NULL) {
00140         boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00141         // double-check after locking mutex
00142         if (m_plugin_data != NULL && --m_plugin_data->m_references == 0) {
00143             // no more references to the plug-in library
00144             
00145             // release the shared object
00146             closeDynamicLibrary(m_plugin_data->m_lib_handle);
00147             
00148             // remove it from the plug-in map
00149             PluginMap::iterator itr = m_plugin_map.find(m_plugin_data->m_plugin_name);
00150             // check itr just to be safe (it SHOULD always find a match)
00151             if (itr != m_plugin_map.end())
00152                 m_plugin_map.erase(itr);
00153             
00154             // release the heap object
00155             delete m_plugin_data;
00156         }
00157         m_plugin_data = NULL;
00158     }
00159 }
00160 
00161 void PionPlugin::grabData(const PionPlugin& p)
00162 {
00163     releaseData();  // make sure we're not already pointing to something
00164     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00165     m_plugin_data = const_cast<PionPluginData*>(p.m_plugin_data);
00166     if (m_plugin_data != NULL) {
00167         ++ m_plugin_data->m_references;
00168     }
00169 }
00170 
00171 bool PionPlugin::findFile(std::string& path_to_file, const std::string& name,
00172                           const std::string& extension)
00173 {
00174     // first, try the name as-is
00175     if (checkForFile(path_to_file, name, "", extension))
00176         return true;
00177 
00178     // nope, check search paths
00179     boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
00180     for (std::vector<std::string>::iterator i = m_plugin_dirs.begin();
00181          i != m_plugin_dirs.end(); ++i)
00182     {
00183         if (checkForFile(path_to_file, *i, name, extension))
00184             return true;
00185     }
00186     
00187     // no plug-in file found
00188     return false;
00189 }
00190 
00191 bool PionPlugin::checkForFile(std::string& final_path, const std::string& start_path,
00192                               const std::string& name, const std::string& extension)
00193 {
00194     // check for cygwin path oddities
00195     boost::filesystem::path cygwin_safe_path(start_path);
00196     checkCygwinPath(cygwin_safe_path, start_path);
00197     boost::filesystem::path test_path(cygwin_safe_path);
00198 
00199     // if a name is specified, append it to the test path
00200     if (! name.empty())
00201         test_path /= name;
00202 
00203     // check for existence of file (without extension)
00204     try {
00205         // is_regular may throw if directory is not readable
00206         if (boost::filesystem::is_regular(test_path)) {
00207             final_path = test_path.string();
00208             return true;
00209         }
00210     } catch (...) {}
00211 
00212     // next, try appending the extension
00213     if (name.empty()) {
00214         // no "name" specified -> append it directly to start_path
00215         test_path = boost::filesystem::path(start_path + extension);
00216         // in this case, we need to re-check for the cygwin oddities
00217         checkCygwinPath(test_path, start_path + extension);
00218     } else {
00219         // name is specified, so we can just re-use cygwin_safe_path
00220         test_path = cygwin_safe_path /
00221             boost::filesystem::path(name + extension);
00222     }
00223 
00224     // re-check for existence of file (after adding extension)
00225     try {
00226         // is_regular may throw if directory is not readable
00227         if (boost::filesystem::is_regular(test_path)) {
00228             final_path = test_path.string();
00229             return true;
00230         }
00231     } catch (...) {}
00232 
00233     // no plug-in file found
00234     return false;
00235 }
00236 
00237 void PionPlugin::openPlugin(const std::string& plugin_file,
00238                             PionPluginData& plugin_data)
00239 {
00240     // get the name of the plugin (for create/destroy symbol names)
00241     plugin_data.m_plugin_name = getPluginName(plugin_file);
00242     
00243     // attempt to open the plugin; note that this tries all search paths
00244     // and also tries a variety of platform-specific extensions
00245     plugin_data.m_lib_handle = loadDynamicLibrary(plugin_file.c_str());
00246     if (plugin_data.m_lib_handle == NULL) {
00247 #ifndef PION_WIN32
00248         const char *error_msg = dlerror();
00249         if (error_msg != NULL) {
00250             std::string error_str(plugin_file);
00251             error_str += " (";
00252             error_str += error_msg;
00253             error_str += ')';
00254             throw OpenPluginException(error_str);
00255         } else
00256 #endif
00257         throw OpenPluginException(plugin_file);
00258     }
00259     
00260     // find the function used to create new plugin objects
00261     plugin_data.m_create_func =
00262         getLibrarySymbol(plugin_data.m_lib_handle,
00263                          PION_PLUGIN_CREATE + plugin_data.m_plugin_name);
00264     if (plugin_data.m_create_func == NULL) {
00265         closeDynamicLibrary(plugin_data.m_lib_handle);
00266         throw PluginMissingCreateException(plugin_file);
00267     }
00268 
00269     // find the function used to destroy existing plugin objects
00270     plugin_data.m_destroy_func =
00271         getLibrarySymbol(plugin_data.m_lib_handle,
00272                          PION_PLUGIN_DESTROY + plugin_data.m_plugin_name);
00273     if (plugin_data.m_destroy_func == NULL) {
00274         closeDynamicLibrary(plugin_data.m_lib_handle);
00275         throw PluginMissingDestroyException(plugin_file);
00276     }
00277 }
00278 
00279 std::string PionPlugin::getPluginName(const std::string& plugin_file)
00280 {
00281     return boost::filesystem::basename(boost::filesystem::path(plugin_file));
00282 }
00283 
00284 void PionPlugin::getAllPluginNames(std::vector<std::string>& plugin_names)
00285 {
00286     // Iterate through all the Plugin directories.
00287     std::vector<std::string>::iterator it;
00288     for (it = m_plugin_dirs.begin(); it != m_plugin_dirs.end(); ++it) {
00289         // Find all shared libraries in the directory and add them to the list of Plugin names.
00290         boost::filesystem::directory_iterator end;
00291         for (boost::filesystem::directory_iterator it2(*it); it2 != end; ++it2) {
00292             if (boost::filesystem::is_regular(*it2)) {
00293                 if (boost::filesystem::extension(it2->path()) == PionPlugin::PION_PLUGIN_EXTENSION) {
00294                     plugin_names.push_back(PionPlugin::getPluginName(it2->path().filename().native()));
00295                 }
00296             }
00297         }
00298     }
00299 }
00300 
00301 void *PionPlugin::loadDynamicLibrary(const std::string& plugin_file)
00302 {
00303 #ifdef PION_WIN32
00304     #ifdef _MSC_VER
00305         return LoadLibraryA(plugin_file.c_str());
00306     #else
00307         return LoadLibrary(plugin_file.c_str());
00308     #endif
00309 #else
00310     // convert into a full/absolute/complete path since dlopen()
00311     // does not always search the CWD on some operating systems
00312     const boost::filesystem::path full_path = boost::filesystem::absolute(plugin_file);
00313     // NOTE: you must load shared libraries using RTLD_GLOBAL on Unix platforms
00314     // due to a bug in GCC (or Boost::any, depending on which crowd you want to believe).
00315     // see: http://svn.boost.org/trac/boost/ticket/754
00316     return dlopen(full_path.string().c_str(), RTLD_LAZY | RTLD_GLOBAL);
00317 #endif
00318 }
00319 
00320 void PionPlugin::closeDynamicLibrary(void *lib_handle)
00321 {
00322 #ifdef PION_WIN32
00323     // Apparently, FreeLibrary sometimes causes crashes when running 
00324     // pion-net-unit-tests under Windows.
00325     // It's hard to pin down, because many things can suppress the crashes,
00326     // such as enabling logging or setting breakpoints (i.e. things that 
00327     // might help pin it down.)  Also, it's very intermittent, and can be 
00328     // strongly affected by other processes that are running.
00329     // So, please don't call FreeLibrary here unless you've been able to 
00330     // reproduce and fix the crashing of the unit tests.
00331 
00332     //FreeLibrary((HINSTANCE) lib_handle);
00333 #else
00334     dlclose(lib_handle);
00335 #endif
00336 }
00337 
00338 void *PionPlugin::getLibrarySymbol(void *lib_handle, const std::string& symbol)
00339 {
00340 #ifdef PION_WIN32
00341     return (void*)GetProcAddress((HINSTANCE) lib_handle, symbol.c_str());
00342 #else
00343     return dlsym(lib_handle, symbol.c_str());
00344 #endif
00345 }
00346 
00347 bool PionPlugin::findStaticEntryPoint(const std::string& plugin_name,
00348                                       void **create_func,
00349                                       void **destroy_func)
00350 {
00351     // check simple case first: no entry points exist
00352     if (m_entry_points_ptr == NULL || m_entry_points_ptr->empty())
00353         return false;
00354 
00355     // try to find the entry point for the plugin
00356     for (std::list<StaticEntryPoint>::const_iterator i = m_entry_points_ptr->begin();
00357          i != m_entry_points_ptr->end(); ++i) {
00358             if (i->m_plugin_name==plugin_name) {
00359                 *create_func  = i->m_create_func;
00360                 *destroy_func = i->m_destroy_func;
00361                 return true;
00362             }
00363     }
00364     return false;
00365 }
00366 
00367 void PionPlugin::addStaticEntryPoint(const std::string& plugin_name,
00368                                      void *create_func,
00369                                      void *destroy_func)
00370 {
00371     // make sure that this function can only be called by one thread at a time
00372     static boost::mutex         entrypoint_mutex;
00373     boost::mutex::scoped_lock   entrypoint_lock(entrypoint_mutex);
00374 
00375     // create the entry point list if it doesn't already exist
00376     if (m_entry_points_ptr == NULL)
00377         m_entry_points_ptr = new StaticEntryPointList;
00378     
00379     // insert it into the entry point list
00380     m_entry_points_ptr->push_back(StaticEntryPoint(plugin_name, create_func, destroy_func));
00381 }
00382 
00383 }   // end namespace pion