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