- Main Page
- Classes
- Files

- File List

# src/plugin.cpp

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