pion  5.0.6
src/plugin.cpp
00001 // ---------------------------------------------------------------------
00002 // pion:  a Boost C++ framework for building lightweight HTTP interfaces
00003 // ---------------------------------------------------------------------
00004 // Copyright (C) 2007-2014 Splunk Inc.  (https://github.com/splunk/pion)
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