OpenCPN Partial API docs
plugin_loader.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: PlugIn Manager Object
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2010 by David S. Register *
9  * Copyright (C) 2022 Alec Leamas *
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  * This program is distributed in the hope that it will be useful, *
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19  * GNU General Public License for more details. *
20  * *
21  * You should have received a copy of the GNU General Public License *
22  * along with this program; if not, write to the *
23  * Free Software Foundation, Inc., *
24  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
25  **************************************************************************/
26 
27 #include "config.h"
28 
29 #include <algorithm>
30 #include <set>
31 #include <sstream>
32 #include <vector>
33 
34 #ifdef USE_LIBELF
35 #include <elf.h>
36 #include <libelf.h>
37 #include <gelf.h>
38 #endif
39 
40 #if defined(__linux__) && !defined(__ANDROID__)
41 #include <wordexp.h>
42 #endif
43 
44 #ifndef WIN32
45 #include <cxxabi.h>
46 #endif
47 
48 #include <wx/wx.h> // NOLINT
49 #include <wx/bitmap.h>
50 #include <wx/dir.h>
51 #include <wx/event.h>
52 #include <wx/hashset.h>
53 #include <wx/filename.h>
54 #include <wx/string.h>
55 #include <wx/tokenzr.h>
56 #include <wx/window.h>
57 #include <wx/process.h>
58 
59 #include "model/base_platform.h"
60 #include "model/catalog_handler.h"
61 #include "model/catalog_parser.h"
62 #include "model/config_vars.h"
63 #include "model/cmdline.h"
64 #include "model/config_vars.h"
65 #include "model/logger.h"
66 #include "model/ocpn_utils.h"
67 #include "model/plugin_blacklist.h"
68 #include "model/plugin_cache.h"
69 #include "model/plugin_handler.h"
70 #include "model/plugin_loader.h"
71 #include "model/plugin_paths.h"
72 #include "model/safe_mode.h"
73 #include "observable_confvar.h"
74 
75 
76 #ifdef __ANDROID__
77 #include "androidUTIL.h"
78 #include <dlfcn.h>
79 #endif
80 
81 #ifdef __WXMSW__
82 #include <Psapi.h>
83 #endif
84 
85 static const std::vector<std::string> SYSTEM_PLUGINS = {
86  "chartdownloader", "wmm", "dashboard", "grib", "demo"};
87 
89 static PlugInContainer* GetContainer(const PlugInData& pd,
90  const ArrayOfPlugIns& plugin_array) {
91  for (size_t i = 0; i < plugin_array.GetCount(); i++) {
92  const auto& p = plugin_array.Item(i);
93  if (p->m_common_name == pd.m_common_name) return p;
94  }
95  return nullptr;
96 }
97 
99 static bool IsSystemPluginPath(const std::string& path) {
100  static const std::vector<std::string> kPlugins = {
101  "chartdldr_pi", "wmm_pi", "dashboard_pi", "grib_pi", "demo_pi"};
102 
103  const std::string lc_path = ocpn::tolower(path);
104  for (const auto& p : kPlugins)
105  if (lc_path.find(p) != std::string::npos) return true;
106  return false;
107 }
108 
110 static bool IsSystemPluginName(const std::string& name) {
111  static const std::vector<std::string> kPlugins = {
112  "chartdownloader", "wmm", "dashboard", "grib", "demo"};
113  auto found =
114  std::find(kPlugins.begin(), kPlugins.end(), ocpn::tolower(name));
115  return found != kPlugins.end();
116 }
117 
119 static std::string GetInstalledVersion(const PlugInData& pd) {
120  std::string path =
121  PluginHandler::versionPath(pd.m_common_name.ToStdString());
122  if (path == "" || !wxFileName::IsFileReadable(path)) {
123  auto loader = PluginLoader::getInstance();
124  auto pic = GetContainer(pd, *loader->GetPlugInArray());
125  if (!pic || !pic->m_pplugin) {
126  return SemanticVersion(0, 0, -1).to_string();
127  }
128  int v_major = pic->m_pplugin->GetPlugInVersionMajor();
129  int v_minor = pic->m_pplugin->GetPlugInVersionMinor();
130  return SemanticVersion(v_major, v_minor, -1).to_string();
131  }
132  std::ifstream stream;
133  std::string version;
134  stream.open(path, std::ifstream::in);
135  stream >> version;
136  return version;
137 }
138 
139 static PluginMetadata CreateMetadata(const PlugInContainer* pic) {
140  auto catalogHdlr = CatalogHandler::getInstance();
141 
142  PluginMetadata mdata;
143  mdata.name = pic->m_common_name.ToStdString();
144  SemanticVersion orphanVersion(pic->m_version_major, pic->m_version_minor);
145  mdata.version = orphanVersion.to_string();
146  mdata.summary = pic->m_short_description;
147  mdata.description = pic->m_long_description;
148 
149  mdata.target = "all"; // Force IsCompatible() true
150  mdata.is_orphan = true;
151 
152  return mdata;
153 }
154 
156  const PlugInData pd,
157  std::function<const PluginMetadata(const std::string&)> get_metadata) {
158  auto loader = PluginLoader::getInstance();
159  auto pic = GetContainer(pd, *loader->GetPlugInArray());
160  if (!pic) {
161  return SemanticVersion(0, 0, -1).to_string();
162  }
163 
164  PluginMetadata metadata;
165  metadata = pic->m_managed_metadata;
166  if (metadata.version == "")
167  metadata = get_metadata(pic->m_common_name.ToStdString());
168  std::string detail_suffix(metadata.is_imported ? _(" [Imported]") : "");
169  if (metadata.is_orphan)
170  detail_suffix = _(" [Orphan]");
171 
172  int v_major(0);
173  int v_minor(0);
174  if (pic->m_pplugin) {
175  v_major = pic->m_pplugin->GetPlugInVersionMajor();
176  v_minor = pic->m_pplugin->GetPlugInVersionMinor();
177  }
178  auto p = dynamic_cast<opencpn_plugin_117*>(pic->m_pplugin);
179  if (p) {
180  // New style plugin, trust version available in the API.
181  auto sv = SemanticVersion(
182  v_major, v_minor, p->GetPlugInVersionPatch(), p->GetPlugInVersionPost(),
183  p->GetPlugInVersionPre(), p->GetPlugInVersionBuild());
184  return sv.to_string() + detail_suffix;
185  } else {
186  if (!metadata.is_orphan) {
187  std::string version = GetInstalledVersion(pd);
188  return version + detail_suffix;
189  }
190  else
191  return metadata.version + detail_suffix;
192  }
193 }
194 
195 PlugInContainer::PlugInContainer()
196  : PlugInData(), m_pplugin(nullptr), m_library(), m_destroy_fn(nullptr) {}
197 
199  : m_has_setup_options(false),
200  m_enabled(false),
201  m_init_state(false),
202  m_toolbox_panel(false),
203  m_cap_flag(0),
204  m_api_version(0),
205  m_version_major(0),
206  m_version_minor(0),
207  m_status(PluginStatus::Unknown) {}
208 
210  m_common_name = wxString(md.name);
211  auto v = SemanticVersion::parse(md.version);
212  m_version_major = v.major;
213  m_version_minor = v.minor;
214  m_managed_metadata = md;
215  m_status = PluginStatus::ManagedInstallAvailable;
216  m_enabled = false;
217 }
218 
219 std::string PlugInData::Key() const {
220  return std::string(m_status == PluginStatus::Managed ? "1" : "0") +
221  m_common_name.ToStdString();
222 }
223 
224 //-----------------------------------------------------------------------------------------------------
225 //
226 // Plugin Loader Implementation
227 //
228 //-----------------------------------------------------------------------------------------------------
229 
239 static void setLoadPath() {
240  using namespace std;
241 
242  auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
243  auto dirs = PluginPaths::getInstance()->Libdirs();
244  if (osSystemId & wxOS_UNIX_LINUX) {
245  string path = ocpn::join(dirs, ':');
246  wxString envPath;
247  if (wxGetEnv("LD_LIBRARY_PATH", &envPath)) {
248  path = path + ":" + envPath.ToStdString();
249  }
250  wxLogMessage("Using LD_LIBRARY_PATH: %s", path.c_str());
251  wxSetEnv("LD_LIBRARY_PATH", path.c_str());
252  } else if (osSystemId & wxOS_WINDOWS) {
253  // On windows, Libdirs() and Bindirs() are the same.
254  string path = ocpn::join(dirs, ';');
255  wxString envPath;
256  if (wxGetEnv("PATH", &envPath)) {
257  path = path + ";" + envPath.ToStdString();
258  }
259  wxLogMessage("Using PATH: %s", path);
260  wxSetEnv("PATH", path);
261  } else if (osSystemId & wxOS_MAC) {
262  string path = ocpn::join(dirs, ':');
263  wxString envPath;
264  if (wxGetEnv("DYLD_LIBRARY_PATH", &envPath)) {
265  path = path + ":" + envPath.ToStdString();
266  }
267  wxLogMessage("Using DYLD_LIBRARY_PATH: %s", path.c_str());
268  wxSetEnv("DYLD_LIBRARY_PATH", path.c_str());
269  } else {
270  wxString os_name = wxPlatformInfo::Get().GetPortIdName();
271  if (os_name.Contains("wxQT")) {
272  wxLogMessage("setLoadPath() using Android library path");
273  } else
274  wxLogWarning("SetLoadPath: Unsupported platform.");
275  }
276  if (osSystemId & wxOS_MAC || osSystemId & wxOS_UNIX_LINUX) {
277  dirs = PluginPaths::getInstance()->Bindirs();
278  string path = ocpn::join(dirs, ':');
279  wxString envPath;
280  wxGetEnv("PATH", &envPath);
281  path = path + ":" + envPath.ToStdString();
282  wxLogMessage("Using PATH: %s", path);
283  wxSetEnv("PATH", path);
284  }
285 }
286 
287 static void ProcessLateInit(PlugInContainer* pic) {
288  if (pic->m_cap_flag & WANTS_LATE_INIT) {
289  wxString msg("PluginLoader: Calling LateInit PlugIn: ");
290  msg += pic->m_plugin_file;
291  wxLogMessage(msg);
292 
293  auto ppi = dynamic_cast<opencpn_plugin_110*>(pic->m_pplugin);
294  if (ppi) ppi->LateInit();
295  }
296 }
297 
298 PluginLoader* PluginLoader::getInstance() {
299  static PluginLoader* instance = nullptr;
300 
301  if (!instance) instance = new PluginLoader();
302  return instance;
303 }
304 
305 PluginLoader::PluginLoader()
306  : m_blacklist(blacklist_factory()),
307  m_default_plugin_icon(nullptr),
308 #ifdef __WXMSW__
309  m_found_wxwidgets(false),
310 #endif
311  m_on_deactivate_cb([](const PlugInContainer* pic) {}) {}
312 
313 bool PluginLoader::IsPlugInAvailable(const wxString& commonName) {
314  for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
315  PlugInContainer* pic = plugin_array[i];
316  if (pic && pic->m_enabled && (pic->m_common_name == commonName))
317  return true;
318  }
319  return false;
320 }
321 
323  wxWindow* parent) {
324  auto loader = PluginLoader::getInstance();
325  auto pic = GetContainer(pd, *loader->GetPlugInArray());
326  if (pic) pic->m_pplugin->ShowPreferencesDialog(parent);
327 }
328 
329 void PluginLoader::NotifySetupOptionsPlugin(const PlugInData* pd) {
330  auto pic = GetContainer(*pd, *GetPlugInArray());
331  if (!pic) return;
332  if (pic->m_has_setup_options) return;
333  pic->m_has_setup_options = true;
334  if (pic->m_enabled && pic->m_init_state) {
335  if (pic->m_cap_flag & INSTALLS_TOOLBOX_PAGE) {
336  switch (pic->m_api_version) {
337  case 109:
338  case 110:
339  case 111:
340  case 112:
341  case 113:
342  case 114:
343  case 115:
344  case 116:
345  case 117:
346  case 118: {
347  if (pic->m_pplugin) {
348  auto ppi = dynamic_cast<opencpn_plugin_19*>(pic->m_pplugin);
349  if (ppi) {
350  ppi->OnSetupOptions();
351  auto loader = PluginLoader::getInstance();
352  loader->SetToolboxPanel(pic->m_common_name, true);
353  }
354  break;
355  }
356  }
357  default:
358  break;
359  }
360  }
361  }
362 }
363 
364 void PluginLoader::SetEnabled(const wxString& common_name, bool enabled) {
365  for (size_t i = 0; i < plugin_array.GetCount(); i++) {
366  PlugInContainer* pic = plugin_array[i];
367  if (pic->m_common_name == common_name) {
368  pic->m_enabled = enabled;
369  return;
370  }
371  }
372 }
373 
374 void PluginLoader::SetToolboxPanel(const wxString& common_name, bool value) {
375  for (size_t i = 0; i < plugin_array.GetCount(); i++) {
376  PlugInContainer* pic = plugin_array[i];
377  if (pic->m_common_name == common_name) {
378  pic->m_toolbox_panel = value;
379  return;
380  }
381  }
382  wxLogMessage("Atttempt to update toolbox panel on non-existing plugin " +
383  common_name);
384 }
385 
386 const wxBitmap* PluginLoader::GetPluginDefaultIcon() {
387  if (!m_default_plugin_icon) m_default_plugin_icon = new wxBitmap(32, 32);
388  return m_default_plugin_icon;
389 }
390 
391 void PluginLoader::SetPluginDefaultIcon(const wxBitmap* bitmap) {
392  delete m_default_plugin_icon;
393  m_default_plugin_icon = bitmap;
394 }
395 
397  auto pic = GetContainer(pd, plugin_array);
398  if (!pic) {
399  wxLogMessage("Attempt to remove non-existing plugin %s",
400  pd.m_common_name.ToStdString().c_str());
401  return;
402  }
403  plugin_array.Remove(pic);
404 }
405 
406 static int ComparePlugins(PlugInContainer** p1, PlugInContainer** p2) {
407  return (*p1)->Key().compare((*p2)->Key());
408 }
409 
411  PlugInContainer**)) {
412  plugin_array.Sort(ComparePlugins);
413 }
414 
415 bool PluginLoader::LoadAllPlugIns(bool load_enabled, bool keep_orphans) {
416  using namespace std;
417 
418  static const wxString sep = wxFileName::GetPathSeparator();
419  vector<string> dirs = PluginPaths::getInstance()->Libdirs();
420  wxLogMessage("PluginLoader: loading plugins from %s", ocpn::join(dirs, ';'));
421  setLoadPath();
422  bool any_dir_loaded = false;
423  for (const auto& dir : dirs) {
424  wxString wxdir(dir);
425  wxLogMessage("Loading plugins from dir: %s", wxdir.mb_str().data());
426  if (LoadPlugInDirectory(wxdir, load_enabled)) any_dir_loaded = true;
427  }
428 
429  // Read the default ocpn-plugins.xml, and update/merge the plugin array
430  // This only needs to happen when the entire universe (enabled and disabled)
431  // of plugins are loaded for management.
432  if (!load_enabled) UpdateManagedPlugins(keep_orphans);
433 
434  // Some additional actions needed after all plugins are loaded.
435  evt_update_chart_types.Notify();
436  auto errors = std::make_shared<std::vector<LoadError>>(load_errors);
438  load_errors.clear();
439 
440  return any_dir_loaded;
441 }
442 
443 bool PluginLoader::LoadPluginCandidate(const wxString& file_name,
444  bool load_enabled) {
445  wxString plugin_file = wxFileName(file_name).GetFullName();
446  wxLogMessage("Checking plugin candidate: %s", file_name.mb_str().data());
447  wxDateTime plugin_modification = wxFileName(file_name).GetModificationTime();
448  wxLog::FlushActive();
449 
450  // this gets called every time we switch to the plugins tab.
451  // this allows plugins to be installed and enabled without restarting
452  // opencpn. For this reason we must check that we didn't already load this
453  // plugin
454  bool loaded = false;
455  PlugInContainer* loaded_pic = nullptr;
456  for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
457  PlugInContainer* pic_test = plugin_array[i];
458 
459  // Checking for dynamically updated plugins
460  if (pic_test->m_plugin_filename == plugin_file) {
461  // Do not re-load same-name plugins from different directories. Certain
462  // to crash...
463  if (pic_test->m_plugin_file == file_name) {
464  if (pic_test->m_plugin_modification != plugin_modification) {
465  // modification times don't match, reload plugin
466  plugin_array.Remove(pic_test);
467  i--;
468 
469  DeactivatePlugIn(pic_test);
470  pic_test->m_destroy_fn(pic_test->m_pplugin);
471 
472  delete pic_test;
473  } else {
474  loaded = true;
475  loaded_pic = pic_test;
476  break;
477  }
478  } else {
479  loaded = true;
480  loaded_pic = pic_test;
481  break;
482  }
483  }
484  }
485 
486  if (loaded) return true;
487  // Avoid loading/testing legacy plugins installed in base plugin path.
488  wxFileName fn_plugin_file(file_name);
489  wxString plugin_file_path =
490  fn_plugin_file.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
491  wxString base_plugin_path = g_BasePlatform->GetPluginDir();
492  if (!base_plugin_path.EndsWith(wxFileName::GetPathSeparator()))
493  base_plugin_path += wxFileName::GetPathSeparator();
494 
495  if (!g_bportable) {
496  if (base_plugin_path.IsSameAs(plugin_file_path)) {
497  if (!IsSystemPluginPath(file_name.ToStdString())) {
498  DEBUG_LOG << "Skipping plugin " << file_name << " in "
499  << g_BasePlatform->GetPluginDir();
500  return false;
501  }
502  }
503  }
504 
505  if (!IsSystemPluginPath(file_name.ToStdString()) && safe_mode::get_mode()) {
506  DEBUG_LOG << "Skipping plugin " << file_name << " in safe mode";
507  return false;
508  }
509 
510  auto msg =
511  std::string("Checking plugin compatibility: ") + file_name.ToStdString();
512  wxLogMessage(msg.c_str());
513  wxLog::FlushActive();
514 
515  bool b_compat = CheckPluginCompatibility(file_name);
516 
517  if (!b_compat) {
518  msg =
519  std::string("Incompatible plugin detected: ") + file_name.ToStdString();
520  wxLogMessage(msg.c_str());
521  if (m_blacklist->mark_unloadable(file_name.ToStdString())) {
522  LoadError le(LoadError::Type::Unloadable, file_name.ToStdString());
523  load_errors.push_back(le);
524  }
525  return false;
526  }
527 
528  PlugInContainer* pic = LoadPlugIn(file_name);
529 
530  // Check the config file to see if this PlugIn is user-enabled,
531  // only loading enabled plugins.
532  // Make the check late enough to pick up incompatible plugins anyway
533  const auto path = std::string("/PlugIns/") + plugin_file.ToStdString();
534  ConfigVar<bool> enabled(path, "bEnabled", TheBaseConfig());
535  if (load_enabled && !enabled.Get(true)) {
536  pic->m_destroy_fn(pic->m_pplugin);
537  delete pic;
538  wxLogMessage("Skipping not enabled candidate.");
539  return true;
540  }
541 
542  if (pic) {
543  if (pic->m_pplugin) {
544  plugin_array.Add(pic);
545 
546  // The common name is available without initialization and startup of
547  // the PlugIn
548  pic->m_common_name = pic->m_pplugin->GetCommonName();
549  pic->m_plugin_filename = plugin_file;
550  pic->m_plugin_modification = plugin_modification;
551  pic->m_enabled = enabled.Get(false);
552 
553  if (safe_mode::get_mode()) {
554  pic->m_enabled = false;
555  enabled.Set(false);
556  }
557  if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
558  // The CLI has no graphics context, but plugins assumes there is.
559  if (pic->m_enabled) {
560  pic->m_cap_flag = pic->m_pplugin->Init();
561  pic->m_init_state = true;
562  }
563  }
564  evt_load_plugin.Notify(pic);
565  wxLog::FlushActive();
566 
567  std::string found_version;
568  for (const auto& p : PluginHandler::getInstance()->getInstalled()) {
569  if (ocpn::tolower(p.name) == pic->m_common_name.Lower()) {
570  found_version = p.readonly ? "" : p.version;
571  break;
572  }
573  }
574  pic->m_version_str = found_version;
575  pic->m_short_description = pic->m_pplugin->GetShortDescription();
576  pic->m_long_description = pic->m_pplugin->GetLongDescription();
577  pic->m_version_major = pic->m_pplugin->GetPlugInVersionMajor();
578  pic->m_version_minor = pic->m_pplugin->GetPlugInVersionMinor();
579 
580  auto pbm0 = pic->m_pplugin->GetPlugInBitmap();
581  if (!pbm0->IsOk()) {
582  pbm0 = (wxBitmap *)GetPluginDefaultIcon();
583  }
584  pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
585  wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
586 
587  if (!pic->m_enabled && pic->m_destroy_fn) {
588  pic->m_destroy_fn(pic->m_pplugin);
589  pic->m_destroy_fn = nullptr;
590  pic->m_pplugin = nullptr;
591  pic->m_init_state = false;
592  if (pic->m_library.IsLoaded()) pic->m_library.Unload();
593  }
594 
595  // Check to see if the plugin just processed has an associated catalog entry
596  // understanding that SYSTEM plugins have no metadata by design
597  auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
598  pic->m_common_name.Lower());
599  bool is_system = found != SYSTEM_PLUGINS.end();
600 
601  if (!is_system) {
602  auto available = PluginHandler::getInstance()->getCompatiblePlugins();
603  wxString name = pic->m_common_name;
604  auto it = find_if(available.begin(), available.end(),
605  [name](const PluginMetadata& md) { return md.name == name; });
606 
607  if (it == available.end()) {
608  // Installed plugin is an orphan....
609  // Add a stub metadata entry to the active CatalogHandler context
610  // to satisfy minimal PIM functionality
611 
612  auto oprhan_metadata = CreateMetadata(pic);
613  auto catalogHdlr = CatalogHandler::getInstance();
614  catalogHdlr->AddMetadataToActiveContext(oprhan_metadata);
615  }
616  }
617 
618  } else { // No pic->m_pplugin
619  wxLogMessage(
620  " PluginLoader: Unloading invalid PlugIn, API version %d ",
621  pic->m_api_version);
622  pic->m_destroy_fn(pic->m_pplugin);
623 
624  LoadError le(LoadError::Type::Unloadable, file_name.ToStdString());
625  delete pic;
626  load_errors.push_back(le);
627  return false;
628  }
629  } else { // pic == 0
630  return false;
631  }
632  return true;
633 }
634 
635 // Helper function: loads all plugins from a single directory
636 bool PluginLoader::LoadPlugInDirectory(const wxString& plugin_dir,
637  bool load_enabled) {
638  evt_load_directory.Notify();
639  m_plugin_location = plugin_dir;
640 
641  wxString msg("PluginLoader searching for PlugIns in location ");
642  msg += m_plugin_location;
643  wxLogMessage(msg);
644 
645 #ifdef __WXMSW__
646  wxString pispec = "*_pi.dll";
647 #elif defined(__WXOSX__)
648  wxString pispec = "*_pi.dylib";
649 #else
650  wxString pispec = "*_pi.so";
651 #endif
652 
653  if (!::wxDirExists(m_plugin_location)) {
654  msg = m_plugin_location;
655  msg.Prepend(" Directory ");
656  msg.Append(" does not exist.");
657  wxLogMessage(msg);
658  return false;
659  }
660 
661  if (!g_BasePlatform->isPlatformCapable(PLATFORM_CAP_PLUGINS)) return false;
662 
663  wxArrayString file_list;
664 
665  int get_flags = wxDIR_FILES | wxDIR_DIRS;
666 #ifdef __WXMSW__
667 #ifdef _DEBUG
668  get_flags = wxDIR_FILES;
669 #endif
670 #endif
671 
672 #ifdef __ANDROID__
673  get_flags = wxDIR_FILES; // No subdirs, especially "/files" where PlugIns are
674  // initially placed in APK
675 #endif
676 
677  bool ret =
678  false; // return true if at least one new plugins gets loaded/unloaded
679  wxDir::GetAllFiles(m_plugin_location, &file_list, pispec, get_flags);
680 
681  wxLogMessage("Found %d candidates", (int)file_list.GetCount());
682  for (unsigned int i = 0; i < file_list.GetCount(); i++) {
683  wxLog::FlushActive();
684 
685  wxString file_name = file_list[i];
686 
687  LoadPluginCandidate(file_name, load_enabled);
688  }
689 
690  // Scrub the plugin array...
691  // Here, looking for duplicates caused by new installation of a plugin
692  // We want to remove the previous entry representing the uninstalled packaged
693  // plugin metadata
694  for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
695  PlugInContainer* pic = plugin_array[i];
696  for (unsigned int j = i + 1; j < plugin_array.GetCount(); j++) {
697  PlugInContainer* pict = plugin_array[j];
698 
699  if (pic->m_common_name == pict->m_common_name) {
700  if (pic->m_plugin_file.IsEmpty())
701  plugin_array.Item(i)->m_status = PluginStatus::PendingListRemoval;
702  else
703  plugin_array.Item(j)->m_status = PluginStatus::PendingListRemoval;
704  }
705  }
706  }
707 
708  // Remove any list items marked
709  size_t i = 0;
710  while ((i >= 0) && (i < plugin_array.GetCount())) {
711  PlugInContainer* pict = plugin_array.Item(i);
712  if (pict->m_status == PluginStatus::PendingListRemoval) {
713  plugin_array.RemoveAt(i);
714  i = 0;
715  } else
716  i++;
717  }
718 
719  return ret;
720 }
721 
722 bool PluginLoader::UpdatePlugIns() {
723  bool bret = false;
724 
725  for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
726  PlugInContainer* pic = plugin_array[i];
727 
728  // Try to confirm that the m_pplugin member points to a valid plugin
729  // image...
730  if (pic->m_pplugin) {
731  auto ppl = dynamic_cast<opencpn_plugin*>(pic->m_pplugin);
732  if (!ppl) {
733  pic->m_pplugin = nullptr;
734  pic->m_init_state = false;
735  }
736  }
737 
738  // Installed and loaded?
739  if (!pic->m_pplugin) { // Needs a reload?
740  if (pic->m_enabled) {
741  PluginStatus stat = pic->m_status;
742  PlugInContainer* newpic = LoadPlugIn(pic->m_plugin_file, pic);
743  if (newpic) {
744  pic->m_status = stat;
745  pic->m_enabled = true;
746  }
747  } else
748  continue;
749  }
750 
751  if (pic->m_enabled && !pic->m_init_state && pic->m_pplugin) {
752  wxString msg("PluginLoader: Initializing PlugIn: ");
753  msg += pic->m_plugin_file;
754  wxLogMessage(msg);
755  if (pic->m_cap_flag & INSTALLS_TOOLBOX_PAGE)
756  pic->m_has_setup_options = false;
757  pic->m_cap_flag = pic->m_pplugin->Init();
758  pic->m_pplugin->SetDefaults();
759  pic->m_init_state = true;
760  ProcessLateInit(pic);
761  pic->m_short_description = pic->m_pplugin->GetShortDescription();
762  pic->m_long_description = pic->m_pplugin->GetLongDescription();
763  pic->m_version_major = pic->m_pplugin->GetPlugInVersionMajor();
764  pic->m_version_minor = pic->m_pplugin->GetPlugInVersionMinor();
765  wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
766  pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
767  wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
768  bret = true;
769  } else if (!pic->m_enabled && pic->m_init_state) {
770  // Save a local copy of the plugin icon before unloading
771  wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
772  pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
773  wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
774 
775  bret = DeactivatePlugIn(pic);
776  if (pic->m_pplugin) pic->m_destroy_fn(pic->m_pplugin);
777  if (pic->m_library.IsLoaded()) pic->m_library.Unload();
778  pic->m_pplugin = nullptr;
779  pic->m_init_state = false;
780  }
781  }
782  evt_update_chart_types.Notify();
783  return bret;
784 }
785 
786 bool PluginLoader::DeactivatePlugIn(PlugInContainer* pic) {
787  if (!pic) return false;
788  if (pic->m_init_state) {
789  wxString msg("PluginLoader: Deactivating PlugIn: ");
790  wxLogMessage(msg + pic->m_plugin_file);
791  m_on_deactivate_cb(pic);
792  pic->m_init_state = false;
793  pic->m_pplugin->DeInit();
794  }
795  return true;
796 }
797 
798 bool PluginLoader::DeactivatePlugIn(const PlugInData& pd) {
799  auto pic = GetContainer(pd, plugin_array);
800  if (!pic) {
801  wxLogError("Attempt to deactivate non-existing plugin %s",
802  pd.m_common_name.ToStdString());
803  return false;
804  }
805  return DeactivatePlugIn(pic);
806 }
807 
808 bool PluginLoader::UnLoadPlugIn(size_t ix) {
809  if (ix >= plugin_array.GetCount()) {
810  wxLogWarning("Attempt to remove non-existing plugin %d", ix);
811  return false;
812  }
813  PlugInContainer* pic = plugin_array[ix];
814  if (!DeactivatePlugIn(pic)) {
815  return false;
816  }
817  if (pic->m_pplugin) {
818  pic->m_destroy_fn(pic->m_pplugin);
819  }
820 
821  delete pic; // This will unload the PlugIn via DTOR of pic->m_library
822  plugin_array.RemoveAt(ix);
823  return true;
824 }
825 
826 static std::string VersionFromManifest(const std::string& plugin_name) {
827  std::string version;
828  std::string path = PluginHandler::versionPath(plugin_name);
829  if (!path.empty() && wxFileName::IsFileReadable(path)) {
830  std::ifstream stream;
831  stream.open(path, std::ifstream::in);
832  stream >> version;
833  }
834  return version;
835 }
836 
839  using namespace std;
840  if (name.empty()) return {};
841 
842  auto import_path = PluginHandler::ImportedMetadataPath(name.c_str());
843  if (isRegularFile(import_path.c_str())) {
844  std::ifstream f(import_path.c_str());
845  std::stringstream ss;
846  ss << f.rdbuf();
847  PluginMetadata pd;
848  ParsePlugin(ss.str(), pd);
849  return pd;
850  }
851  auto available = PluginHandler::getInstance()->getCompatiblePlugins();
852  vector<PluginMetadata> matches;
853  copy_if(available.begin(), available.end(), back_inserter(matches),
854  [name](const PluginMetadata& md) { return md.name == name; });
855  if (matches.size() == 0) return {};
856  if (matches.size() == 1) return matches[0]; // only one found with given name
857 
858  auto version = VersionFromManifest(name);
859  auto predicate =
860  [version](const PluginMetadata& md) { return version == md.version; };
861  auto found = find_if(matches.begin(), matches.end(), predicate);
862  return found != matches.end() ? *found : matches[0];
863 }
864 
867  const PluginMetadata& md) {
868  auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
869  plugin->m_common_name.Lower());
870  bool is_system = found != SYSTEM_PLUGINS.end();
871 
872  std::string installed = VersionFromManifest(md.name);
873  plugin->m_manifest_version = installed;
874  auto installedVersion = SemanticVersion::parse(installed);
875  auto metaVersion = SemanticVersion::parse(md.version);
876 
877  if (is_system)
878  plugin->m_status = PluginStatus::System;
879  else if (installedVersion < metaVersion)
880  plugin->m_status = PluginStatus::ManagedInstalledUpdateAvailable;
881  else if (installedVersion == metaVersion)
882  plugin->m_status = PluginStatus::ManagedInstalledCurrentVersion;
883  else
884  plugin->m_status = PluginStatus::ManagedInstalledDowngradeAvailable;
885 
886  if (!is_system && md.is_orphan)
887  plugin->m_status = PluginStatus::Unmanaged;
888 
889 
890  plugin->m_managed_metadata = md;
891 }
892 
893 void PluginLoader::UpdateManagedPlugins(bool keep_orphans) {
894  std::vector<PlugInContainer*> loaded_plugins;
895  for (size_t i = 0; i < plugin_array.GetCount(); i++)
896  loaded_plugins.push_back(plugin_array.Item(i));
897 
898  // Initiate status to "unmanaged" or "system" on all plugins
899  for (auto& p : loaded_plugins) {
900  auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
901  p->m_common_name.Lower().ToStdString());
902  bool is_system = found != SYSTEM_PLUGINS.end();
903  p->m_status = is_system ? PluginStatus::System : PluginStatus::Unmanaged;
904  }
905  if (!keep_orphans) {
906  // Remove any inactive/uninstalled managed plugins that are no longer
907  // available in the current catalog Usually due to reverting from
908  // Alpha/Beta catalog back to master
909  auto predicate = [](const PlugInContainer* pd) -> bool {
910  const auto md(
911  PluginLoader::MetadataByName(pd->m_common_name.ToStdString()));
912  return md.name.empty() && !md.is_imported && !pd->m_pplugin &&
913  !IsSystemPluginName(pd->m_common_name.ToStdString());
914  };
915  auto end =
916  std::remove_if(loaded_plugins.begin(), loaded_plugins.end(), predicate);
917  loaded_plugins.erase(end, loaded_plugins.end());
918  }
919 
920  // Update from the catalog metadata
921  for (auto& plugin : loaded_plugins) {
922  auto md = PluginLoader::MetadataByName(plugin->m_common_name.ToStdString());
923  if (!md.name.empty()) {
924  auto import_path = PluginHandler::ImportedMetadataPath(md.name.c_str());
925  md.is_imported = isRegularFile(import_path.c_str());
926  if (isRegularFile(PluginHandler::fileListPath(md.name).c_str())) {
927  // This is an installed plugin
928  PluginLoader::UpdatePlugin(plugin, md);
929  } else if (IsSystemPluginName(md.name)) {
930  plugin->m_status = PluginStatus::System;
931  } else if (md.is_orphan) {
932  plugin->m_status = PluginStatus::Unmanaged;
933  } else if (plugin->m_api_version) {
934  // If the plugin is actually loaded, but the new plugin is known not
935  // to be installed, then it must be a legacy plugin loaded.
936  plugin->m_status = PluginStatus::LegacyUpdateAvailable;
937  plugin->m_managed_metadata = md;
938  } else {
939  // Otherwise, this is an uninstalled managed plugin.
940  plugin->m_status = PluginStatus::ManagedInstallAvailable;
941  }
942  }
943  }
944 
945  plugin_array.Clear();
946  for (const auto& p : loaded_plugins)
947  plugin_array.Add(p);
948  evt_pluglist_change.Notify();
949 }
950 
951 bool PluginLoader::UnLoadAllPlugIns() {
952  bool rv = true;
953  while (plugin_array.GetCount()) {
954  if (!UnLoadPlugIn(0)) {
955  rv = false;
956  }
957  }
958  return rv;
959 }
960 
961 bool PluginLoader::DeactivateAllPlugIns() {
962  for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
963  PlugInContainer* pic = plugin_array[i];
964  if (pic && pic->m_enabled && pic->m_init_state) DeactivatePlugIn(pic);
965  }
966  return true;
967 }
968 
969 #ifdef __WXMSW__
970 /*Convert Virtual Address to File Offset */
971 DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt) {
972  size_t i = 0;
973  PIMAGE_SECTION_HEADER pSeh;
974  if (rva == 0) {
975  return (rva);
976  }
977  pSeh = psh;
978  for (i = 0; i < pnt->FileHeader.NumberOfSections; i++) {
979  if (rva >= pSeh->VirtualAddress &&
980  rva < pSeh->VirtualAddress + pSeh->Misc.VirtualSize) {
981  break;
982  }
983  pSeh++;
984  }
985  return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
986 }
987 #endif
988 
989 class ModuleInfo {
990 public:
991  ModuleInfo() : type_magic(0) {}
992  WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, DependencySet);
993  WX_DECLARE_HASH_MAP(wxString, wxString, wxStringHash, wxStringEqual,
994  DependencyMap);
995 
996  uint64_t type_magic;
997  DependencyMap dependencies;
998 };
999 
1000 #ifdef USE_LIBELF
1001 bool ReadModuleInfoFromELF(const wxString& file,
1002  const ModuleInfo::DependencySet& dependencies,
1003  ModuleInfo& info) {
1004  static bool b_libelf_initialized = false;
1005  static bool b_libelf_usable = false;
1006 
1007  if (b_libelf_usable) {
1008  // Nothing to do.
1009  } else if (b_libelf_initialized) {
1010  return false;
1011  } else if (elf_version(EV_CURRENT) == EV_NONE) {
1012  b_libelf_initialized = true;
1013  b_libelf_usable = false;
1014  wxLogError("LibELF is outdated.");
1015  return false;
1016  } else {
1017  b_libelf_initialized = true;
1018  b_libelf_usable = true;
1019  }
1020 
1021  int file_handle;
1022  Elf* elf_handle = nullptr;
1023  GElf_Ehdr elf_file_header;
1024  Elf_Scn* elf_section_handle = nullptr;
1025 
1026  file_handle = open(file, O_RDONLY);
1027  if (file_handle == -1) {
1028  wxLogMessage("Could not open file \"%s\" for reading: %s", file,
1029  strerror(errno));
1030  goto FailureEpilogue;
1031  }
1032 
1033  elf_handle = elf_begin(file_handle, ELF_C_READ, nullptr);
1034  if (elf_handle == nullptr) {
1035  wxLogMessage("Could not get ELF structures from \"%s\".", file);
1036  goto FailureEpilogue;
1037  }
1038 
1039  if (gelf_getehdr(elf_handle, &elf_file_header) != &elf_file_header) {
1040  wxLogMessage("Could not get ELF file header from \"%s\".", file);
1041  goto FailureEpilogue;
1042  }
1043 
1044  switch (elf_file_header.e_type) {
1045  case ET_EXEC:
1046  case ET_DYN:
1047  break;
1048  default:
1049  wxLogMessage(wxString::Format(
1050  "Module \"%s\" is not an executable or shared library.", file));
1051  goto FailureEpilogue;
1052  }
1053 
1054  info.type_magic =
1055  (static_cast<uint64_t>(elf_file_header.e_ident[EI_CLASS])
1056  << 0) | // ELF class (32/64).
1057  (static_cast<uint64_t>(elf_file_header.e_ident[EI_DATA])
1058  << 8) | // Endianness.
1059  (static_cast<uint64_t>(elf_file_header.e_ident[EI_OSABI])
1060  << 16) | // OS ABI (Linux, FreeBSD, etc.).
1061  (static_cast<uint64_t>(elf_file_header.e_ident[EI_ABIVERSION])
1062  << 24) | // OS ABI version.
1063  (static_cast<uint64_t>(elf_file_header.e_machine)
1064  << 32) | // Instruction set.
1065  0;
1066 
1067  while ((elf_section_handle = elf_nextscn(elf_handle, elf_section_handle)) !=
1068  nullptr) {
1069  GElf_Shdr elf_section_header;
1070  Elf_Data* elf_section_data = nullptr;
1071  size_t elf_section_entry_count = 0;
1072 
1073  if (gelf_getshdr(elf_section_handle, &elf_section_header) !=
1074  &elf_section_header) {
1075  wxLogMessage("Could not get ELF section header from \"%s\".", file);
1076  goto FailureEpilogue;
1077  } else if (elf_section_header.sh_type != SHT_DYNAMIC) {
1078  continue;
1079  }
1080 
1081  elf_section_data = elf_getdata(elf_section_handle, nullptr);
1082  if (elf_section_data == nullptr) {
1083  wxLogMessage("Could not get ELF section data from \"%s\".", file);
1084  goto FailureEpilogue;
1085  }
1086 
1087  if ((elf_section_data->d_size == 0) ||
1088  (elf_section_header.sh_entsize == 0)) {
1089  wxLogMessage("Got malformed ELF section metadata from \"%s\".", file);
1090  goto FailureEpilogue;
1091  }
1092 
1093  elf_section_entry_count =
1094  elf_section_data->d_size / elf_section_header.sh_entsize;
1095  for (size_t elf_section_entry_index = 0;
1096  elf_section_entry_index < elf_section_entry_count;
1097  ++elf_section_entry_index) {
1098  GElf_Dyn elf_dynamic_entry;
1099  const char* elf_dynamic_entry_name = nullptr;
1100  if (gelf_getdyn(elf_section_data,
1101  static_cast<int>(elf_section_entry_index),
1102  &elf_dynamic_entry) != &elf_dynamic_entry) {
1103  wxLogMessage("Could not get ELF dynamic_section entry from \"%s\".",
1104  file);
1105  goto FailureEpilogue;
1106  } else if (elf_dynamic_entry.d_tag != DT_NEEDED) {
1107  continue;
1108  }
1109  elf_dynamic_entry_name = elf_strptr(
1110  elf_handle, elf_section_header.sh_link, elf_dynamic_entry.d_un.d_val);
1111  if (elf_dynamic_entry_name == nullptr) {
1112  wxLogMessage(wxString::Format("Could not get %s %s from \"%s\".", "ELF",
1113  "string entry", file));
1114  goto FailureEpilogue;
1115  }
1116  wxString name_full(elf_dynamic_entry_name);
1117  wxString name_part(elf_dynamic_entry_name,
1118  strcspn(elf_dynamic_entry_name, "-."));
1119  if (dependencies.find(name_part) != dependencies.end()) {
1120  info.dependencies.insert(
1121  ModuleInfo::DependencyMap::value_type(name_part, name_full));
1122  }
1123  }
1124  }
1125 
1126  goto SuccessEpilogue;
1127 
1128 SuccessEpilogue:
1129  elf_end(elf_handle);
1130  close(file_handle);
1131  return true;
1132 
1133 FailureEpilogue:
1134  if (elf_handle != nullptr) elf_end(elf_handle);
1135  if (file_handle >= 0) close(file_handle);
1136  wxLog::FlushActive();
1137  return false;
1138 }
1139 #endif // USE_LIBELF
1140 
1141 bool PluginLoader::CheckPluginCompatibility(const wxString& plugin_file) {
1142  bool b_compat = false;
1143 #ifdef __WXOSX__
1144  //TODO: Actually do some tests (In previous versions b_compat was initialized to true, so the actual behavior was exactly like this)
1145  b_compat = true;
1146 #endif
1147 #ifdef __WXMSW__
1148  // For Windows we identify the dll file containing the core wxWidgets functions
1149  // Later we will compare this with the file containing the wxWidgets functions used
1150  // by plugins. If these file names match exactly then we assume the plugin is compatible.
1151  // By using the file names we avoid having to hard code the file name into the OpenCPN sources.
1152  // This makes it easier to update wxWigets versions without editing sources.
1153  // NOTE: this solution may not follow symlinks but almost no one uses simlinks for wxWidgets dlls
1154 
1155  // Only go through this process once per instance of O.
1156  if (!m_found_wxwidgets) {
1157  DWORD myPid = GetCurrentProcessId();
1158  HANDLE hProcess =
1159  OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, myPid);
1160  if (hProcess == NULL) {
1161  wxLogMessage(wxString::Format("Cannot identify running process for %s",
1162  plugin_file.c_str()));
1163  } else {
1164  // Find namme of wxWidgets core DLL used by the current process
1165  // so we can compare it to the one used by the plugin
1166  HMODULE hMods[1024];
1167  DWORD cbNeeded;
1168  if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
1169  for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
1170  TCHAR szModName[MAX_PATH];
1171  if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
1172  sizeof(szModName) / sizeof(TCHAR))) {
1173  m_module_name = szModName;
1174  if (m_module_name.Find("wxmsw") != wxNOT_FOUND) {
1175  if (m_module_name.Find("_core_") != wxNOT_FOUND) {
1176  m_found_wxwidgets = true;
1177  wxLogMessage(wxString::Format(
1178  "Found wxWidgets core DLL: %s",
1179  m_module_name.c_str()));
1180  break;
1181  }
1182  }
1183  }
1184  }
1185  } else {
1186  wxLogMessage(wxString::Format("Cannot enumerate process modules for %s",
1187  plugin_file.c_str()));
1188  }
1189  if (hProcess) CloseHandle(hProcess);
1190  }
1191  }
1192  if (!m_found_wxwidgets) {
1193  wxLogMessage(wxString::Format("Cannot identify wxWidgets core DLL for %s",
1194  plugin_file.c_str()));
1195  } else {
1196  LPCWSTR fName = plugin_file.wc_str();
1197  HANDLE handle = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING,
1198  FILE_ATTRIBUTE_NORMAL, 0);
1199  DWORD byteread, size = GetFileSize(handle, NULL);
1200  PVOID virtualpointer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
1201  bool status = ReadFile(handle, virtualpointer, size, &byteread, NULL);
1202  CloseHandle(handle);
1203  PIMAGE_NT_HEADERS ntheaders =
1204  (PIMAGE_NT_HEADERS)(PCHAR(virtualpointer) +
1205  PIMAGE_DOS_HEADER(virtualpointer)->e_lfanew);
1206  PIMAGE_SECTION_HEADER pSech =
1207  IMAGE_FIRST_SECTION(ntheaders); // Pointer to first section header
1208  PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // Pointer to import descriptor
1209  if (ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
1210  .Size !=
1211  0) /*if size of the table is 0 - Import Table does not exist */
1212  {
1213  pImportDescriptor =
1214  (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)virtualpointer +
1215  Rva2Offset(
1216  ntheaders->OptionalHeader
1217  .DataDirectory
1218  [IMAGE_DIRECTORY_ENTRY_IMPORT]
1219  .VirtualAddress,
1220  pSech, ntheaders));
1221  LPSTR libname[256];
1222  size_t i = 0;
1223  // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR or we find core wxWidgets DLL
1224  while (pImportDescriptor->Name != 0) {
1225  // Get the name of each DLL
1226  libname[i] =
1227  (PCHAR)((DWORD_PTR)virtualpointer +
1228  Rva2Offset(pImportDescriptor->Name, pSech, ntheaders));
1229  // Check if the plugin DLL dependencey is same as main process wxWidgets core DLL
1230  if (m_module_name.Find(libname[i]) != wxNOT_FOUND) {
1231  // Match found - plugin is compatible
1232  b_compat = true;
1233  wxLogMessage(
1234  wxString::Format("Compatible wxWidgets plugin library found for %s: %s",
1235  plugin_file.c_str(), libname[i]));
1236  break;
1237  }
1238  pImportDescriptor++; // advance to next IMAGE_IMPORT_DESCRIPTOR
1239  i++;
1240  }
1241  } else {
1242  wxLogMessage(
1243  wxString::Format("No Import Table! in %s", plugin_file.c_str()));
1244  }
1245  if (virtualpointer) VirtualFree(virtualpointer, size, MEM_DECOMMIT);
1246  }
1247 #endif
1248 #if defined(__WXGTK__) || defined(__WXQT__)
1249 #if defined(USE_LIBELF)
1250 
1251  static bool b_own_info_queried = false;
1252  static bool b_own_info_usable = false;
1253  static ModuleInfo own_info;
1254  static ModuleInfo::DependencySet dependencies;
1255 
1256  if (!b_own_info_queried) {
1257  dependencies.insert("libwx_baseu");
1258 
1259  char exe_buf[100] = {0};
1260  ssize_t len = readlink("/proc/self/exe", exe_buf, 99);
1261  if (len > 0) {
1262  exe_buf[len] = '\0';
1263  wxString app_path(exe_buf);
1264  wxLogMessage("Executable path: %s", exe_buf);
1265  b_own_info_usable =
1266  ReadModuleInfoFromELF(app_path, dependencies, own_info);
1267  if (!b_own_info_usable) {
1268  wxLogMessage("Cannot get own info from: %s", exe_buf);
1269  }
1270  } else {
1271  wxLogMessage("Cannot get own executable path.");
1272  }
1273  b_own_info_queried = true;
1274  }
1275 
1276  if (b_own_info_usable) {
1277  bool b_pi_info_usable = false;
1278  ModuleInfo pi_info;
1279  b_pi_info_usable =
1280  ReadModuleInfoFromELF(plugin_file, dependencies, pi_info);
1281  if (b_pi_info_usable) {
1282  b_compat = (pi_info.type_magic == own_info.type_magic);
1283 
1284  // OSABI field on flatpak builds
1285  if ((pi_info.type_magic ^ own_info.type_magic) == 0x00030000) {
1286  b_compat = true;
1287  }
1288 
1289  if (!b_compat) {
1290  pi_info.dependencies.clear();
1291  wxLogMessage(
1292  wxString::Format(" Plugin \"%s\" is of another binary "
1293  "flavor than the main module.",
1294  plugin_file));
1295  wxLogMessage("host magic: %.8x, plugin magic: %.8x",
1296  own_info.type_magic, pi_info.type_magic);
1297  }
1298  for (const auto& own_dependency : own_info.dependencies) {
1299  ModuleInfo::DependencyMap::const_iterator pi_dependency =
1300  pi_info.dependencies.find(own_dependency.first);
1301  if ((pi_dependency != pi_info.dependencies.end()) &&
1302  (pi_dependency->second != own_dependency.second)) {
1303  b_compat = false;
1304  wxLogMessage(
1305  " Plugin \"%s\" depends on library \"%s\", but the main "
1306  "module was built for \"%s\".",
1307  plugin_file, pi_dependency->second, own_dependency.second);
1308  break;
1309  }
1310  }
1311  } else {
1312  b_compat = false;
1313  wxLogMessage(
1314  wxString::Format(" Plugin \"%s\" could not be reliably "
1315  "checked for compatibility.",
1316  plugin_file));
1317  }
1318  } else {
1319  // Allow any plugin when own info is not available.
1320  b_compat = true;
1321  }
1322 
1323  wxLogMessage("Plugin is compatible by elf library scan: %s",
1324  b_compat ? "true" : "false");
1325 
1326  wxLog::FlushActive();
1327  return b_compat;
1328 
1329 #endif // LIBELF
1330 
1331  // But Android Plugins do not include the wxlib specification in their ELF
1332  // file. So we assume Android Plugins are compatible....
1333 #ifdef __ANDROID__
1334  return true;
1335 #endif
1336 
1337  // If libelf is not available, then we must use a simplistic file scan method.
1338  // This is easily fooled if the wxWidgets version in use is not exactly
1339  // recognized. File scan is 3x faster than the ELF scan method
1340 
1341  FILE* f = fopen(plugin_file, "r");
1342  char strver[26]; // Enough space even for very big integers...
1343  if (f == NULL) {
1344  wxLogMessage("Plugin %s can't be opened", plugin_file);
1345  return false;
1346  }
1347  sprintf(strver,
1348 #if defined(__WXGTK3__)
1349  "libwx_gtk3u_core-%i.%i"
1350 #elif defined(__WXGTK20__)
1351  "libwx_gtk2u_core-%i.%i"
1352 #elif defined(__WXQT__)
1353  "libwx_qtu_core-%i.%i"
1354 #else
1355 #error undefined plugin platform
1356 #endif
1357  ,
1358  wxMAJOR_VERSION, wxMINOR_VERSION);
1359  b_compat = false;
1360 
1361  size_t pos(0);
1362  size_t len(strlen(strver));
1363  int c;
1364  while ((c = fgetc(f)) != EOF) {
1365  if (c == strver[pos]) {
1366  if (++pos == len) {
1367  b_compat = true;
1368  break;
1369  }
1370  } else
1371  pos = 0;
1372  }
1373  fclose(f);
1374 #endif // __WXGTK__ or __WXQT__
1375 
1376  wxLogMessage("Plugin is compatible: %s", b_compat ? "true" : "false");
1377  return b_compat;
1378 }
1379 
1380 PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file) {
1381  auto pic = new PlugInContainer;
1382  if (!LoadPlugIn(plugin_file, pic)) {
1383  delete pic;
1384  return nullptr;
1385  } else {
1386  return pic;
1387  }
1388 }
1389 
1390 PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file,
1391  PlugInContainer* pic) {
1392  wxLogMessage(wxString("PluginLoader: Loading PlugIn: ") + plugin_file);
1393 
1394  if (plugin_file.empty()) {
1395  wxLogMessage("Ignoring loading of empty path");
1396  return nullptr;
1397  }
1398 
1399  if (!wxIsReadable(plugin_file)) {
1400  wxLogMessage("Ignoring unreadable plugin %s",
1401  plugin_file.ToStdString().c_str());
1402  LoadError le(LoadError::Type::Unreadable, plugin_file.ToStdString());
1403  load_errors.push_back(le);
1404  return nullptr;
1405  }
1406 
1407  // Check if blacklisted, exit if so.
1408  auto sts =
1409  m_blacklist->get_status(pic->m_common_name.ToStdString(),
1410  pic->m_version_major, pic->m_version_minor);
1411  if (sts != plug_status::unblocked) {
1412  wxLogDebug("Refusing to load blacklisted plugin: %s",
1413  pic->m_common_name.ToStdString().c_str());
1414  return nullptr;
1415  }
1416  auto data = m_blacklist->get_library_data(plugin_file.ToStdString());
1417  if (!data.name.empty()) {
1418  wxLogDebug("Refusing to load blacklisted library: %s",
1419  plugin_file.ToStdString().c_str());
1420  return nullptr;
1421  }
1422  pic->m_plugin_file = plugin_file;
1423  pic->m_status =
1424  PluginStatus::Unmanaged; // Status is updated later, if necessary
1425 
1426  // load the library
1427  if (pic->m_library.IsLoaded()) pic->m_library.Unload();
1428  pic->m_library.Load(plugin_file);
1429 
1430  if (!pic->m_library.IsLoaded()) {
1431  // Look in the Blacklist, try to match a filename, to give some kind of
1432  // message extract the probable plugin name
1433  wxFileName fn(plugin_file);
1434  std::string name = fn.GetName().ToStdString();
1435  auto found = m_blacklist->get_library_data(name);
1436  if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1437  wxLogMessage("Ignoring blacklisted plugin %s", name.c_str());
1438  if (!found.name.empty()) {
1439  SemanticVersion v(found.major, found.minor);
1440  LoadError le(LoadError::Type::Unloadable, name, v);
1441  load_errors.push_back(le);
1442  } else {
1443  LoadError le(LoadError::Type::Unloadable, plugin_file.ToStdString());
1444  load_errors.push_back(le);
1445  }
1446  }
1447  wxLogMessage(wxString(" PluginLoader: Cannot load library: ") +
1448  plugin_file);
1449  return nullptr;
1450  }
1451 
1452  // load the factory symbols
1453  const char* const FIX_LOADING =
1454  _("\n Install/uninstall plugin or remove file to mute message");
1455  create_t* create_plugin = (create_t*)pic->m_library.GetSymbol("create_pi");
1456  if (nullptr == create_plugin) {
1457  std::string msg(_(" PluginLoader: Cannot load symbol create_pi: "));
1458  wxLogMessage(msg + plugin_file);
1459  if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1460  LoadError le(LoadError::Type::NoCreate, plugin_file.ToStdString());
1461  load_errors.push_back(le);
1462  }
1463  return nullptr;
1464  }
1465 
1466  destroy_t* destroy_plugin =
1467  (destroy_t*)pic->m_library.GetSymbol("destroy_pi");
1468  pic->m_destroy_fn = destroy_plugin;
1469  if (nullptr == destroy_plugin) {
1470  wxLogMessage(" PluginLoader: Cannot load symbol destroy_pi: " +
1471  plugin_file);
1472  if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1473  LoadError le(LoadError::Type::NoDestroy, plugin_file.ToStdString());
1474  load_errors.push_back(le);
1475  }
1476  return nullptr;
1477  }
1478 
1479  // create an instance of the plugin class
1480  opencpn_plugin* plug_in = create_plugin(this);
1481 
1482  int api_major = plug_in->GetAPIVersionMajor();
1483  int api_minor = plug_in->GetAPIVersionMinor();
1484  int api_ver = (api_major * 100) + api_minor;
1485  pic->m_api_version = api_ver;
1486 
1487  int pi_major = plug_in->GetPlugInVersionMajor();
1488  int pi_minor = plug_in->GetPlugInVersionMinor();
1489  SemanticVersion pi_ver(pi_major, pi_minor, -1);
1490 
1491  wxString pi_name = plug_in->GetCommonName();
1492 
1493  wxLogDebug("blacklist: Get status for %s %d %d",
1494  pi_name.ToStdString().c_str(), pi_major, pi_minor);
1495  const auto status =
1496  m_blacklist->get_status(pi_name.ToStdString(), pi_major, pi_minor);
1497  if (status != plug_status::unblocked) {
1498  wxLogDebug("Ignoring blacklisted plugin.");
1499  if (status != plug_status::unloadable) {
1500  SemanticVersion v(pi_major, pi_minor);
1501  LoadError le(LoadError::Type::Blacklisted, pi_name.ToStdString(), v);
1502  load_errors.push_back(le);
1503  }
1504  return nullptr;
1505  }
1506 
1507  switch (api_ver) {
1508  case 105:
1509  pic->m_pplugin = dynamic_cast<opencpn_plugin*>(plug_in);
1510  break;
1511 
1512  case 106:
1513  pic->m_pplugin = dynamic_cast<opencpn_plugin_16*>(plug_in);
1514  break;
1515 
1516  case 107:
1517  pic->m_pplugin = dynamic_cast<opencpn_plugin_17*>(plug_in);
1518  break;
1519 
1520  case 108:
1521  pic->m_pplugin = dynamic_cast<opencpn_plugin_18*>(plug_in);
1522  break;
1523 
1524  case 109:
1525  pic->m_pplugin = dynamic_cast<opencpn_plugin_19*>(plug_in);
1526  break;
1527 
1528  case 110:
1529  pic->m_pplugin = dynamic_cast<opencpn_plugin_110*>(plug_in);
1530  break;
1531 
1532  case 111:
1533  pic->m_pplugin = dynamic_cast<opencpn_plugin_111*>(plug_in);
1534  break;
1535 
1536  case 112:
1537  pic->m_pplugin = dynamic_cast<opencpn_plugin_112*>(plug_in);
1538  break;
1539 
1540  case 113:
1541  pic->m_pplugin = dynamic_cast<opencpn_plugin_113*>(plug_in);
1542  break;
1543 
1544  case 114:
1545  pic->m_pplugin = dynamic_cast<opencpn_plugin_114*>(plug_in);
1546  break;
1547  case 115:
1548  pic->m_pplugin = dynamic_cast<opencpn_plugin_115*>(plug_in);
1549  break;
1550 
1551  case 116:
1552  pic->m_pplugin = dynamic_cast<opencpn_plugin_116*>(plug_in);
1553  break;
1554 
1555  case 117:
1556  pic->m_pplugin = dynamic_cast<opencpn_plugin_117*>(plug_in);
1557  do /* force a local scope */ {
1558  auto p = dynamic_cast<opencpn_plugin_117*>(plug_in);
1559  pi_ver =
1560  SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1561  p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1562  p->GetPlugInVersionBuild());
1563  } while (false); // NOLINT
1564  break;
1565  case 118:
1566  pic->m_pplugin = dynamic_cast<opencpn_plugin_118*>(plug_in);
1567  do /* force a local scope */ {
1568  auto p = dynamic_cast<opencpn_plugin_118*>(plug_in);
1569  pi_ver =
1570  SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1571  p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1572  p->GetPlugInVersionBuild());
1573  } while (false); // NOLINT
1574  break;
1575 
1576  default:
1577  break;
1578  }
1579 
1580  if (!pic->m_pplugin) {
1581  INFO_LOG << _("Incompatible plugin detected: ") << plugin_file << "\n";
1582  INFO_LOG << _(" API Version detected: ");
1583  INFO_LOG << api_major << "." << api_minor << "\n";
1584  INFO_LOG << _(" PlugIn Version detected: ") << pi_ver << "\n";
1585  if (m_blacklist->mark_unloadable(pi_name.ToStdString(), pi_ver.major,
1586  pi_ver.minor)) {
1587  LoadError le(LoadError::Type::Incompatible, pi_name.ToStdString(),
1588  pi_ver);
1589  load_errors.push_back(le);
1590  }
1591  return nullptr;
1592  }
1593  return pic;
1594 }
wxString & GetPluginDir()
The original in-tree plugin directory, sometimes not user-writable.
Wrapper for configuration variables which lives in a wxBaseConfig object.
const void Notify()
Notify all listeners, no data supplied.
Data for a loaded plugin, including dl-loaded library.
Definition: plugin_loader.h:99
Basic data for a loaded plugin, trivially copyable.
Definition: plugin_loader.h:64
wxString m_plugin_filename
The short file path.
Definition: plugin_loader.h:77
wxString m_plugin_file
The full file path.
Definition: plugin_loader.h:76
int m_cap_flag
PlugIn Capabilities descriptor.
Definition: plugin_loader.h:75
PlugInData(const PluginMetadata &md)
Create a container with applicable fields defined from metadata.
wxString m_common_name
A common name string for the plugin.
Definition: plugin_loader.h:79
bool m_has_setup_options
Has run NotifySetupOptionsPlugin()
Definition: plugin_loader.h:71
std::string Key() const
sort key.
std::string m_manifest_version
As detected from manifest.
Definition: plugin_loader.h:89
wxDateTime m_plugin_modification
used to detect upgraded plugins
Definition: plugin_loader.h:78
wxString m_version_str
Complete version as of semantic_vers.
Definition: plugin_loader.h:88
std::vector< PluginMetadata > getCompatiblePlugins()
Return list of available, unique and compatible plugins from configured XML catalog.
static std::string ImportedMetadataPath(std::string name)
Return path to imported metadata for given plugin.
static std::string fileListPath(std::string name)
Return path to installation manifest for given plugin.
static std::string versionPath(std::string name)
Return path to file containing version for given plugin.
PluginLoader is a backend module without any direct GUI functionality.
bool LoadAllPlugIns(bool enabled_plugins, bool keep_orphans=false)
Update catalog with imported metadata and load all plugin library files.
EventVar evt_plugin_loadall_finalize
Emitted after all plugins are loaded.
static std::string GetPluginVersion(const PlugInData pd, std::function< const PluginMetadata(const std::string &)> get_metadata)
Return version string for a plugin, possibly with an "Imported" suffix.
bool UnLoadPlugIn(size_t ix)
Unload, delete and remove item ix in GetPlugInArray().
void SortPlugins(int(*cmp_func)(PlugInContainer **, PlugInContainer **))
Sort GetPluginArray().
static void UpdatePlugin(PlugInContainer *plugin, const PluginMetadata &md)
Update PlugInContainer status using data from PluginMetadata and manifest.
void SetEnabled(const wxString &common_name, bool enabled)
Update enabled/disabled state for plugin with given name.
void SetToolboxPanel(const wxString &common_name, bool value)
Update m_toolbox_panel state for plugin with given name.
static PluginMetadata MetadataByName(const std::string &name)
Find metadata for given plugin.
void RemovePlugin(const PlugInData &pd)
Remove a plugin from *GetPluginArray().
void ShowPreferencesDialog(const PlugInData &pd, wxWindow *parent)
Display the preferences dialog for a plugin.
std::vector< std::string > Bindirs()
'List of directories for plugin binary helpers.
Definition: plugin_paths.h:29
std::vector< std::string > Libdirs()
List of directories from which we load plugins.
Definition: plugin_paths.h:26
static PluginPaths * getInstance()
Return the singleton instance.
virtual wxBitmap * GetPlugInBitmap()
FIXME static wxBitmap* LoadSVG(const wxString filename, unsigned int width, unsigned int height) { if...
Definition: ocpn_plugin.cpp:78
Global variables reflecting command line options and arguments.
Plugin metadata, reflects the xml format directly.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
Definition: semantic_vers.h:51
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.
std::string to_string()
Return printable representation.