pion
5.0.6
|
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