# 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     try {
    00205         // is_regular may throw if directory is not readable
    00206         if (boost::filesystem::is_regular(test_path)) {
    00207             final_path = test_path.file_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.file_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().leaf()));
    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::complete(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.file_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