pion  5.0.6
plugin.cpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #include <boost/filesystem.hpp>
11 #include <boost/filesystem/operations.hpp>
12 #include <boost/thread/mutex.hpp>
13 #include <pion/config.hpp>
14 #include <pion/error.hpp>
15 #include <pion/plugin.hpp>
16 
17 #ifdef PION_WIN32
18  #include <windows.h>
19 #else
20  #include <dlfcn.h>
21 #endif
22 
23 
24 namespace pion { // begin namespace pion
25 
26 // static members of plugin
27 
28 const std::string plugin::PION_PLUGIN_CREATE("pion_create_");
29 const std::string plugin::PION_PLUGIN_DESTROY("pion_destroy_");
30 #ifdef PION_WIN32
31  const std::string plugin::PION_PLUGIN_EXTENSION(".dll");
32 #else
33  const std::string plugin::PION_PLUGIN_EXTENSION(".so");
34 #endif
35 const std::string plugin::PION_CONFIG_EXTENSION(".conf");
36 boost::once_flag plugin::m_instance_flag = BOOST_ONCE_INIT;
37 plugin::config_type *plugin::m_config_ptr = NULL;
38 
39 
40 // plugin member functions
41 
42 void plugin::create_plugin_config(void)
43 {
44  static config_type UNIQUE_PION_PLUGIN_CONFIG;
45  m_config_ptr = &UNIQUE_PION_PLUGIN_CONFIG;
46 }
47 
48 void plugin::check_cygwin_path(boost::filesystem::path& final_path,
49  const std::string& start_path)
50 {
51 #if defined(PION_WIN32) && defined(PION_CYGWIN_DIRECTORY)
52  // try prepending PION_CYGWIN_DIRECTORY if not complete
53  if (! final_path.is_complete() && final_path.has_root_directory()) {
54  final_path = boost::filesystem::path(std::string(PION_CYGWIN_DIRECTORY) + start_path);
55  }
56 #endif
57 }
58 
59 void plugin::add_plugin_directory(const std::string& dir)
60 {
61  boost::filesystem::path plugin_path = boost::filesystem::system_complete(dir);
62  check_cygwin_path(plugin_path, dir);
63  if (! boost::filesystem::exists(plugin_path) )
64  BOOST_THROW_EXCEPTION( error::directory_not_found() << error::errinfo_dir_name(dir) );
65  config_type& cfg = get_plugin_config();
66  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
67 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
68  cfg.m_plugin_dirs.push_back(plugin_path.string());
69 #else
70  cfg.m_plugin_dirs.push_back(plugin_path.directory_string());
71 #endif
72 
73 }
74 
76 {
77  config_type& cfg = get_plugin_config();
78  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
79  cfg.m_plugin_dirs.clear();
80 }
81 
82 void plugin::open(const std::string& plugin_name)
83 {
84  // check first if name matches an existing plugin name
85  {
86  config_type& cfg = get_plugin_config();
87  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
88  map_type::iterator itr = cfg.m_plugin_map.find(plugin_name);
89  if (itr != cfg.m_plugin_map.end()) {
90  release_data(); // make sure we're not already pointing to something
91  m_plugin_data = itr->second;
92  ++ m_plugin_data->m_references;
93  return;
94  }
95  }
96 
97  // nope, look for shared library file
98  std::string plugin_file;
99 
100  if (!find_plugin_file(plugin_file, plugin_name))
101  BOOST_THROW_EXCEPTION( error::plugin_not_found() << error::errinfo_plugin_name(plugin_name) );
102 
103  open_file(plugin_file);
104 }
105 
106 void plugin::open_file(const std::string& plugin_file)
107 {
108  release_data(); // make sure we're not already pointing to something
109 
110  // use a temporary object first since open_plugin() may throw
111  data_type plugin_data(get_plugin_name(plugin_file));
112 
113  // check to see if we already have a matching shared library
114  config_type& cfg = get_plugin_config();
115  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
116  map_type::iterator itr = cfg.m_plugin_map.find(plugin_data.m_plugin_name);
117  if (itr == cfg.m_plugin_map.end()) {
118  // no plug-ins found with the same name
119 
120  // open up the shared library using our temporary data object
121  open_plugin(plugin_file, plugin_data); // may throw
122 
123  // all is good -> insert it into the plug-in map
124  m_plugin_data = new data_type(plugin_data);
125  cfg.m_plugin_map.insert( std::make_pair(m_plugin_data->m_plugin_name,
126  m_plugin_data) );
127  } else {
128  // found an existing plug-in with the same name
129  m_plugin_data = itr->second;
130  }
131 
132  // increment the number of references
133  ++ m_plugin_data->m_references;
134 }
135 
137 {
138  if (m_plugin_data != NULL) {
139  config_type& cfg = get_plugin_config();
140  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
141  // double-check after locking mutex
142  if (m_plugin_data != NULL && --m_plugin_data->m_references == 0) {
143  // no more references to the plug-in library
144 
145  // make sure it's not a static library
146  if (m_plugin_data->m_lib_handle != NULL) {
147 
148  // release the shared object
149  close_dynamic_library(m_plugin_data->m_lib_handle);
150 
151  // remove it from the plug-in map
152  map_type::iterator itr = cfg.m_plugin_map.find(m_plugin_data->m_plugin_name);
153  // check itr just to be safe (it SHOULD always find a match)
154  if (itr != cfg.m_plugin_map.end())
155  cfg.m_plugin_map.erase(itr);
156 
157  // release the heap object
158  delete m_plugin_data;
159  }
160  }
161  m_plugin_data = NULL;
162  }
163 }
164 
165 void plugin::grab_data(const plugin& p)
166 {
167  release_data(); // make sure we're not already pointing to something
168  config_type& cfg = get_plugin_config();
169  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
170  m_plugin_data = const_cast<data_type*>(p.m_plugin_data);
171  if (m_plugin_data != NULL) {
172  ++ m_plugin_data->m_references;
173  }
174 }
175 
176 bool plugin::find_file(std::string& path_to_file, const std::string& name,
177  const std::string& extension)
178 {
179  // first, try the name as-is
180  if (check_for_file(path_to_file, name, "", extension))
181  return true;
182 
183  // nope, check search paths
184  config_type& cfg = get_plugin_config();
185  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
186  for (std::vector<std::string>::iterator i = cfg.m_plugin_dirs.begin();
187  i != cfg.m_plugin_dirs.end(); ++i)
188  {
189  if (check_for_file(path_to_file, *i, name, extension))
190  return true;
191  }
192 
193  // no plug-in file found
194  return false;
195 }
196 
197 bool plugin::check_for_file(std::string& final_path, const std::string& start_path,
198  const std::string& name, const std::string& extension)
199 {
200  // check for cygwin path oddities
201  boost::filesystem::path cygwin_safe_path(start_path);
202  check_cygwin_path(cygwin_safe_path, start_path);
203  boost::filesystem::path test_path(cygwin_safe_path);
204 
205  // if a name is specified, append it to the test path
206  if (! name.empty())
207  test_path /= name;
208 
209  // check for existence of file (without extension)
210  try {
211  // is_regular may throw if directory is not readable
212  if (boost::filesystem::is_regular(test_path)) {
213 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
214  final_path = test_path.string();
215 #else
216  final_path = test_path.file_string();
217 #endif
218  return true;
219  }
220  } catch (...) {}
221 
222  // next, try appending the extension
223  if (name.empty()) {
224  // no "name" specified -> append it directly to start_path
225  test_path = boost::filesystem::path(start_path + extension);
226  // in this case, we need to re-check for the cygwin oddities
227  check_cygwin_path(test_path, start_path + extension);
228  } else {
229  // name is specified, so we can just re-use cygwin_safe_path
230  test_path = cygwin_safe_path /
231  boost::filesystem::path(name + extension);
232  }
233 
234  // re-check for existence of file (after adding extension)
235  try {
236  // is_regular may throw if directory is not readable
237  if (boost::filesystem::is_regular(test_path)) {
238 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
239  final_path = test_path.string();
240 #else
241  final_path = test_path.file_string();
242 #endif
243  return true;
244  }
245  } catch (...) {}
246 
247  // no plug-in file found
248  return false;
249 }
250 
251 void plugin::open_plugin(const std::string& plugin_file,
252  data_type& plugin_data)
253 {
254  // get the name of the plugin (for create/destroy symbol names)
255  plugin_data.m_plugin_name = get_plugin_name(plugin_file);
256 
257  // attempt to open the plugin; note that this tries all search paths
258  // and also tries a variety of platform-specific extensions
259  plugin_data.m_lib_handle = load_dynamic_library(plugin_file.c_str());
260  if (plugin_data.m_lib_handle == NULL) {
261 #ifndef PION_WIN32
262  const char *error_msg = dlerror();
263  if (error_msg != NULL) {
264  std::string error_str(plugin_file);
265  error_str += " (";
266  error_str += error_msg;
267  error_str += ')';
268  BOOST_THROW_EXCEPTION( error::open_plugin()
269  << error::errinfo_plugin_name(plugin_data.m_plugin_name)
270  << error::errinfo_message(error_str) );
271  } else
272 #endif
273  BOOST_THROW_EXCEPTION( error::open_plugin()
274  << error::errinfo_plugin_name(plugin_data.m_plugin_name) );
275  }
276 
277  // find the function used to create new plugin objects
278  plugin_data.m_create_func =
279  get_library_symbol(plugin_data.m_lib_handle,
280  PION_PLUGIN_CREATE + plugin_data.m_plugin_name);
281  if (plugin_data.m_create_func == NULL) {
282  close_dynamic_library(plugin_data.m_lib_handle);
283  BOOST_THROW_EXCEPTION( error::plugin_missing_symbol()
284  << error::errinfo_plugin_name(plugin_data.m_plugin_name)
285  << error::errinfo_symbol_name(PION_PLUGIN_CREATE + plugin_data.m_plugin_name) );
286  }
287 
288  // find the function used to destroy existing plugin objects
289  plugin_data.m_destroy_func =
290  get_library_symbol(plugin_data.m_lib_handle,
291  PION_PLUGIN_DESTROY + plugin_data.m_plugin_name);
292  if (plugin_data.m_destroy_func == NULL) {
293  close_dynamic_library(plugin_data.m_lib_handle);
294  BOOST_THROW_EXCEPTION( error::plugin_missing_symbol()
295  << error::errinfo_plugin_name(plugin_data.m_plugin_name)
296  << error::errinfo_symbol_name(PION_PLUGIN_DESTROY + plugin_data.m_plugin_name) );
297  }
298 }
299 
300 std::string plugin::get_plugin_name(const std::string& plugin_file)
301 {
302  return boost::filesystem::basename(boost::filesystem::path(plugin_file));
303 }
304 
305 void plugin::get_all_plugin_names(std::vector<std::string>& plugin_names)
306 {
307  // Iterate through all the Plugin directories.
308  std::vector<std::string>::iterator it;
309  config_type& cfg = get_plugin_config();
310  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
311  for (it = cfg.m_plugin_dirs.begin(); it != cfg.m_plugin_dirs.end(); ++it) {
312  // Find all shared libraries in the directory and add them to the list of Plugin names.
313  boost::filesystem::directory_iterator end;
314  for (boost::filesystem::directory_iterator it2(*it); it2 != end; ++it2) {
315  if (boost::filesystem::is_regular(*it2)) {
316  if (boost::filesystem::extension(it2->path()) == plugin::PION_PLUGIN_EXTENSION) {
317 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
318  plugin_names.push_back(plugin::get_plugin_name(it2->path().filename().string()));
319 #else
320  plugin_names.push_back(plugin::get_plugin_name(it2->path().leaf()));
321 #endif
322  }
323  }
324  }
325  }
326 
327  // Append static-linked libraries
328  for (map_type::const_iterator itr = cfg.m_plugin_map.begin(); itr != cfg.m_plugin_map.end(); ++itr) {
329  const data_type& plugin_data = *(itr->second);
330  if (plugin_data.m_lib_handle == NULL) {
331  plugin_names.push_back(plugin_data.m_plugin_name);
332  }
333  }
334 }
335 
336 void *plugin::load_dynamic_library(const std::string& plugin_file)
337 {
338 #ifdef PION_WIN32
339  #ifdef _MSC_VER
340  return LoadLibraryA(plugin_file.c_str());
341  #else
342  return LoadLibrary(plugin_file.c_str());
343  #endif
344 #else
345  // convert into a full/absolute/complete path since dlopen()
346  // does not always search the CWD on some operating systems
347 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
348  const boost::filesystem::path full_path = boost::filesystem::absolute(plugin_file);
349 #else
350  const boost::filesystem::path full_path = boost::filesystem::complete(plugin_file);
351 #endif
352  // NOTE: you must load shared libraries using RTLD_GLOBAL on Unix platforms
353  // due to a bug in GCC (or Boost::any, depending on which crowd you want to believe).
354  // see: http://svn.boost.org/trac/boost/ticket/754
355 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
356  return dlopen(full_path.string().c_str(), RTLD_LAZY | RTLD_GLOBAL);
357 #else
358  return dlopen(full_path.file_string().c_str(), RTLD_LAZY | RTLD_GLOBAL);
359 #endif
360 #endif
361 }
362 
363 void plugin::close_dynamic_library(void *lib_handle)
364 {
365 #ifdef PION_WIN32
366  // Apparently, FreeLibrary sometimes causes crashes when running
367  // unit tests under Windows.
368  // It's hard to pin down, because many things can suppress the crashes,
369  // such as enabling logging or setting breakpoints (i.e. things that
370  // might help pin it down.) Also, it's very intermittent, and can be
371  // strongly affected by other processes that are running.
372  // So, please don't call FreeLibrary here unless you've been able to
373  // reproduce and fix the crashing of the unit tests.
374 
375  //FreeLibrary((HINSTANCE) lib_handle);
376 #else
377  dlclose(lib_handle);
378 #endif
379 }
380 
381 void *plugin::get_library_symbol(void *lib_handle, const std::string& symbol)
382 {
383 #ifdef PION_WIN32
384  return (void*)GetProcAddress((HINSTANCE) lib_handle, symbol.c_str());
385 #else
386  return dlsym(lib_handle, symbol.c_str());
387 #endif
388 }
389 
390 void plugin::add_static_entry_point(const std::string& plugin_name,
391  void *create_func,
392  void *destroy_func)
393 {
394  // check for duplicate
395  config_type& cfg = get_plugin_config();
396  boost::mutex::scoped_lock plugin_lock(cfg.m_plugin_mutex);
397  map_type::iterator itr = cfg.m_plugin_map.find(plugin_name);
398  if (itr == cfg.m_plugin_map.end()) {
399  // no plug-ins found with the same name
400  // all is good -> insert it into the plug-in map
401  data_type *plugin_data = new data_type(plugin_name);
402  plugin_data->m_lib_handle = NULL; // this will indicate that we are using statically linked plug-in
403  plugin_data->m_create_func = create_func;
404  plugin_data->m_destroy_func = destroy_func;
405  cfg.m_plugin_map.insert(std::make_pair(plugin_name, plugin_data));
406  }
407 }
408 
409 } // end namespace pion
void open_file(const std::string &plugin_file)
Definition: plugin.cpp:106
void grab_data(const plugin &p)
grabs a reference to another plug-in's shared library symbols
Definition: plugin.cpp:165
void * m_lib_handle
symbol library loaded from a shared object file
Definition: plugin.hpp:157
std::string get_plugin_name(void) const
returns the name of the plugin that is currently open
Definition: plugin.hpp:96
static void add_plugin_directory(const std::string &dir)
appends a directory to the plug-in search path
Definition: plugin.cpp:59
void release_data(void)
releases the plug-in's shared library symbols
Definition: plugin.cpp:136
static void reset_plugin_directories(void)
clears all directories from the plug-in search path
Definition: plugin.cpp:75
void * m_create_func
function used to create instances of the plug-in object
Definition: plugin.hpp:160
static bool find_plugin_file(std::string &path_to_file, const std::string &name)
Definition: plugin.hpp:40
std::string m_plugin_name
the name of the plugin (must be unique per process)
Definition: plugin.hpp:166
exception thrown if a required directory is not found
Definition: error.hpp:174
void * m_destroy_func
function used to destroy instances of the plug-in object
Definition: plugin.hpp:163
static void check_cygwin_path(boost::filesystem::path &final_path, const std::string &path_string)
Definition: plugin.cpp:48
unsigned long m_references
number of references to this class
Definition: plugin.hpp:169
exception thrown if a plugin cannot be found
Definition: error.hpp:181
void open(const std::string &plugin_name)
Definition: plugin.cpp:82
static void add_static_entry_point(const std::string &plugin_name, void *create_func, void *destroy_func)
Definition: plugin.cpp:390
static void get_all_plugin_names(std::vector< std::string > &plugin_names)
returns a list of all Plugins found in all Plugin directories
Definition: plugin.cpp:305