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 if (!g_bportable) {
571 if (base_plugin_path.IsSameAs(plugin_file_path)) {
572 if (!IsSystemPluginPath(file_name.ToStdString())) {
573 DEBUG_LOG << "Skipping plugin " << file_name << " in "
575
576 ClearLoadStamp(plugin_loadstamp.ToStdString()); // Not a fatal error
577 return false;
578 }
579 }
580 }
581
582 if (!IsSystemPluginPath(file_name.ToStdString()) && safe_mode::get_mode()) {
583 DEBUG_LOG << "Skipping plugin " << file_name << " in safe mode";
584 ClearLoadStamp(plugin_loadstamp.ToStdString()); // Not a fatal error
585 return false;
586 }
587
588 auto msg =
589 std::string("Checking plugin compatibility: ") + file_name.ToStdString();
590 wxLogMessage(msg.c_str());
591 wxLog::FlushActive();
592
593 bool b_compat = CheckPluginCompatibility(file_name);
594
595 if (!b_compat) {
596 msg =
597 std::string("Incompatible plugin detected: ") + file_name.ToStdString();
598 wxLogMessage(msg.c_str());
599 if (m_blacklist->mark_unloadable(file_name.ToStdString())) {
600 LoadError le(LoadError::Type::Unloadable, file_name.ToStdString());
601 load_errors.push_back(le);
602 }
603 return false;
604 }
605
606 PlugInContainer* pic = LoadPlugIn(file_name);
607
608 // Check the config file to see if this PlugIn is user-enabled,
609 // only loading enabled plugins.
610 // Make the check late enough to pick up incompatible plugins anyway
611 const auto path = std::string("/PlugIns/") + plugin_file.ToStdString();
612 ConfigVar<bool> enabled(path, "bEnabled", TheBaseConfig());
613 if (pic && load_enabled && !enabled.Get(true)) {
614 pic->m_destroy_fn(pic->m_pplugin);
615 delete pic;
616 wxLogMessage("Skipping not enabled candidate.");
617 ClearLoadStamp(plugin_loadstamp.ToStdString());
618 return true;
619 }
620
621 if (pic) {
622 if (pic->m_pplugin) {
623 plugin_array.Add(pic);
624
625 // The common name is available without initialization and startup of
626 // the PlugIn
627 pic->m_common_name = pic->m_pplugin->GetCommonName();
628 pic->m_plugin_filename = plugin_file;
629 pic->m_plugin_modification = plugin_modification;
630 pic->m_enabled = enabled.Get(false);
631
632 if (safe_mode::get_mode()) {
633 pic->m_enabled = false;
634 enabled.Set(false);
635 }
636 if (dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
637 // The CLI has no graphics context, but plugins assumes there is.
638 if (pic->m_enabled) {
639 pic->m_cap_flag = pic->m_pplugin->Init();
640 pic->m_init_state = true;
641 }
642 }
644 wxLog::FlushActive();
645
646 std::string found_version;
647 for (const auto& p : PluginHandler::GetInstance()->GetInstalled()) {
648 if (ocpn::tolower(p.name) == pic->m_common_name.Lower()) {
649 found_version = p.readonly ? "" : p.version;
650 break;
651 }
652 }
653 pic->m_version_str = found_version;
654 pic->m_short_description = pic->m_pplugin->GetShortDescription();
655 pic->m_long_description = pic->m_pplugin->GetLongDescription();
656 pic->m_version_major = pic->m_pplugin->GetPlugInVersionMajor();
657 pic->m_version_minor = pic->m_pplugin->GetPlugInVersionMinor();
658 m_on_activate_cb(pic);
659
660 auto pbm0 = pic->m_pplugin->GetPlugInBitmap();
661 if (!pbm0->IsOk()) {
662 pbm0 = (wxBitmap*)GetPluginDefaultIcon();
663 }
664 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
665 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
666
667 if (!pic->m_enabled && pic->m_destroy_fn) {
668 pic->m_destroy_fn(pic->m_pplugin);
669 pic->m_destroy_fn = nullptr;
670 pic->m_pplugin = nullptr;
671 pic->m_init_state = false;
672 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
673 }
674
675 // Check to see if the plugin just processed has an associated catalog
676 // entry understanding that SYSTEM plugins have no metadata by design
677 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
678 pic->m_common_name.Lower());
679 bool is_system = found != SYSTEM_PLUGINS.end();
680
681 if (!is_system) {
683 wxString name = pic->m_common_name;
684 auto it = find_if(
685 available.begin(), available.end(),
686 [name](const PluginMetadata& md) { return md.name == name; });
687
688 if (it == available.end()) {
689 // Installed plugin is an orphan....
690 // Add a stub metadata entry to the active CatalogHandler context
691 // to satisfy minimal PIM functionality
692
693 auto oprhan_metadata = CreateMetadata(pic);
694 auto catalogHdlr = CatalogHandler::GetInstance();
695 catalogHdlr->AddMetadataToActiveContext(oprhan_metadata);
696 }
697 }
698
699 } else { // No pic->m_pplugin
700 wxLogMessage(
701 " PluginLoader: Unloading invalid PlugIn, API version %d ",
702 pic->m_api_version);
703 pic->m_destroy_fn(pic->m_pplugin);
704
705 LoadError le(LoadError::Type::Unloadable, file_name.ToStdString());
706 delete pic;
707 load_errors.push_back(le);
708 return false;
709 }
710 } else { // pic == 0
711 return false;
712 }
713 ClearLoadStamp(plugin_loadstamp.ToStdString());
714 return true;
715}
716
717// Helper function: loads all plugins from a single directory
718bool PluginLoader::LoadPlugInDirectory(const wxString& plugin_dir,
719 bool load_enabled) {
721 m_plugin_location = plugin_dir;
722
723 wxString msg("PluginLoader searching for PlugIns in location ");
724 msg += m_plugin_location;
725 wxLogMessage(msg);
726
727#ifdef __WXMSW__
728 wxString pispec = "*_pi.dll";
729#elif defined(__WXOSX__)
730 wxString pispec = "*_pi.dylib";
731#else
732 wxString pispec = "*_pi.so";
733#endif
734
735 if (!::wxDirExists(m_plugin_location)) {
736 msg = m_plugin_location;
737 msg.Prepend(" Directory ");
738 msg.Append(" does not exist.");
739 wxLogMessage(msg);
740 return false;
741 }
742
743 if (!g_BasePlatform->isPlatformCapable(PLATFORM_CAP_PLUGINS)) return false;
744
745 wxArrayString file_list;
746
747 int get_flags = wxDIR_FILES | wxDIR_DIRS;
748#ifdef __WXMSW__
749#ifdef _DEBUG
750 get_flags = wxDIR_FILES;
751#endif
752#endif
753
754#ifdef __ANDROID__
755 get_flags = wxDIR_FILES; // No subdirs, especially "/files" where PlugIns are
756 // initially placed in APK
757#endif
758
759 bool ret =
760 false; // return true if at least one new plugins gets loaded/unloaded
761 wxDir::GetAllFiles(m_plugin_location, &file_list, pispec, get_flags);
762
763 wxLogMessage("Found %d candidates", (int)file_list.GetCount());
764 for (auto& file_name : file_list) {
765 wxLog::FlushActive();
766
767 LoadPluginCandidate(file_name, load_enabled);
768 }
769
770 // Scrub the plugin array...
771 // Here, looking for duplicates caused by new installation of a plugin
772 // We want to remove the previous entry representing the uninstalled packaged
773 // plugin metadata
774 for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
775 PlugInContainer* pic = plugin_array[i];
776 for (unsigned int j = i + 1; j < plugin_array.GetCount(); j++) {
777 PlugInContainer* pict = plugin_array[j];
778
779 if (pic->m_common_name == pict->m_common_name) {
780 if (pic->m_plugin_file.IsEmpty())
781 plugin_array.Item(i)->m_status = PluginStatus::PendingListRemoval;
782 else
783 plugin_array.Item(j)->m_status = PluginStatus::PendingListRemoval;
784 }
785 }
786 }
787
788 // Remove any list items marked
789 size_t i = 0;
790 while ((i >= 0) && (i < plugin_array.GetCount())) {
791 PlugInContainer* pict = plugin_array.Item(i);
792 if (pict->m_status == PluginStatus::PendingListRemoval) {
793 plugin_array.RemoveAt(i);
794 i = 0;
795 } else
796 i++;
797 }
798
799 return ret;
800}
801
803 bool bret = false;
804
805 for (const auto& pic : plugin_array) {
806 // Try to confirm that the m_pplugin member points to a valid plugin
807 // image...
808 if (pic->m_pplugin) {
809 auto ppl = dynamic_cast<opencpn_plugin*>(pic->m_pplugin);
810 if (!ppl) {
811 pic->m_pplugin = nullptr;
812 pic->m_init_state = false;
813 }
814 }
815
816 // Installed and loaded?
817 if (!pic->m_pplugin) { // Needs a reload?
818 if (pic->m_enabled) {
819 PluginStatus stat = pic->m_status;
820 PlugInContainer* newpic = LoadPlugIn(pic->m_plugin_file, pic);
821 if (newpic) {
822 pic->m_status = stat;
823 pic->m_enabled = true;
824 }
825 } else
826 continue;
827 }
828
829 if (pic->m_enabled && !pic->m_init_state && pic->m_pplugin) {
830 wxString msg("PluginLoader: Initializing PlugIn: ");
831 msg += pic->m_plugin_file;
832 wxLogMessage(msg);
834 pic->m_has_setup_options = false;
835 pic->m_cap_flag = pic->m_pplugin->Init();
836 pic->m_pplugin->SetDefaults();
837 pic->m_init_state = true;
838 ProcessLateInit(pic);
839 pic->m_short_description = pic->m_pplugin->GetShortDescription();
840 pic->m_long_description = pic->m_pplugin->GetLongDescription();
841 pic->m_version_major = pic->m_pplugin->GetPlugInVersionMajor();
842 pic->m_version_minor = pic->m_pplugin->GetPlugInVersionMinor();
843 wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
844 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
845 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
846 m_on_activate_cb(pic);
847 bret = true;
848 } else if (!pic->m_enabled && pic->m_init_state) {
849 // Save a local copy of the plugin icon before unloading
850 wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
851 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
852 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
853
854 bret = DeactivatePlugIn(pic);
855 if (pic->m_pplugin) pic->m_destroy_fn(pic->m_pplugin);
856 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
857 pic->m_pplugin = nullptr;
858 pic->m_init_state = false;
859 pic->m_has_setup_options = false;
860 }
861 }
863 return bret;
864}
865
867 if (!pic) return false;
868 if (pic->m_init_state) {
869 wxString msg("PluginLoader: Deactivating PlugIn: ");
870 wxLogMessage(msg + pic->m_plugin_file);
871 m_on_deactivate_cb(pic);
872 pic->m_init_state = false;
873 pic->m_pplugin->DeInit();
874 }
875 return true;
876}
877
879 auto pic = GetContainer(pd, plugin_array);
880 if (!pic) {
881 wxLogError("Attempt to deactivate non-existing plugin %s",
882 pd.m_common_name.ToStdString());
883 return false;
884 }
885 return DeactivatePlugIn(pic);
886}
887
889 if (ix >= plugin_array.GetCount()) {
890 wxLogWarning("Attempt to remove non-existing plugin %d", ix);
891 return false;
892 }
893 PlugInContainer* pic = plugin_array[ix];
894 if (!DeactivatePlugIn(pic)) {
895 return false;
896 }
897 if (pic->m_pplugin) {
898 pic->m_destroy_fn(pic->m_pplugin);
899 }
900
901 delete pic; // This will unload the PlugIn via DTOR of pic->m_library
902 plugin_array.RemoveAt(ix);
903 return true;
904}
905
906static std::string VersionFromManifest(const std::string& plugin_name) {
907 std::string version;
908 std::string path = PluginHandler::VersionPath(plugin_name);
909 if (!path.empty() && wxFileName::IsFileReadable(path)) {
910 std::ifstream stream;
911 stream.open(path, std::ifstream::in);
912 stream >> version;
913 }
914 return version;
915}
916
919 using namespace std;
920 if (name.empty()) return {};
921
922 auto import_path = PluginHandler::ImportedMetadataPath(name.c_str());
923 if (isRegularFile(import_path.c_str())) {
924 std::ifstream f(import_path.c_str());
925 std::stringstream ss;
926 ss << f.rdbuf();
928 ParsePlugin(ss.str(), pd);
929 pd.is_imported = true;
930 return pd;
931 }
932
934 vector<PluginMetadata> matches;
935 copy_if(available.begin(), available.end(), back_inserter(matches),
936 [name](const PluginMetadata& md) { return md.name == name; });
937 if (matches.size() == 0) return {};
938 if (matches.size() == 1) return matches[0]; // only one found with given name
939
940 auto version = VersionFromManifest(name);
941 auto predicate = [version](const PluginMetadata& md) {
942 return version == md.version;
943 };
944 auto found = find_if(matches.begin(), matches.end(), predicate);
945 return found != matches.end() ? *found : matches[0];
946}
947
950 using namespace std;
951 if (name.empty()) return {};
952
954 vector<PluginMetadata> matches;
955 copy_if(available.begin(), available.end(), back_inserter(matches),
956 [name](const PluginMetadata& md) { return md.name == name; });
957 if (matches.size() == 0) return {};
958 if (matches.size() == 1) return matches[0]; // only one found with given name
959
960 // Check for any later version available in the catalog
961 auto version = SemanticVersion::parse(VersionFromManifest(name));
962 auto rv = matches[0];
963 for (auto p : matches) {
964 auto catVersion = SemanticVersion::parse(p.version);
965 if (catVersion > version) {
966 version = catVersion;
967 rv = p;
968 }
969 }
970
971 return rv;
972}
973
976 const PluginMetadata& md) {
977 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
978 plugin->m_common_name.Lower());
979 bool is_system = found != SYSTEM_PLUGINS.end();
980
981 std::string installed = VersionFromManifest(md.name);
982 plugin->m_manifest_version = installed;
983 auto installedVersion = SemanticVersion::parse(installed);
984 auto metaVersion = SemanticVersion::parse(md.version);
985
986 if (is_system)
987 plugin->m_status = PluginStatus::System;
988 else if (plugin->m_status == PluginStatus::Imported)
989 ; // plugin->m_status = PluginStatus::Imported;
990 else if (installedVersion < metaVersion)
991 plugin->m_status = PluginStatus::ManagedInstalledUpdateAvailable;
992 else if (installedVersion == metaVersion)
993 plugin->m_status = PluginStatus::ManagedInstalledCurrentVersion;
994 else
995 plugin->m_status = PluginStatus::ManagedInstalledDowngradeAvailable;
996
997 if (!is_system && md.is_orphan) plugin->m_status = PluginStatus::Unmanaged;
998
999 plugin->m_managed_metadata = md;
1000}
1001
1002void PluginLoader::UpdateManagedPlugins(bool keep_orphans) {
1003 std::vector<PlugInContainer*> loaded_plugins;
1004 for (auto& p : plugin_array) loaded_plugins.push_back(p);
1005
1006 // Initiate status to "unmanaged" or "system" on all plugins
1007 for (auto& p : loaded_plugins) {
1008 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
1009 p->m_common_name.Lower().ToStdString());
1010 bool is_system = found != SYSTEM_PLUGINS.end();
1011 p->m_status = is_system ? PluginStatus::System : PluginStatus::Unmanaged;
1012 }
1013 if (!keep_orphans) {
1014 // Remove any inactive/uninstalled managed plugins that are no longer
1015 // available in the current catalog Usually due to reverting from
1016 // Alpha/Beta catalog back to master
1017 auto predicate = [](const PlugInContainer* pd) -> bool {
1018 const auto md(
1019 PluginLoader::MetadataByName(pd->m_common_name.ToStdString()));
1020 return md.name.empty() && !md.is_imported && !pd->m_pplugin &&
1021 !IsSystemPluginName(pd->m_common_name.ToStdString());
1022 };
1023 auto end =
1024 std::remove_if(loaded_plugins.begin(), loaded_plugins.end(), predicate);
1025 loaded_plugins.erase(end, loaded_plugins.end());
1026 }
1027
1028 // Update from the catalog metadata
1029 for (auto& plugin : loaded_plugins) {
1030 auto md =
1031 PluginLoader::LatestMetadataByName(plugin->m_common_name.ToStdString());
1032 if (!md.name.empty()) {
1033 auto import_path = PluginHandler::ImportedMetadataPath(md.name.c_str());
1034 md.is_imported = isRegularFile(import_path.c_str());
1035 if (md.is_imported) {
1036 plugin->m_status = PluginStatus::Imported;
1037 } else if (isRegularFile(PluginHandler::FileListPath(md.name).c_str())) {
1038 // This is an installed plugin
1039 PluginLoader::UpdatePlugin(plugin, md);
1040 } else if (IsSystemPluginName(md.name)) {
1041 plugin->m_status = PluginStatus::System;
1042 } else if (md.is_orphan) {
1043 plugin->m_status = PluginStatus::Unmanaged;
1044 } else if (plugin->m_api_version) {
1045 // If the plugin is actually loaded, but the new plugin is known not
1046 // to be installed, then it must be a legacy plugin loaded.
1047 plugin->m_status = PluginStatus::LegacyUpdateAvailable;
1048 plugin->m_managed_metadata = md;
1049 } else {
1050 // Otherwise, this is an uninstalled managed plugin.
1051 plugin->m_status = PluginStatus::ManagedInstallAvailable;
1052 }
1053 }
1054 }
1055
1056 plugin_array.Clear();
1057 for (const auto& p : loaded_plugins) plugin_array.Add(p);
1059}
1060
1062 bool rv = true;
1063 while (plugin_array.GetCount()) {
1064 if (!UnLoadPlugIn(0)) {
1065 rv = false;
1066 }
1067 }
1068 return rv;
1069}
1070
1072 for (auto* pic : plugin_array) {
1073 if (pic && pic->m_enabled && pic->m_init_state) DeactivatePlugIn(pic);
1074 }
1075 return true;
1076}
1077
1078#ifdef __WXMSW__
1079/*Convert Virtual Address to File Offset */
1080DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt) {
1081 size_t i = 0;
1082 PIMAGE_SECTION_HEADER pSeh;
1083 if (rva == 0) {
1084 return (rva);
1085 }
1086 pSeh = psh;
1087 for (i = 0; i < pnt->FileHeader.NumberOfSections; i++) {
1088 if (rva >= pSeh->VirtualAddress &&
1089 rva < pSeh->VirtualAddress + pSeh->Misc.VirtualSize) {
1090 break;
1091 }
1092 pSeh++;
1093 }
1094 return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
1095}
1096#endif
1097
1099public:
1100 ModuleInfo() : type_magic(0) {}
1101 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, DependencySet);
1102 WX_DECLARE_HASH_MAP(wxString, wxString, wxStringHash, wxStringEqual,
1103 DependencyMap);
1104
1105 uint64_t type_magic;
1106 DependencyMap dependencies;
1107};
1108
1109#ifdef USE_LIBELF
1110bool ReadModuleInfoFromELF(const wxString& file,
1111 const ModuleInfo::DependencySet& dependencies,
1112 ModuleInfo& info) {
1113 static bool b_libelf_initialized = false;
1114 static bool b_libelf_usable = false;
1115
1116 if (b_libelf_usable) {
1117 // Nothing to do.
1118 } else if (b_libelf_initialized) {
1119 return false;
1120 } else if (elf_version(EV_CURRENT) == EV_NONE) {
1121 b_libelf_initialized = true;
1122 b_libelf_usable = false;
1123 wxLogError("LibELF is outdated.");
1124 return false;
1125 } else {
1126 b_libelf_initialized = true;
1127 b_libelf_usable = true;
1128 }
1129
1130 int file_handle;
1131 Elf* elf_handle = nullptr;
1132 GElf_Ehdr elf_file_header;
1133 Elf_Scn* elf_section_handle = nullptr;
1134
1135 file_handle = open(file, O_RDONLY);
1136 if (file_handle == -1) {
1137 wxLogMessage("Could not open file \"%s\" for reading: %s", file,
1138 strerror(errno));
1139 goto FailureEpilogue;
1140 }
1141
1142 elf_handle = elf_begin(file_handle, ELF_C_READ, nullptr);
1143 if (elf_handle == nullptr) {
1144 wxLogMessage("Could not get ELF structures from \"%s\".", file);
1145 goto FailureEpilogue;
1146 }
1147
1148 if (gelf_getehdr(elf_handle, &elf_file_header) != &elf_file_header) {
1149 wxLogMessage("Could not get ELF file header from \"%s\".", file);
1150 goto FailureEpilogue;
1151 }
1152
1153 switch (elf_file_header.e_type) {
1154 case ET_EXEC:
1155 case ET_DYN:
1156 break;
1157 default:
1158 wxLogMessage(wxString::Format(
1159 "Module \"%s\" is not an executable or shared library.", file));
1160 goto FailureEpilogue;
1161 }
1162
1163 info.type_magic =
1164 (static_cast<uint64_t>(elf_file_header.e_ident[EI_CLASS])
1165 << 0) | // ELF class (32/64).
1166 (static_cast<uint64_t>(elf_file_header.e_ident[EI_DATA])
1167 << 8) | // Endianness.
1168 (static_cast<uint64_t>(elf_file_header.e_ident[EI_OSABI])
1169 << 16) | // OS ABI (Linux, FreeBSD, etc.).
1170 (static_cast<uint64_t>(elf_file_header.e_ident[EI_ABIVERSION])
1171 << 24) | // OS ABI version.
1172 (static_cast<uint64_t>(elf_file_header.e_machine)
1173 << 32) | // Instruction set.
1174 0;
1175
1176 while ((elf_section_handle = elf_nextscn(elf_handle, elf_section_handle)) !=
1177 nullptr) {
1178 GElf_Shdr elf_section_header;
1179 Elf_Data* elf_section_data = nullptr;
1180 size_t elf_section_entry_count = 0;
1181
1182 if (gelf_getshdr(elf_section_handle, &elf_section_header) !=
1183 &elf_section_header) {
1184 wxLogMessage("Could not get ELF section header from \"%s\".", file);
1185 goto FailureEpilogue;
1186 } else if (elf_section_header.sh_type != SHT_DYNAMIC) {
1187 continue;
1188 }
1189
1190 elf_section_data = elf_getdata(elf_section_handle, nullptr);
1191 if (elf_section_data == nullptr) {
1192 wxLogMessage("Could not get ELF section data from \"%s\".", file);
1193 goto FailureEpilogue;
1194 }
1195
1196 if ((elf_section_data->d_size == 0) ||
1197 (elf_section_header.sh_entsize == 0)) {
1198 wxLogMessage("Got malformed ELF section metadata from \"%s\".", file);
1199 goto FailureEpilogue;
1200 }
1201
1202 elf_section_entry_count =
1203 elf_section_data->d_size / elf_section_header.sh_entsize;
1204 for (size_t elf_section_entry_index = 0;
1205 elf_section_entry_index < elf_section_entry_count;
1206 ++elf_section_entry_index) {
1207 GElf_Dyn elf_dynamic_entry;
1208 const char* elf_dynamic_entry_name = nullptr;
1209 if (gelf_getdyn(elf_section_data,
1210 static_cast<int>(elf_section_entry_index),
1211 &elf_dynamic_entry) != &elf_dynamic_entry) {
1212 wxLogMessage("Could not get ELF dynamic_section entry from \"%s\".",
1213 file);
1214 goto FailureEpilogue;
1215 } else if (elf_dynamic_entry.d_tag != DT_NEEDED) {
1216 continue;
1217 }
1218 elf_dynamic_entry_name = elf_strptr(
1219 elf_handle, elf_section_header.sh_link, elf_dynamic_entry.d_un.d_val);
1220 if (elf_dynamic_entry_name == nullptr) {
1221 wxLogMessage(wxString::Format("Could not get %s %s from \"%s\".", "ELF",
1222 "string entry", file));
1223 goto FailureEpilogue;
1224 }
1225 wxString name_full(elf_dynamic_entry_name);
1226 wxString name_part(elf_dynamic_entry_name,
1227 strcspn(elf_dynamic_entry_name, "-."));
1228 if (dependencies.find(name_part) != dependencies.end()) {
1229 info.dependencies.insert(
1230 ModuleInfo::DependencyMap::value_type(name_part, name_full));
1231 }
1232 }
1233 }
1234
1235 goto SuccessEpilogue;
1236
1237SuccessEpilogue:
1238 elf_end(elf_handle);
1239 close(file_handle);
1240 return true;
1241
1242FailureEpilogue:
1243 if (elf_handle != nullptr) elf_end(elf_handle);
1244 if (file_handle >= 0) close(file_handle);
1245 wxLog::FlushActive();
1246 return false;
1247}
1248#endif // USE_LIBELF
1249
1250bool PluginLoader::CheckPluginCompatibility(const wxString& plugin_file) {
1251 bool b_compat = false;
1252#ifdef __WXOSX__
1253 // TODO: Actually do some tests (In previous versions b_compat was initialized
1254 // to true, so the actual behavior was exactly like this)
1255 b_compat = true;
1256#endif
1257#ifdef __WXMSW__
1258 // For Windows we identify the dll file containing the core wxWidgets
1259 // functions Later we will compare this with the file containing the wxWidgets
1260 // functions used by plugins. If these file names match exactly then we
1261 // assume the plugin is compatible. By using the file names we avoid having to
1262 // hard code the file name into the OpenCPN sources. This makes it easier to
1263 // update wxWigets versions without editing sources. NOTE: this solution may
1264 // not follow symlinks but almost no one uses simlinks for wxWidgets dlls
1265
1266 // Only go through this process once per instance of O.
1267 if (!m_found_wxwidgets) {
1268 DWORD myPid = GetCurrentProcessId();
1269 HANDLE hProcess =
1270 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, myPid);
1271 if (hProcess == NULL) {
1272 wxLogMessage(wxString::Format("Cannot identify running process for %s",
1273 plugin_file.c_str()));
1274 } else {
1275 // Find namme of wxWidgets core DLL used by the current process
1276 // so we can compare it to the one used by the plugin
1277 HMODULE hMods[1024];
1278 DWORD cbNeeded;
1279 if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
1280 for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
1281 TCHAR szModName[MAX_PATH];
1282 if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
1283 sizeof(szModName) / sizeof(TCHAR))) {
1284 m_module_name = szModName;
1285 if (m_module_name.Find("wxmsw") != wxNOT_FOUND) {
1286 if (m_module_name.Find("_core_") != wxNOT_FOUND) {
1287 m_found_wxwidgets = true;
1288 wxLogMessage(wxString::Format("Found wxWidgets core DLL: %s",
1289 m_module_name.c_str()));
1290 break;
1291 }
1292 }
1293 }
1294 }
1295 } else {
1296 wxLogMessage(wxString::Format("Cannot enumerate process modules for %s",
1297 plugin_file.c_str()));
1298 }
1299 if (hProcess) CloseHandle(hProcess);
1300 }
1301 }
1302 if (!m_found_wxwidgets) {
1303 wxLogMessage(wxString::Format("Cannot identify wxWidgets core DLL for %s",
1304 plugin_file.c_str()));
1305 } else {
1306 LPCWSTR fName = plugin_file.wc_str();
1307 HANDLE handle = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING,
1308 FILE_ATTRIBUTE_NORMAL, 0);
1309 DWORD byteread, size = GetFileSize(handle, NULL);
1310 PVOID virtualpointer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
1311 bool status = ReadFile(handle, virtualpointer, size, &byteread, NULL);
1312 CloseHandle(handle);
1313 PIMAGE_NT_HEADERS ntheaders =
1314 (PIMAGE_NT_HEADERS)(PCHAR(virtualpointer) +
1315 PIMAGE_DOS_HEADER(virtualpointer)->e_lfanew);
1316 PIMAGE_SECTION_HEADER pSech =
1317 IMAGE_FIRST_SECTION(ntheaders); // Pointer to first section header
1318 PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // Pointer to import descriptor
1319 if (ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
1320 .Size !=
1321 0) /*if size of the table is 0 - Import Table does not exist */
1322 {
1323 pImportDescriptor =
1324 (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)virtualpointer +
1325 Rva2Offset(
1326 ntheaders->OptionalHeader
1327 .DataDirectory
1328 [IMAGE_DIRECTORY_ENTRY_IMPORT]
1329 .VirtualAddress,
1330 pSech, ntheaders));
1331 LPSTR libname[256];
1332 size_t i = 0;
1333 // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR or we find core
1334 // wxWidgets DLL
1335 while (pImportDescriptor->Name != 0) {
1336 // Get the name of each DLL
1337 libname[i] =
1338 (PCHAR)((DWORD_PTR)virtualpointer +
1339 Rva2Offset(pImportDescriptor->Name, pSech, ntheaders));
1340 // Check if the plugin DLL dependencey is same as main process wxWidgets
1341 // core DLL
1342 if (m_module_name.Find(libname[i]) != wxNOT_FOUND) {
1343 // Match found - plugin is compatible
1344 b_compat = true;
1345 wxLogMessage(wxString::Format(
1346 "Compatible wxWidgets plugin library found for %s: %s",
1347 plugin_file.c_str(), libname[i]));
1348 break;
1349 }
1350 pImportDescriptor++; // advance to next IMAGE_IMPORT_DESCRIPTOR
1351 i++;
1352 }
1353 } else {
1354 wxLogMessage(
1355 wxString::Format("No Import Table! in %s", plugin_file.c_str()));
1356 }
1357 if (virtualpointer) VirtualFree(virtualpointer, size, MEM_DECOMMIT);
1358 }
1359#endif
1360#if defined(__WXGTK__) || defined(__WXQT__)
1361#if defined(USE_LIBELF)
1362
1363 static bool b_own_info_queried = false;
1364 static bool b_own_info_usable = false;
1365 static ModuleInfo own_info;
1366 static ModuleInfo::DependencySet dependencies;
1367
1368 if (!b_own_info_queried) {
1369 dependencies.insert("libwx_baseu");
1370
1371 char exe_buf[100] = {0};
1372 ssize_t len = readlink("/proc/self/exe", exe_buf, 99);
1373 if (len > 0) {
1374 exe_buf[len] = '\0';
1375 wxString app_path(exe_buf);
1376 wxLogMessage("Executable path: %s", exe_buf);
1377 b_own_info_usable =
1378 ReadModuleInfoFromELF(app_path, dependencies, own_info);
1379 if (!b_own_info_usable) {
1380 wxLogMessage("Cannot get own info from: %s", exe_buf);
1381 }
1382 } else {
1383 wxLogMessage("Cannot get own executable path.");
1384 }
1385 b_own_info_queried = true;
1386 }
1387
1388 if (b_own_info_usable) {
1389 bool b_pi_info_usable = false;
1390 ModuleInfo pi_info;
1391 b_pi_info_usable =
1392 ReadModuleInfoFromELF(plugin_file, dependencies, pi_info);
1393 if (b_pi_info_usable) {
1394 b_compat = (pi_info.type_magic == own_info.type_magic);
1395
1396 // OSABI field on flatpak builds
1397 if ((pi_info.type_magic ^ own_info.type_magic) == 0x00030000) {
1398 b_compat = true;
1399 }
1400
1401 if (!b_compat) {
1402 pi_info.dependencies.clear();
1403 wxLogMessage(
1404 wxString::Format(" Plugin \"%s\" is of another binary "
1405 "flavor than the main module.",
1406 plugin_file));
1407 wxLogMessage("host magic: %.8x, plugin magic: %.8x",
1408 own_info.type_magic, pi_info.type_magic);
1409 }
1410 for (const auto& own_dependency : own_info.dependencies) {
1411 ModuleInfo::DependencyMap::const_iterator pi_dependency =
1412 pi_info.dependencies.find(own_dependency.first);
1413 if ((pi_dependency != pi_info.dependencies.end()) &&
1414 (pi_dependency->second != own_dependency.second)) {
1415 b_compat = false;
1416 wxLogMessage(
1417 " Plugin \"%s\" depends on library \"%s\", but the main "
1418 "module was built for \"%s\".",
1419 plugin_file, pi_dependency->second, own_dependency.second);
1420 break;
1421 }
1422 }
1423 } else {
1424 b_compat = false;
1425 wxLogMessage(
1426 wxString::Format(" Plugin \"%s\" could not be reliably "
1427 "checked for compatibility.",
1428 plugin_file));
1429 }
1430 } else {
1431 // Allow any plugin when own info is not available.
1432 b_compat = true;
1433 }
1434
1435 wxLogMessage("Plugin is compatible by elf library scan: %s",
1436 b_compat ? "true" : "false");
1437
1438 wxLog::FlushActive();
1439 return b_compat;
1440
1441#endif // LIBELF
1442
1443 // But Android Plugins do not include the wxlib specification in their ELF
1444 // file. So we assume Android Plugins are compatible....
1445#ifdef __ANDROID__
1446 return true;
1447#endif
1448
1449 // If libelf is not available, then we must use a simplistic file scan method.
1450 // This is easily fooled if the wxWidgets version in use is not exactly
1451 // recognized. File scan is 3x faster than the ELF scan method
1452
1453 FILE* f = fopen(plugin_file, "r");
1454 char strver[26]; // Enough space even for very big integers...
1455 if (f == NULL) {
1456 wxLogMessage("Plugin %s can't be opened", plugin_file);
1457 return false;
1458 }
1459 sprintf(strver,
1460#if defined(__WXGTK3__)
1461 "libwx_gtk3u_core-%i.%i"
1462#elif defined(__WXGTK20__)
1463 "libwx_gtk2u_core-%i.%i"
1464#elif defined(__WXQT__)
1465 "libwx_qtu_core-%i.%i"
1466#else
1467#error undefined plugin platform
1468#endif
1469 ,
1470 wxMAJOR_VERSION, wxMINOR_VERSION);
1471 b_compat = false;
1472
1473 size_t pos(0);
1474 size_t len(strlen(strver));
1475 int c;
1476 while ((c = fgetc(f)) != EOF) {
1477 if (c == strver[pos]) {
1478 if (++pos == len) {
1479 b_compat = true;
1480 break;
1481 }
1482 } else
1483 pos = 0;
1484 }
1485 fclose(f);
1486#endif // __WXGTK__ or __WXQT__
1487
1488 wxLogMessage("Plugin is compatible: %s", b_compat ? "true" : "false");
1489 return b_compat;
1490}
1491
1492PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file) {
1493 auto pic = new PlugInContainer;
1494 if (!LoadPlugIn(plugin_file, pic)) {
1495 delete pic;
1496 return nullptr;
1497 } else {
1498 return pic;
1499 }
1500}
1501
1502PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file,
1503 PlugInContainer* pic) {
1504 wxLogMessage(wxString("PluginLoader: Loading PlugIn: ") + plugin_file);
1505
1506 if (plugin_file.empty()) {
1507 wxLogMessage("Ignoring loading of empty path");
1508 return nullptr;
1509 }
1510
1511 if (!wxIsReadable(plugin_file)) {
1512 wxLogMessage("Ignoring unreadable plugin %s",
1513 plugin_file.ToStdString().c_str());
1514 LoadError le(LoadError::Type::Unreadable, plugin_file.ToStdString());
1515 load_errors.push_back(le);
1516 return nullptr;
1517 }
1518
1519 // Check if blacklisted, exit if so.
1520 auto sts =
1521 m_blacklist->get_status(pic->m_common_name.ToStdString(),
1522 pic->m_version_major, pic->m_version_minor);
1523 if (sts != plug_status::unblocked) {
1524 wxLogDebug("Refusing to load blacklisted plugin: %s",
1525 pic->m_common_name.ToStdString().c_str());
1526 return nullptr;
1527 }
1528 auto data = m_blacklist->get_library_data(plugin_file.ToStdString());
1529 if (!data.name.empty()) {
1530 wxLogDebug("Refusing to load blacklisted library: %s",
1531 plugin_file.ToStdString().c_str());
1532 return nullptr;
1533 }
1534 pic->m_plugin_file = plugin_file;
1535 pic->m_status =
1536 PluginStatus::Unmanaged; // Status is updated later, if necessary
1537
1538 // load the library
1539 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
1540 pic->m_library.Load(plugin_file);
1541
1542 if (!pic->m_library.IsLoaded()) {
1543 // Look in the Blacklist, try to match a filename, to give some kind of
1544 // message extract the probable plugin name
1545 wxFileName fn(plugin_file);
1546 std::string name = fn.GetName().ToStdString();
1547 auto found = m_blacklist->get_library_data(name);
1548 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1549 wxLogMessage("Ignoring blacklisted plugin %s", name.c_str());
1550 if (!found.name.empty()) {
1551 SemanticVersion v(found.major, found.minor);
1552 LoadError le(LoadError::Type::Unloadable, name, v);
1553 load_errors.push_back(le);
1554 } else {
1555 LoadError le(LoadError::Type::Unloadable, plugin_file.ToStdString());
1556 load_errors.push_back(le);
1557 }
1558 }
1559 wxLogMessage(wxString(" PluginLoader: Cannot load library: ") +
1560 plugin_file);
1561 return nullptr;
1562 }
1563
1564 // load the factory symbols
1565 const char* const FIX_LOADING =
1566 _("\n Install/uninstall plugin or remove file to mute message");
1567 create_t* create_plugin = (create_t*)pic->m_library.GetSymbol("create_pi");
1568 if (nullptr == create_plugin) {
1569 std::string msg(_(" PluginLoader: Cannot load symbol create_pi: "));
1570 wxLogMessage(msg + plugin_file);
1571 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1572 LoadError le(LoadError::Type::NoCreate, plugin_file.ToStdString());
1573 load_errors.push_back(le);
1574 }
1575 return nullptr;
1576 }
1577
1578 destroy_t* destroy_plugin =
1579 (destroy_t*)pic->m_library.GetSymbol("destroy_pi");
1580 pic->m_destroy_fn = destroy_plugin;
1581 if (nullptr == destroy_plugin) {
1582 wxLogMessage(" PluginLoader: Cannot load symbol destroy_pi: " +
1583 plugin_file);
1584 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1585 LoadError le(LoadError::Type::NoDestroy, plugin_file.ToStdString());
1586 load_errors.push_back(le);
1587 }
1588 return nullptr;
1589 }
1590
1591 // create an instance of the plugin class
1592 opencpn_plugin* plug_in = create_plugin(this);
1593
1594 int api_major = plug_in->GetAPIVersionMajor();
1595 int api_minor = plug_in->GetAPIVersionMinor();
1596 int api_ver = (api_major * 100) + api_minor;
1597 pic->m_api_version = api_ver;
1598
1599 int pi_major = plug_in->GetPlugInVersionMajor();
1600 int pi_minor = plug_in->GetPlugInVersionMinor();
1601 SemanticVersion pi_ver(pi_major, pi_minor, -1);
1602
1603 wxString pi_name = plug_in->GetCommonName();
1604
1605 wxLogDebug("blacklist: Get status for %s %d %d",
1606 pi_name.ToStdString().c_str(), pi_major, pi_minor);
1607 const auto status =
1608 m_blacklist->get_status(pi_name.ToStdString(), pi_major, pi_minor);
1609 if (status != plug_status::unblocked) {
1610 wxLogDebug("Ignoring blacklisted plugin.");
1611 if (status != plug_status::unloadable) {
1612 SemanticVersion v(pi_major, pi_minor);
1613 LoadError le(LoadError::Type::Blacklisted, pi_name.ToStdString(), v);
1614 load_errors.push_back(le);
1615 }
1616 return nullptr;
1617 }
1618
1619 switch (api_ver) {
1620 case 105:
1621 pic->m_pplugin = dynamic_cast<opencpn_plugin*>(plug_in);
1622 break;
1623
1624 case 106:
1625 pic->m_pplugin = dynamic_cast<opencpn_plugin_16*>(plug_in);
1626 break;
1627
1628 case 107:
1629 pic->m_pplugin = dynamic_cast<opencpn_plugin_17*>(plug_in);
1630 break;
1631
1632 case 108:
1633 pic->m_pplugin = dynamic_cast<opencpn_plugin_18*>(plug_in);
1634 break;
1635
1636 case 109:
1637 pic->m_pplugin = dynamic_cast<opencpn_plugin_19*>(plug_in);
1638 break;
1639
1640 case 110:
1641 pic->m_pplugin = dynamic_cast<opencpn_plugin_110*>(plug_in);
1642 break;
1643
1644 case 111:
1645 pic->m_pplugin = dynamic_cast<opencpn_plugin_111*>(plug_in);
1646 break;
1647
1648 case 112:
1649 pic->m_pplugin = dynamic_cast<opencpn_plugin_112*>(plug_in);
1650 break;
1651
1652 case 113:
1653 pic->m_pplugin = dynamic_cast<opencpn_plugin_113*>(plug_in);
1654 break;
1655
1656 case 114:
1657 pic->m_pplugin = dynamic_cast<opencpn_plugin_114*>(plug_in);
1658 break;
1659
1660 case 115:
1661 pic->m_pplugin = dynamic_cast<opencpn_plugin_115*>(plug_in);
1662 break;
1663
1664 case 116:
1665 pic->m_pplugin = dynamic_cast<opencpn_plugin_116*>(plug_in);
1666 break;
1667
1668 case 117:
1669 pic->m_pplugin = dynamic_cast<opencpn_plugin_117*>(plug_in);
1670 break;
1671
1672 case 118:
1673 pic->m_pplugin = dynamic_cast<opencpn_plugin_118*>(plug_in);
1674 break;
1675
1676 case 119:
1677 pic->m_pplugin = dynamic_cast<opencpn_plugin_119*>(plug_in);
1678 break;
1679
1680 case 120:
1681 pic->m_pplugin = dynamic_cast<opencpn_plugin_120*>(plug_in);
1682 break;
1683
1684 case 121:
1685 pic->m_pplugin = dynamic_cast<opencpn_plugin_121*>(plug_in);
1686 break;
1687
1688 default:
1689 break;
1690 }
1691
1692 if (auto p = dynamic_cast<opencpn_plugin_117*>(plug_in)) {
1693 // For API 1.17+ use the version info in the plugin API in favor of
1694 // the version file created when installing plugin.
1695 pi_ver =
1696 SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1697 p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1698 p->GetPlugInVersionBuild());
1699 }
1700
1701 if (!pic->m_pplugin) {
1702 INFO_LOG << _("Incompatible plugin detected: ") << plugin_file << "\n";
1703 INFO_LOG << _(" API Version detected: ");
1704 INFO_LOG << api_major << "." << api_minor << "\n";
1705 INFO_LOG << _(" PlugIn Version detected: ") << pi_ver << "\n";
1706 if (m_blacklist->mark_unloadable(pi_name.ToStdString(), pi_ver.major,
1707 pi_ver.minor)) {
1708 LoadError le(LoadError::Type::Incompatible, pi_name.ToStdString(),
1709 pi_ver);
1710 load_errors.push_back(le);
1711 }
1712 return nullptr;
1713 }
1714 return pic;
1715}
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.