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