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.directory_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     if (boost::filesystem::is_regular(test_path)) {
00205         final_path = test_path.file_string();
00206         return true;
00207     }
00208         
00209     // next, try appending the extension
00210     if (name.empty()) {
00211         // no "name" specified -> append it directly to start_path
00212         test_path = boost::filesystem::path(start_path + extension);
00213         // in this case, we need to re-check for the cygwin oddities
00214         checkCygwinPath(test_path, start_path + extension);
00215     } else {
00216         // name is specified, so we can just re-use cygwin_safe_path
00217         test_path = cygwin_safe_path /
00218             boost::filesystem::path(name + extension);
00219     }
00220 
00221     // re-check for existence of file (after adding extension)
00222     if (boost::filesystem::is_regular(test_path)) {
00223         final_path = test_path.file_string();
00224         return true;
00225     }
00226     
00227     // no plug-in file found
00228     return false;
00229 }
00230 
00231 void PionPlugin::openPlugin(const std::string& plugin_file,
00232                             PionPluginData& plugin_data)
00233 {
00234     // get the name of the plugin (for create/destroy symbol names)
00235     plugin_data.m_plugin_name = getPluginName(plugin_file);
00236     
00237     // attempt to open the plugin; note that this tries all search paths
00238     // and also tries a variety of platform-specific extensions
00239     plugin_data.m_lib_handle = loadDynamicLibrary(plugin_file.c_str());
00240     if (plugin_data.m_lib_handle == NULL) {
00241 #ifndef PION_WIN32
00242         char *error_msg = dlerror();
00243         if (error_msg != NULL) {
00244             std::string error_str(plugin_file);
00245             error_str += " (";
00246             error_str += error_msg;
00247             error_str += ')';
00248             throw OpenPluginException(error_str);
00249         } else
00250 #endif
00251         throw OpenPluginException(plugin_file);
00252     }
00253     
00254     // find the function used to create new plugin objects
00255     plugin_data.m_create_func =
00256         getLibrarySymbol(plugin_data.m_lib_handle,
00257                          PION_PLUGIN_CREATE + plugin_data.m_plugin_name);
00258     if (plugin_data.m_create_func == NULL) {
00259         closeDynamicLibrary(plugin_data.m_lib_handle);
00260         throw PluginMissingCreateException(plugin_file);
00261     }
00262 
00263     // find the function used to destroy existing plugin objects
00264     plugin_data.m_destroy_func =
00265         getLibrarySymbol(plugin_data.m_lib_handle,
00266                          PION_PLUGIN_DESTROY + plugin_data.m_plugin_name);
00267     if (plugin_data.m_destroy_func == NULL) {
00268         closeDynamicLibrary(plugin_data.m_lib_handle);
00269         throw PluginMissingDestroyException(plugin_file);
00270     }
00271 }
00272 
00273 std::string PionPlugin::getPluginName(const std::string& plugin_file)
00274 {
00275     return boost::filesystem::basename(boost::filesystem::path(plugin_file));
00276 }
00277 
00278 void PionPlugin::getAllPluginNames(std::vector<std::string>& plugin_names)
00279 {
00280     // Iterate through all the Plugin directories.
00281     std::vector<std::string>::iterator it;
00282     for (it = m_plugin_dirs.begin(); it != m_plugin_dirs.end(); ++it) {
00283         // Find all shared libraries in the directory and add them to the list of Plugin names.
00284         boost::filesystem::directory_iterator end;
00285         for (boost::filesystem::directory_iterator it2(*it); it2 != end; ++it2) {
00286             if (boost::filesystem::is_regular(*it2)) {
00287                 if (boost::filesystem::extension(it2->path()) == PionPlugin::PION_PLUGIN_EXTENSION) {
00288                     plugin_names.push_back(PionPlugin::getPluginName(it2->path().leaf()));
00289                 }
00290             }
00291         }
00292     }
00293 }
00294 
00295 void *PionPlugin::loadDynamicLibrary(const std::string& plugin_file)
00296 {
00297 #ifdef PION_WIN32
00298     #ifdef _MSC_VER
00299         return LoadLibraryA(plugin_file.c_str());
00300     #else
00301         return LoadLibrary(plugin_file.c_str());
00302     #endif
00303 #else
00304     // convert into a full/absolute/complete path since dlopen()
00305     // does not always search the CWD on some operating systems
00306     const boost::filesystem::path full_path = boost::filesystem::complete(plugin_file);
00307     // NOTE: you must load shared libraries using RTLD_GLOBAL on Unix platforms
00308     // due to a bug in GCC (or Boost::any, depending on which crowd you want to believe).
00309     // see: http://svn.boost.org/trac/boost/ticket/754
00310     return dlopen(full_path.file_string().c_str(), RTLD_LAZY | RTLD_GLOBAL);
00311 #endif
00312 }
00313 
00314 void PionPlugin::closeDynamicLibrary(void *lib_handle)
00315 {
00316 #ifdef PION_WIN32
00317     // Apparently, FreeLibrary sometimes causes crashes when running 
00318     // pion-net-unit-tests under Windows.
00319     // It's hard to pin down, because many things can suppress the crashes,
00320     // such as enabling logging or setting breakpoints (i.e. things that 
00321     // might help pin it down.)  Also, it's very intermittent, and can be 
00322     // strongly affected by other processes that are running.
00323     // So, please don't call FreeLibrary here unless you've been able to 
00324     // reproduce and fix the crashing of the unit tests.
00325 
00326     //FreeLibrary((HINSTANCE) lib_handle);
00327 #else
00328     dlclose(lib_handle);
00329 #endif
00330 }
00331 
00332 void *PionPlugin::getLibrarySymbol(void *lib_handle, const std::string& symbol)
00333 {
00334 #ifdef PION_WIN32
00335     return (void*)GetProcAddress((HINSTANCE) lib_handle, symbol.c_str());
00336 #else
00337     return dlsym(lib_handle, symbol.c_str());
00338 #endif
00339 }
00340 
00341 bool PionPlugin::findStaticEntryPoint(const std::string& plugin_name,
00342                                       void **create_func,
00343                                       void **destroy_func)
00344 {
00345     // check simple case first: no entry points exist
00346     if (m_entry_points_ptr == NULL || m_entry_points_ptr->empty())
00347         return false;
00348 
00349     // try to find the entry point for the plugin
00350     for (std::list<StaticEntryPoint>::const_iterator i = m_entry_points_ptr->begin();
00351          i != m_entry_points_ptr->end(); ++i) {
00352             if (i->m_plugin_name==plugin_name) {
00353                 *create_func  = i->m_create_func;
00354                 *destroy_func = i->m_destroy_func;
00355                 return true;
00356             }
00357     }
00358     return false;
00359 }
00360 
00361 void PionPlugin::addStaticEntryPoint(const std::string& plugin_name,
00362                                      void *create_func,
00363                                      void *destroy_func)
00364 {
00365     // make sure that this function can only be called by one thread at a time
00366     static boost::mutex         entrypoint_mutex;
00367     boost::mutex::scoped_lock   entrypoint_lock(entrypoint_mutex);
00368 
00369     // create the entry point list if it doesn't already exist
00370     if (m_entry_points_ptr == NULL)
00371         m_entry_points_ptr = new StaticEntryPointList;
00372     
00373     // insert it into the entry point list
00374     m_entry_points_ptr->push_back(StaticEntryPoint(plugin_name, create_func, destroy_func));
00375 }
00376 
00377 }   // end namespace pion

Generated on Fri Apr 30 14:48:53 2010 for pion-net by  doxygen 1.4.7