pion-net  4.0.9
PionPlugin.cpp
1 // -----------------------------------------------------------------------
2 // pion-common: a collection of common libraries used by the Pion Platform
3 // -----------------------------------------------------------------------
4 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com)
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/PionConfig.hpp>
14 #include <pion/PionPlugin.hpp>
15 
16 #ifdef PION_WIN32
17  #include <windows.h>
18 #else
19  #include <dlfcn.h>
20 #endif
21 
22 
23 namespace pion { // begin namespace pion
24 
25 // static members of PionPlugin
26 
27 const std::string PionPlugin::PION_PLUGIN_CREATE("pion_create_");
28 const std::string PionPlugin::PION_PLUGIN_DESTROY("pion_destroy_");
29 #ifdef PION_WIN32
30  const std::string PionPlugin::PION_PLUGIN_EXTENSION(".dll");
31 #else
32  const std::string PionPlugin::PION_PLUGIN_EXTENSION(".so");
33 #endif
34 const std::string PionPlugin::PION_CONFIG_EXTENSION(".conf");
35 std::vector<std::string> PionPlugin::m_plugin_dirs;
36 PionPlugin::PluginMap PionPlugin::m_plugin_map;
37 boost::mutex PionPlugin::m_plugin_mutex;
38 PionPlugin::StaticEntryPointList *PionPlugin::m_entry_points_ptr = NULL;
39 
40 
41 // PionPlugin member functions
42 
43 void PionPlugin::checkCygwinPath(boost::filesystem::path& final_path,
44  const std::string& start_path)
45 {
46 #if defined(PION_WIN32) && defined(PION_CYGWIN_DIRECTORY)
47  // try prepending PION_CYGWIN_DIRECTORY if not complete
48  if (! final_path.is_complete() && final_path.has_root_directory()) {
49  final_path = boost::filesystem::path(std::string(PION_CYGWIN_DIRECTORY) + start_path);
50  }
51 #endif
52 }
53 
54 void PionPlugin::addPluginDirectory(const std::string& dir)
55 {
56  boost::filesystem::path plugin_path = boost::filesystem::system_complete(dir);
57  checkCygwinPath(plugin_path, dir);
58  if (! boost::filesystem::exists(plugin_path) )
59  throw DirectoryNotFoundException(dir);
60  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
61  m_plugin_dirs.push_back(plugin_path.string());
62 }
63 
65 {
66  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
67  m_plugin_dirs.clear();
68 }
69 
70 void PionPlugin::open(const std::string& plugin_name)
71 {
72  std::string plugin_file;
73 
74  if (!findPluginFile(plugin_file, plugin_name))
75  throw PluginNotFoundException(plugin_name);
76 
77  openFile(plugin_file);
78 }
79 
80 void PionPlugin::openFile(const std::string& plugin_file)
81 {
82  releaseData(); // make sure we're not already pointing to something
83 
84  // use a temporary object first since openPlugin() may throw
85  PionPluginData plugin_data(getPluginName(plugin_file));
86 
87  // check to see if we already have a matching shared library
88  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
89  PluginMap::iterator itr = m_plugin_map.find(plugin_data.m_plugin_name);
90  if (itr == m_plugin_map.end()) {
91  // no plug-ins found with the same name
92 
93  // open up the shared library using our temporary data object
94  openPlugin(plugin_file, plugin_data); // may throw
95 
96  // all is good -> insert it into the plug-in map
97  m_plugin_data = new PionPluginData(plugin_data);
98  m_plugin_map.insert( std::make_pair(m_plugin_data->m_plugin_name,
99  m_plugin_data) );
100  } else {
101  // found an existing plug-in with the same name
102  m_plugin_data = itr->second;
103  }
104 
105  // increment the number of references
106  ++ m_plugin_data->m_references;
107 }
108 
109 void PionPlugin::openStaticLinked(const std::string& plugin_name,
110  void *create_func,
111  void *destroy_func)
112 {
113  releaseData(); // make sure we're not already pointing to something
114 
115  // check to see if we already have a matching shared library
116  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
117  PluginMap::iterator itr = m_plugin_map.find(plugin_name);
118  if (itr == m_plugin_map.end()) {
119  // no plug-ins found with the same name
120 
121  // all is good -> insert it into the plug-in map
122  m_plugin_data = new PionPluginData(plugin_name);
123  m_plugin_data->m_lib_handle = NULL; // this will indicate that we are using statically linked plug-in
124  m_plugin_data->m_create_func = create_func;
125  m_plugin_data->m_destroy_func = destroy_func;
126  m_plugin_map.insert(std::make_pair(m_plugin_data->m_plugin_name,
127  m_plugin_data));
128  } else {
129  // found an existing plug-in with the same name
130  m_plugin_data = itr->second;
131  }
132 
133  // increment the number of references
134  ++ m_plugin_data->m_references;
135 }
136 
138 {
139  if (m_plugin_data != NULL) {
140  boost::mutex::scoped_lock plugin_lock(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  // release the shared object
146  closeDynamicLibrary(m_plugin_data->m_lib_handle);
147 
148  // remove it from the plug-in map
149  PluginMap::iterator itr = m_plugin_map.find(m_plugin_data->m_plugin_name);
150  // check itr just to be safe (it SHOULD always find a match)
151  if (itr != m_plugin_map.end())
152  m_plugin_map.erase(itr);
153 
154  // release the heap object
155  delete m_plugin_data;
156  }
157  m_plugin_data = NULL;
158  }
159 }
160 
162 {
163  releaseData(); // make sure we're not already pointing to something
164  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
165  m_plugin_data = const_cast<PionPluginData*>(p.m_plugin_data);
166  if (m_plugin_data != NULL) {
167  ++ m_plugin_data->m_references;
168  }
169 }
170 
171 bool PionPlugin::findFile(std::string& path_to_file, const std::string& name,
172  const std::string& extension)
173 {
174  // first, try the name as-is
175  if (checkForFile(path_to_file, name, "", extension))
176  return true;
177 
178  // nope, check search paths
179  boost::mutex::scoped_lock plugin_lock(m_plugin_mutex);
180  for (std::vector<std::string>::iterator i = m_plugin_dirs.begin();
181  i != m_plugin_dirs.end(); ++i)
182  {
183  if (checkForFile(path_to_file, *i, name, extension))
184  return true;
185  }
186 
187  // no plug-in file found
188  return false;
189 }
190 
191 bool PionPlugin::checkForFile(std::string& final_path, const std::string& start_path,
192  const std::string& name, const std::string& extension)
193 {
194  // check for cygwin path oddities
195  boost::filesystem::path cygwin_safe_path(start_path);
196  checkCygwinPath(cygwin_safe_path, start_path);
197  boost::filesystem::path test_path(cygwin_safe_path);
198 
199  // if a name is specified, append it to the test path
200  if (! name.empty())
201  test_path /= name;
202 
203  // check for existence of file (without extension)
204  try {
205  // is_regular may throw if directory is not readable
206  if (boost::filesystem::is_regular(test_path)) {
207  final_path = test_path.string();
208  return true;
209  }
210  } catch (...) {}
211 
212  // next, try appending the extension
213  if (name.empty()) {
214  // no "name" specified -> append it directly to start_path
215  test_path = boost::filesystem::path(start_path + extension);
216  // in this case, we need to re-check for the cygwin oddities
217  checkCygwinPath(test_path, start_path + extension);
218  } else {
219  // name is specified, so we can just re-use cygwin_safe_path
220  test_path = cygwin_safe_path /
221  boost::filesystem::path(name + extension);
222  }
223 
224  // re-check for existence of file (after adding extension)
225  try {
226  // is_regular may throw if directory is not readable
227  if (boost::filesystem::is_regular(test_path)) {
228  final_path = test_path.string();
229  return true;
230  }
231  } catch (...) {}
232 
233  // no plug-in file found
234  return false;
235 }
236 
237 void PionPlugin::openPlugin(const std::string& plugin_file,
238  PionPluginData& plugin_data)
239 {
240  // get the name of the plugin (for create/destroy symbol names)
241  plugin_data.m_plugin_name = getPluginName(plugin_file);
242 
243  // attempt to open the plugin; note that this tries all search paths
244  // and also tries a variety of platform-specific extensions
245  plugin_data.m_lib_handle = loadDynamicLibrary(plugin_file.c_str());
246  if (plugin_data.m_lib_handle == NULL) {
247 #ifndef PION_WIN32
248  const char *error_msg = dlerror();
249  if (error_msg != NULL) {
250  std::string error_str(plugin_file);
251  error_str += " (";
252  error_str += error_msg;
253  error_str += ')';
254  throw OpenPluginException(error_str);
255  } else
256 #endif
257  throw OpenPluginException(plugin_file);
258  }
259 
260  // find the function used to create new plugin objects
261  plugin_data.m_create_func =
262  getLibrarySymbol(plugin_data.m_lib_handle,
263  PION_PLUGIN_CREATE + plugin_data.m_plugin_name);
264  if (plugin_data.m_create_func == NULL) {
265  closeDynamicLibrary(plugin_data.m_lib_handle);
266  throw PluginMissingCreateException(plugin_file);
267  }
268 
269  // find the function used to destroy existing plugin objects
270  plugin_data.m_destroy_func =
271  getLibrarySymbol(plugin_data.m_lib_handle,
272  PION_PLUGIN_DESTROY + plugin_data.m_plugin_name);
273  if (plugin_data.m_destroy_func == NULL) {
274  closeDynamicLibrary(plugin_data.m_lib_handle);
275  throw PluginMissingDestroyException(plugin_file);
276  }
277 }
278 
279 std::string PionPlugin::getPluginName(const std::string& plugin_file)
280 {
281  return boost::filesystem::basename(boost::filesystem::path(plugin_file));
282 }
283 
284 void PionPlugin::getAllPluginNames(std::vector<std::string>& plugin_names)
285 {
286  // Iterate through all the Plugin directories.
287  std::vector<std::string>::iterator it;
288  for (it = m_plugin_dirs.begin(); it != m_plugin_dirs.end(); ++it) {
289  // Find all shared libraries in the directory and add them to the list of Plugin names.
290  boost::filesystem::directory_iterator end;
291  for (boost::filesystem::directory_iterator it2(*it); it2 != end; ++it2) {
292  if (boost::filesystem::is_regular(*it2)) {
293  if (boost::filesystem::extension(it2->path()) == PionPlugin::PION_PLUGIN_EXTENSION) {
294  plugin_names.push_back(PionPlugin::getPluginName(it2->path().filename().native()));
295  }
296  }
297  }
298  }
299 }
300 
301 void *PionPlugin::loadDynamicLibrary(const std::string& plugin_file)
302 {
303 #ifdef PION_WIN32
304  #ifdef _MSC_VER
305  return LoadLibraryA(plugin_file.c_str());
306  #else
307  return LoadLibrary(plugin_file.c_str());
308  #endif
309 #else
310  // convert into a full/absolute/complete path since dlopen()
311  // does not always search the CWD on some operating systems
312  const boost::filesystem::path full_path = boost::filesystem::absolute(plugin_file);
313  // NOTE: you must load shared libraries using RTLD_GLOBAL on Unix platforms
314  // due to a bug in GCC (or Boost::any, depending on which crowd you want to believe).
315  // see: http://svn.boost.org/trac/boost/ticket/754
316  return dlopen(full_path.string().c_str(), RTLD_LAZY | RTLD_GLOBAL);
317 #endif
318 }
319 
320 void PionPlugin::closeDynamicLibrary(void *lib_handle)
321 {
322 #ifdef PION_WIN32
323  // Apparently, FreeLibrary sometimes causes crashes when running
324  // pion-net-unit-tests under Windows.
325  // It's hard to pin down, because many things can suppress the crashes,
326  // such as enabling logging or setting breakpoints (i.e. things that
327  // might help pin it down.) Also, it's very intermittent, and can be
328  // strongly affected by other processes that are running.
329  // So, please don't call FreeLibrary here unless you've been able to
330  // reproduce and fix the crashing of the unit tests.
331 
332  //FreeLibrary((HINSTANCE) lib_handle);
333 #else
334  dlclose(lib_handle);
335 #endif
336 }
337 
338 void *PionPlugin::getLibrarySymbol(void *lib_handle, const std::string& symbol)
339 {
340 #ifdef PION_WIN32
341  return (void*)GetProcAddress((HINSTANCE) lib_handle, symbol.c_str());
342 #else
343  return dlsym(lib_handle, symbol.c_str());
344 #endif
345 }
346 
347 bool PionPlugin::findStaticEntryPoint(const std::string& plugin_name,
348  void **create_func,
349  void **destroy_func)
350 {
351  // check simple case first: no entry points exist
352  if (m_entry_points_ptr == NULL || m_entry_points_ptr->empty())
353  return false;
354 
355  // try to find the entry point for the plugin
356  for (std::list<StaticEntryPoint>::const_iterator i = m_entry_points_ptr->begin();
357  i != m_entry_points_ptr->end(); ++i) {
358  if (i->m_plugin_name==plugin_name) {
359  *create_func = i->m_create_func;
360  *destroy_func = i->m_destroy_func;
361  return true;
362  }
363  }
364  return false;
365 }
366 
367 void PionPlugin::addStaticEntryPoint(const std::string& plugin_name,
368  void *create_func,
369  void *destroy_func)
370 {
371  // make sure that this function can only be called by one thread at a time
372  static boost::mutex entrypoint_mutex;
373  boost::mutex::scoped_lock entrypoint_lock(entrypoint_mutex);
374 
375  // create the entry point list if it doesn't already exist
376  if (m_entry_points_ptr == NULL)
377  m_entry_points_ptr = new StaticEntryPointList;
378 
379  // insert it into the entry point list
380  m_entry_points_ptr->push_back(StaticEntryPoint(plugin_name, create_func, destroy_func));
381 }
382 
383 } // end namespace pion