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