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 "observable/configvar.h"
70
71#include "model/base_platform.h"
74#include "model/config_vars.h"
75#include "model/cmdline.h"
76#include "model/config_vars.h"
77#include "model/logger.h"
78#include "model/ocpn_utils.h"
80#include "model/plugin_cache.h"
82#include "model/plugin_loader.h"
83#include "model/plugin_paths.h"
84#include "model/safe_mode.h"
85#include "model/semantic_vers.h"
86
87#include "std_filesystem.h"
88
89#ifdef __ANDROID__
90#include "androidUTIL.h"
91#endif
92
93static const std::vector<std::string> SYSTEM_PLUGINS = {
94 "chartdownloader", "wmm", "dashboard", "grib", "demo"};
95
97static PlugInContainer* GetContainer(const PlugInData& pd,
98 const ArrayOfPlugIns& plugin_array) {
99 for (const auto& p : plugin_array) {
100 if (p->m_common_name == pd.m_common_name) return p;
101 }
102 return nullptr;
103}
104
106static bool IsSystemPluginPath(const std::string& path) {
107 static const std::vector<std::string> kPlugins = {
108 "chartdldr_pi", "wmm_pi", "dashboard_pi", "grib_pi", "demo_pi"};
109
110 const std::string lc_path = ocpn::tolower(path);
111 for (const auto& p : kPlugins)
112 if (lc_path.find(p) != std::string::npos) return true;
113 return false;
114}
115
117static bool IsSystemPluginName(const std::string& name) {
118 static const std::vector<std::string> kPlugins = {
119 "chartdownloader", "wmm", "dashboard", "grib", "demo"};
120 auto found = std::find(kPlugins.begin(), kPlugins.end(), ocpn::tolower(name));
121 return found != kPlugins.end();
122}
123
125static std::string GetInstalledVersion(const PlugInData& pd) {
126 std::string path = PluginHandler::VersionPath(pd.m_common_name.ToStdString());
127 if (path == "" || !wxFileName::IsFileReadable(path)) {
128 auto loader = PluginLoader::GetInstance();
129 auto pic = GetContainer(pd, *loader->GetPlugInArray());
130 if (!pic || !pic->m_pplugin) {
131 return SemanticVersion(0, 0, -1).to_string();
132 }
133 int v_major = pic->m_pplugin->GetPlugInVersionMajor();
134 int v_minor = pic->m_pplugin->GetPlugInVersionMinor();
135 return SemanticVersion(v_major, v_minor, -1).to_string();
136 }
137 std::ifstream stream;
138 std::string version;
139 stream.open(path, std::ifstream::in);
140 stream >> version;
141 return version;
142}
143
145static PluginMetadata CreateMetadata(const PlugInContainer* pic) {
146 auto catalogHdlr = CatalogHandler::GetInstance();
147
148 PluginMetadata mdata;
149 mdata.name = pic->m_common_name.ToStdString();
150 SemanticVersion orphanVersion(pic->m_version_major, pic->m_version_minor);
151 mdata.version = orphanVersion.to_string();
152 mdata.summary = pic->m_short_description;
153 mdata.description = pic->m_long_description;
154
155 mdata.target = "all"; // Force IsCompatible() true
156 mdata.is_orphan = true;
157
158 return mdata;
159}
160
162static fs::path LoadStampPath(const std::string& file_path) {
163 fs::path path(g_BasePlatform->DefaultPrivateDataDir().ToStdString());
164 path = path / "load_stamps";
165 if (!ocpn::exists(path.string())) {
166 ocpn::mkdir(path.string());
167 }
168 path /= file_path;
169 return path.parent_path() / path.stem();
170}
171
173static void CreateLoadStamp(const std::string& filename) {
174 std::ofstream(LoadStampPath(filename).string());
175}
176
184static bool HasLoadStamp(const std::string& filename) {
185 return exists(LoadStampPath(filename));
186}
187
192static void ClearLoadStamp(const std::string& filename) {
193 if (filename.empty()) return;
194 auto path = LoadStampPath(filename);
195 if (exists(path)) {
196 if (!remove(path)) {
197 MESSAGE_LOG << " Cannot remove load stamp file: " << path;
198 }
199 }
200}
201
202void PluginLoader::MarkAsLoadable(const std::string& library_path) {
203 ClearLoadStamp(library_path);
204}
205
207 const PlugInData pd,
208 std::function<const PluginMetadata(const std::string&)> get_metadata) {
209 auto loader = PluginLoader::GetInstance();
210 auto pic = GetContainer(pd, *loader->GetPlugInArray());
211 if (!pic) {
212 return SemanticVersion(0, 0, -1).to_string();
213 }
214
215 PluginMetadata metadata;
216 metadata = pic->m_managed_metadata;
217 if (metadata.version == "")
218 metadata = get_metadata(pic->m_common_name.ToStdString());
219 std::string detail_suffix(metadata.is_imported ? _(" [Imported]") : "");
220 if (metadata.is_orphan) detail_suffix = _(" [Orphan]");
221
222 int v_major(0);
223 int v_minor(0);
224 if (pic->m_pplugin) {
225 v_major = pic->m_pplugin->GetPlugInVersionMajor();
226 v_minor = pic->m_pplugin->GetPlugInVersionMinor();
227 }
228 auto p = dynamic_cast<opencpn_plugin_117*>(pic->m_pplugin);
229 if (p) {
230 // New style plugin, trust version available in the API.
231 auto sv = SemanticVersion(
232 v_major, v_minor, p->GetPlugInVersionPatch(), p->GetPlugInVersionPost(),
233 p->GetPlugInVersionPre(), p->GetPlugInVersionBuild());
234 return sv.to_string() + detail_suffix;
235 } else {
236 if (!metadata.is_orphan) {
237 std::string version = GetInstalledVersion(pd);
238 return version + detail_suffix;
239 } else
240 return metadata.version + detail_suffix;
241 }
242}
243
244PlugInContainer::PlugInContainer()
245 : PlugInData(), m_pplugin(nullptr), m_library(), m_destroy_fn(nullptr) {}
246
248 : m_has_setup_options(false),
249 m_enabled(false),
250 m_init_state(false),
251 m_toolbox_panel(false),
252 m_cap_flag(0),
253 m_api_version(0),
254 m_version_major(0),
255 m_version_minor(0),
256 m_status(PluginStatus::Unknown) {}
257
259 m_common_name = wxString(md.name);
260 auto v = SemanticVersion::parse(md.version);
261 m_version_major = v.major;
262 m_version_minor = v.minor;
263 m_managed_metadata = md;
264 m_status = PluginStatus::ManagedInstallAvailable;
265 m_enabled = false;
266}
267
268std::string PlugInData::Key() const {
269 return std::string(m_status == PluginStatus::Managed ? "1" : "0") +
270 m_common_name.ToStdString();
271}
272
273//-----------------------------------------------------------------------------------------------------
274//
275// Plugin Loader Implementation
276//
277//-----------------------------------------------------------------------------------------------------
278
288static void setLoadPath() {
289 using namespace std;
290
291 auto const osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
292 auto dirs = PluginPaths::GetInstance()->Libdirs();
293 if (osSystemId & wxOS_UNIX_LINUX) {
294 string path = ocpn::join(dirs, ':');
295 wxString envPath;
296 if (wxGetEnv("LD_LIBRARY_PATH", &envPath)) {
297 path = path + ":" + envPath.ToStdString();
298 }
299 wxLogMessage("Using LD_LIBRARY_PATH: %s", path.c_str());
300 wxSetEnv("LD_LIBRARY_PATH", path.c_str());
301 } else if (osSystemId & wxOS_WINDOWS) {
302 // On windows, Libdirs() and Bindirs() are the same.
303 string path = ocpn::join(dirs, ';');
304 wxString envPath;
305 if (wxGetEnv("PATH", &envPath)) {
306 path = path + ";" + envPath.ToStdString();
307 }
308 wxLogMessage("Using PATH: %s", path);
309 wxSetEnv("PATH", path);
310 } else if (osSystemId & wxOS_MAC) {
311 string path = ocpn::join(dirs, ':');
312 wxString envPath;
313 if (wxGetEnv("DYLD_LIBRARY_PATH", &envPath)) {
314 path = path + ":" + envPath.ToStdString();
315 }
316 wxLogMessage("Using DYLD_LIBRARY_PATH: %s", path.c_str());
317 wxSetEnv("DYLD_LIBRARY_PATH", path.c_str());
318 } else {
319 wxString os_name = wxPlatformInfo::Get().GetPortIdName();
320 if (os_name.Contains("wxQT")) {
321 wxLogMessage("setLoadPath() using Android library path");
322 } else
323 wxLogWarning("SetLoadPath: Unsupported platform.");
324 }
325 if (osSystemId & wxOS_MAC || osSystemId & wxOS_UNIX_LINUX) {
327 string path = ocpn::join(dirs, ':');
328 wxString envPath;
329 wxGetEnv("PATH", &envPath);
330 path = path + ":" + envPath.ToStdString();
331 wxLogMessage("Using PATH: %s", path);
332 wxSetEnv("PATH", path);
333 }
334}
335
336static void ProcessLateInit(PlugInContainer* pic) {
337 if (pic->m_cap_flag & WANTS_LATE_INIT) {
338 wxString msg("PluginLoader: Calling LateInit PlugIn: ");
339 msg += pic->m_plugin_file;
340 wxLogMessage(msg);
341
342 auto ppi = dynamic_cast<opencpn_plugin_110*>(pic->m_pplugin);
343 if (ppi) ppi->LateInit();
344 }
345}
346
347PluginLoader* PluginLoader::GetInstance() {
348 static PluginLoader* instance = nullptr;
349
350 if (!instance) instance = new PluginLoader();
351 return instance;
352}
353
354PluginLoader::PluginLoader()
355 : m_blacklist(blacklist_factory()),
356 m_default_plugin_icon(nullptr),
357#ifdef __WXMSW__
358 m_found_wxwidgets(false),
359#endif
360 m_on_deactivate_cb([](const PlugInContainer* pic) {}) {
361}
362
363bool PluginLoader::IsPlugInAvailable(const wxString& commonName) {
364 for (auto* pic : plugin_array) {
365 if (pic && pic->m_enabled && (pic->m_common_name == commonName))
366 return true;
367 }
368 return false;
369}
370
372 wxWindow* parent) {
373 auto loader = PluginLoader::GetInstance();
374 auto pic = GetContainer(pd, *loader->GetPlugInArray());
375 if (pic) pic->m_pplugin->ShowPreferencesDialog(parent);
376}
377
378void PluginLoader::NotifySetupOptionsPlugin(const PlugInData* pd) {
379 auto pic = GetContainer(*pd, *GetPlugInArray());
380 if (!pic) return;
381 if (pic->m_has_setup_options) return;
382 pic->m_has_setup_options = true;
383 if (pic->m_enabled && pic->m_init_state) {
385 switch (pic->m_api_version) {
386 case 109:
387 case 110:
388 case 111:
389 case 112:
390 case 113:
391 case 114:
392 case 115:
393 case 116:
394 case 117:
395 case 118:
396 case 119:
397 case 120:
398 case 121: {
399 if (pic->m_pplugin) {
400 auto ppi = dynamic_cast<opencpn_plugin_19*>(pic->m_pplugin);
401 if (ppi) {
402 ppi->OnSetupOptions();
403 auto loader = PluginLoader::GetInstance();
404 loader->SetToolboxPanel(pic->m_common_name, true);
405 }
406 break;
407 }
408 }
409 default:
410 break;
411 }
412 }
413 }
414}
415
416void PluginLoader::SetEnabled(const wxString& common_name, bool enabled) {
417 for (auto* pic : plugin_array) {
418 if (pic->m_common_name == common_name) {
419 pic->m_enabled = enabled;
420 return;
421 }
422 }
423}
424
425void PluginLoader::SetToolboxPanel(const wxString& common_name, bool value) {
426 for (auto* pic : plugin_array) {
427 if (pic->m_common_name == common_name) {
428 pic->m_toolbox_panel = value;
429 return;
430 }
431 }
432 wxLogMessage("Atttempt to update toolbox panel on non-existing plugin " +
433 common_name);
434}
435
436void PluginLoader::SetSetupOptions(const wxString& common_name, bool value) {
437 for (auto* pic : plugin_array) {
438 if (pic->m_common_name == common_name) {
439 pic->m_has_setup_options = value;
440 return;
441 }
442 }
443 wxLogMessage("Atttempt to update setup options on non-existing plugin " +
444 common_name);
445}
446
447const wxBitmap* PluginLoader::GetPluginDefaultIcon() {
448 if (!m_default_plugin_icon) m_default_plugin_icon = new wxBitmap(32, 32);
449 return m_default_plugin_icon;
450}
451
452void PluginLoader::SetPluginDefaultIcon(const wxBitmap* bitmap) {
453 delete m_default_plugin_icon;
454 m_default_plugin_icon = bitmap;
455}
456
458 auto pic = GetContainer(pd, plugin_array);
459 if (!pic) {
460 wxLogMessage("Attempt to remove non-existing plugin %s",
461 pd.m_common_name.ToStdString().c_str());
462 return;
463 }
464 plugin_array.Remove(pic);
465}
466
467static int ComparePlugins(PlugInContainer** p1, PlugInContainer** p2) {
468 return (*p1)->Key().compare((*p2)->Key());
469}
470
472 PlugInContainer**)) {
473 plugin_array.Sort(ComparePlugins);
474}
475
476bool PluginLoader::LoadAllPlugIns(bool load_enabled, bool keep_orphans) {
477 using namespace std;
478
479 static const wxString sep = wxFileName::GetPathSeparator();
480 vector<string> dirs = PluginPaths::GetInstance()->Libdirs();
481 wxLogMessage("PluginLoader: loading plugins from %s", ocpn::join(dirs, ';'));
482 setLoadPath();
483 bool any_dir_loaded = false;
484 for (const auto& dir : dirs) {
485 wxString wxdir(dir);
486 wxLogMessage("Loading plugins from dir: %s", wxdir.mb_str().data());
487 if (LoadPlugInDirectory(wxdir, load_enabled)) any_dir_loaded = true;
488 }
489
490 // Read the default ocpn-plugins.xml, and update/merge the plugin array
491 // This only needs to happen when the entire universe (enabled and disabled)
492 // of plugins are loaded for management.
493 if (!load_enabled) UpdateManagedPlugins(keep_orphans);
494
495 // Some additional actions needed after all plugins are loaded.
496 evt_update_chart_types.Notify();
497 auto errors = std::make_shared<std::vector<LoadError>>(load_errors);
498 evt_plugin_loadall_finalize.Notify(errors, "");
499 load_errors.clear();
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::GetMode()) {
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 obs::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::GetMode() &&
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 }
651 evt_load_plugin.Notify(pic);
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) {
728 evt_load_directory.Notify();
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 }
870 evt_update_chart_types.Notify();
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 auto name = pic->m_pplugin->GetCommonName().ToStdString();
883 evt_deactivate_plugin.Notify(name);
884 }
885 return true;
886}
887
889 auto pic = GetContainer(pd, plugin_array);
890 if (!pic) {
891 wxLogError("Attempt to deactivate non-existing plugin %s",
892 pd.m_common_name.ToStdString());
893 return false;
894 }
895 return DeactivatePlugIn(pic);
896}
897
899 if (ix >= plugin_array.GetCount()) {
900 wxLogWarning("Attempt to remove non-existing plugin %d", ix);
901 return false;
902 }
903 PlugInContainer* pic = plugin_array[ix];
904 if (!DeactivatePlugIn(pic)) {
905 return false;
906 }
907 if (pic->m_pplugin) {
908 pic->m_destroy_fn(pic->m_pplugin);
909 }
910
911 delete pic; // This will unload the PlugIn via DTOR of pic->m_library
912 plugin_array.RemoveAt(ix);
913 return true;
914}
915
916static std::string VersionFromManifest(const std::string& plugin_name) {
917 std::string version;
918 std::string path = PluginHandler::VersionPath(plugin_name);
919 if (!path.empty() && wxFileName::IsFileReadable(path)) {
920 std::ifstream stream;
921 stream.open(path, std::ifstream::in);
922 stream >> version;
923 }
924 return version;
925}
926
929 using namespace std;
930 if (name.empty()) return {};
931
932 auto import_path = PluginHandler::ImportedMetadataPath(name.c_str());
933 if (isRegularFile(import_path.c_str())) {
934 std::ifstream f(import_path.c_str());
935 std::stringstream ss;
936 ss << f.rdbuf();
938 ParsePlugin(ss.str(), pd);
939 pd.is_imported = true;
940 return pd;
941 }
942
944 vector<PluginMetadata> matches;
945 copy_if(available.begin(), available.end(), back_inserter(matches),
946 [name](const PluginMetadata& md) { return md.name == name; });
947 if (matches.size() == 0) return {};
948 if (matches.size() == 1) return matches[0]; // only one found with given name
949
950 auto version = VersionFromManifest(name);
951 auto predicate = [version](const PluginMetadata& md) {
952 return version == md.version;
953 };
954 auto found = find_if(matches.begin(), matches.end(), predicate);
955 return found != matches.end() ? *found : matches[0];
956}
957
960 using namespace std;
961 if (name.empty()) return {};
962
964 vector<PluginMetadata> matches;
965 copy_if(available.begin(), available.end(), back_inserter(matches),
966 [name](const PluginMetadata& md) { return md.name == name; });
967 if (matches.size() == 0) return {};
968 if (matches.size() == 1) return matches[0]; // only one found with given name
969
970 // Check for any later version available in the catalog
971 auto version = SemanticVersion::parse(VersionFromManifest(name));
972 auto rv = matches[0];
973 for (auto p : matches) {
974 auto catVersion = SemanticVersion::parse(p.version);
975 if (catVersion > version) {
976 version = catVersion;
977 rv = p;
978 }
979 }
980
981 return rv;
982}
983
986 const PluginMetadata& md) {
987 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
988 plugin->m_common_name.Lower());
989 bool is_system = found != SYSTEM_PLUGINS.end();
990
991 std::string installed = VersionFromManifest(md.name);
992 plugin->m_manifest_version = installed;
993 auto installedVersion = SemanticVersion::parse(installed);
994 auto metaVersion = SemanticVersion::parse(md.version);
995
996 if (is_system)
997 plugin->m_status = PluginStatus::System;
998 else if (plugin->m_status == PluginStatus::Imported)
999 ; // plugin->m_status = PluginStatus::Imported;
1000 else if (installedVersion < metaVersion)
1001 plugin->m_status = PluginStatus::ManagedInstalledUpdateAvailable;
1002 else if (installedVersion == metaVersion)
1003 plugin->m_status = PluginStatus::ManagedInstalledCurrentVersion;
1004 else
1005 plugin->m_status = PluginStatus::ManagedInstalledDowngradeAvailable;
1006
1007 if (!is_system && md.is_orphan) plugin->m_status = PluginStatus::Unmanaged;
1008
1009 plugin->m_managed_metadata = md;
1010}
1011
1012void PluginLoader::UpdateManagedPlugins(bool keep_orphans) {
1013 std::vector<PlugInContainer*> loaded_plugins;
1014 for (auto& p : plugin_array) loaded_plugins.push_back(p);
1015
1016 // Initiate status to "unmanaged" or "system" on all plugins
1017 for (auto& p : loaded_plugins) {
1018 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
1019 p->m_common_name.Lower().ToStdString());
1020 bool is_system = found != SYSTEM_PLUGINS.end();
1021 p->m_status = is_system ? PluginStatus::System : PluginStatus::Unmanaged;
1022 }
1023 if (!keep_orphans) {
1024 // Remove any inactive/uninstalled managed plugins that are no longer
1025 // available in the current catalog Usually due to reverting from
1026 // Alpha/Beta catalog back to master
1027 auto predicate = [](const PlugInContainer* pd) -> bool {
1028 const auto md(
1029 PluginLoader::MetadataByName(pd->m_common_name.ToStdString()));
1030 return md.name.empty() && !md.is_imported && !pd->m_pplugin &&
1031 !IsSystemPluginName(pd->m_common_name.ToStdString());
1032 };
1033 auto end =
1034 std::remove_if(loaded_plugins.begin(), loaded_plugins.end(), predicate);
1035 loaded_plugins.erase(end, loaded_plugins.end());
1036 }
1037
1038 // Update from the catalog metadata
1039 for (auto& plugin : loaded_plugins) {
1040 auto md =
1041 PluginLoader::LatestMetadataByName(plugin->m_common_name.ToStdString());
1042 if (!md.name.empty()) {
1043 auto import_path = PluginHandler::ImportedMetadataPath(md.name.c_str());
1044 md.is_imported = isRegularFile(import_path.c_str());
1045 if (md.is_imported) {
1046 plugin->m_status = PluginStatus::Imported;
1047 } else if (isRegularFile(PluginHandler::FileListPath(md.name).c_str())) {
1048 // This is an installed plugin
1049 PluginLoader::UpdatePlugin(plugin, md);
1050 } else if (IsSystemPluginName(md.name)) {
1051 plugin->m_status = PluginStatus::System;
1052 } else if (md.is_orphan) {
1053 plugin->m_status = PluginStatus::Unmanaged;
1054 } else if (plugin->m_api_version) {
1055 // If the plugin is actually loaded, but the new plugin is known not
1056 // to be installed, then it must be a legacy plugin loaded.
1057 plugin->m_status = PluginStatus::LegacyUpdateAvailable;
1058 plugin->m_managed_metadata = md;
1059 } else {
1060 // Otherwise, this is an uninstalled managed plugin.
1061 plugin->m_status = PluginStatus::ManagedInstallAvailable;
1062 }
1063 }
1064 }
1065
1066 plugin_array.Clear();
1067 for (const auto& p : loaded_plugins) plugin_array.Add(p);
1068 evt_pluglist_change.Notify();
1069}
1070
1072 bool rv = true;
1073 while (plugin_array.GetCount()) {
1074 if (!UnLoadPlugIn(0)) {
1075 rv = false;
1076 }
1077 }
1078 return rv;
1079}
1080
1082 for (auto* pic : plugin_array) {
1083 if (pic && pic->m_enabled && pic->m_init_state) DeactivatePlugIn(pic);
1084 }
1085 return true;
1086}
1087
1088#ifdef __WXMSW__
1089/*Convert Virtual Address to File Offset */
1090DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt) {
1091 size_t i = 0;
1092 PIMAGE_SECTION_HEADER pSeh;
1093 if (rva == 0) {
1094 return (rva);
1095 }
1096 pSeh = psh;
1097 for (i = 0; i < pnt->FileHeader.NumberOfSections; i++) {
1098 if (rva >= pSeh->VirtualAddress &&
1099 rva < pSeh->VirtualAddress + pSeh->Misc.VirtualSize) {
1100 break;
1101 }
1102 pSeh++;
1103 }
1104 return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
1105}
1106#endif
1107
1109public:
1110 ModuleInfo() : type_magic(0) {}
1111 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, DependencySet);
1112 WX_DECLARE_HASH_MAP(wxString, wxString, wxStringHash, wxStringEqual,
1113 DependencyMap);
1114
1115 uint64_t type_magic;
1116 DependencyMap dependencies;
1117};
1118
1119#ifdef USE_LIBELF
1120bool ReadModuleInfoFromELF(const wxString& file,
1121 const ModuleInfo::DependencySet& dependencies,
1122 ModuleInfo& info) {
1123 static bool b_libelf_initialized = false;
1124 static bool b_libelf_usable = false;
1125
1126 if (b_libelf_usable) {
1127 // Nothing to do.
1128 } else if (b_libelf_initialized) {
1129 return false;
1130 } else if (elf_version(EV_CURRENT) == EV_NONE) {
1131 b_libelf_initialized = true;
1132 b_libelf_usable = false;
1133 wxLogError("LibELF is outdated.");
1134 return false;
1135 } else {
1136 b_libelf_initialized = true;
1137 b_libelf_usable = true;
1138 }
1139
1140 int file_handle;
1141 Elf* elf_handle = nullptr;
1142 GElf_Ehdr elf_file_header;
1143 Elf_Scn* elf_section_handle = nullptr;
1144
1145 file_handle = open(file, O_RDONLY);
1146 if (file_handle == -1) {
1147 wxLogMessage("Could not open file \"%s\" for reading: %s", file,
1148 strerror(errno));
1149 goto FailureEpilogue;
1150 }
1151
1152 elf_handle = elf_begin(file_handle, ELF_C_READ, nullptr);
1153 if (elf_handle == nullptr) {
1154 wxLogMessage("Could not get ELF structures from \"%s\".", file);
1155 goto FailureEpilogue;
1156 }
1157
1158 if (gelf_getehdr(elf_handle, &elf_file_header) != &elf_file_header) {
1159 wxLogMessage("Could not get ELF file header from \"%s\".", file);
1160 goto FailureEpilogue;
1161 }
1162
1163 switch (elf_file_header.e_type) {
1164 case ET_EXEC:
1165 case ET_DYN:
1166 break;
1167 default:
1168 wxLogMessage(wxString::Format(
1169 "Module \"%s\" is not an executable or shared library.", file));
1170 goto FailureEpilogue;
1171 }
1172
1173 info.type_magic =
1174 (static_cast<uint64_t>(elf_file_header.e_ident[EI_CLASS])
1175 << 0) | // ELF class (32/64).
1176 (static_cast<uint64_t>(elf_file_header.e_ident[EI_DATA])
1177 << 8) | // Endianness.
1178 (static_cast<uint64_t>(elf_file_header.e_ident[EI_OSABI])
1179 << 16) | // OS ABI (Linux, FreeBSD, etc.).
1180 (static_cast<uint64_t>(elf_file_header.e_ident[EI_ABIVERSION])
1181 << 24) | // OS ABI version.
1182 (static_cast<uint64_t>(elf_file_header.e_machine)
1183 << 32) | // Instruction set.
1184 0;
1185
1186 while ((elf_section_handle = elf_nextscn(elf_handle, elf_section_handle)) !=
1187 nullptr) {
1188 GElf_Shdr elf_section_header;
1189 Elf_Data* elf_section_data = nullptr;
1190 size_t elf_section_entry_count = 0;
1191
1192 if (gelf_getshdr(elf_section_handle, &elf_section_header) !=
1193 &elf_section_header) {
1194 wxLogMessage("Could not get ELF section header from \"%s\".", file);
1195 goto FailureEpilogue;
1196 } else if (elf_section_header.sh_type != SHT_DYNAMIC) {
1197 continue;
1198 }
1199
1200 elf_section_data = elf_getdata(elf_section_handle, nullptr);
1201 if (elf_section_data == nullptr) {
1202 wxLogMessage("Could not get ELF section data from \"%s\".", file);
1203 goto FailureEpilogue;
1204 }
1205
1206 if ((elf_section_data->d_size == 0) ||
1207 (elf_section_header.sh_entsize == 0)) {
1208 wxLogMessage("Got malformed ELF section metadata from \"%s\".", file);
1209 goto FailureEpilogue;
1210 }
1211
1212 elf_section_entry_count =
1213 elf_section_data->d_size / elf_section_header.sh_entsize;
1214 for (size_t elf_section_entry_index = 0;
1215 elf_section_entry_index < elf_section_entry_count;
1216 ++elf_section_entry_index) {
1217 GElf_Dyn elf_dynamic_entry;
1218 const char* elf_dynamic_entry_name = nullptr;
1219 if (gelf_getdyn(elf_section_data,
1220 static_cast<int>(elf_section_entry_index),
1221 &elf_dynamic_entry) != &elf_dynamic_entry) {
1222 wxLogMessage("Could not get ELF dynamic_section entry from \"%s\".",
1223 file);
1224 goto FailureEpilogue;
1225 } else if (elf_dynamic_entry.d_tag != DT_NEEDED) {
1226 continue;
1227 }
1228 elf_dynamic_entry_name = elf_strptr(
1229 elf_handle, elf_section_header.sh_link, elf_dynamic_entry.d_un.d_val);
1230 if (elf_dynamic_entry_name == nullptr) {
1231 wxLogMessage(wxString::Format("Could not get %s %s from \"%s\".", "ELF",
1232 "string entry", file));
1233 goto FailureEpilogue;
1234 }
1235 wxString name_full(elf_dynamic_entry_name);
1236 wxString name_part(elf_dynamic_entry_name,
1237 strcspn(elf_dynamic_entry_name, "-."));
1238 if (dependencies.find(name_part) != dependencies.end()) {
1239 info.dependencies.insert(
1240 ModuleInfo::DependencyMap::value_type(name_part, name_full));
1241 }
1242 }
1243 }
1244
1245 goto SuccessEpilogue;
1246
1247SuccessEpilogue:
1248 elf_end(elf_handle);
1249 close(file_handle);
1250 return true;
1251
1252FailureEpilogue:
1253 if (elf_handle != nullptr) elf_end(elf_handle);
1254 if (file_handle >= 0) close(file_handle);
1255 wxLog::FlushActive();
1256 return false;
1257}
1258#endif // USE_LIBELF
1259
1260bool PluginLoader::CheckPluginCompatibility(const wxString& plugin_file) {
1261 bool b_compat = false;
1262#ifdef __WXOSX__
1263 // TODO: Actually do some tests (In previous versions b_compat was initialized
1264 // to true, so the actual behavior was exactly like this)
1265 b_compat = true;
1266#endif
1267#ifdef __WXMSW__
1268 // For Windows we identify the dll file containing the core wxWidgets
1269 // functions Later we will compare this with the file containing the wxWidgets
1270 // functions used by plugins. If these file names match exactly then we
1271 // assume the plugin is compatible. By using the file names we avoid having to
1272 // hard code the file name into the OpenCPN sources. This makes it easier to
1273 // update wxWigets versions without editing sources. NOTE: this solution may
1274 // not follow symlinks but almost no one uses simlinks for wxWidgets dlls
1275
1276 // Only go through this process once per instance of O.
1277 if (!m_found_wxwidgets) {
1278 DWORD myPid = GetCurrentProcessId();
1279 HANDLE hProcess =
1280 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, myPid);
1281 if (hProcess == NULL) {
1282 wxLogMessage(wxString::Format("Cannot identify running process for %s",
1283 plugin_file.c_str()));
1284 } else {
1285 // Find namme of wxWidgets core DLL used by the current process
1286 // so we can compare it to the one used by the plugin
1287 HMODULE hMods[1024];
1288 DWORD cbNeeded;
1289 if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
1290 for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
1291 TCHAR szModName[MAX_PATH];
1292 if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
1293 sizeof(szModName) / sizeof(TCHAR))) {
1294 m_module_name = szModName;
1295 if (m_module_name.Find("wxmsw") != wxNOT_FOUND) {
1296 if (m_module_name.Find("_core_") != wxNOT_FOUND) {
1297 m_found_wxwidgets = true;
1298 wxLogMessage(wxString::Format("Found wxWidgets core DLL: %s",
1299 m_module_name.c_str()));
1300 break;
1301 }
1302 }
1303 }
1304 }
1305 } else {
1306 wxLogMessage(wxString::Format("Cannot enumerate process modules for %s",
1307 plugin_file.c_str()));
1308 }
1309 if (hProcess) CloseHandle(hProcess);
1310 }
1311 }
1312 if (!m_found_wxwidgets) {
1313 wxLogMessage(wxString::Format("Cannot identify wxWidgets core DLL for %s",
1314 plugin_file.c_str()));
1315 } else {
1316 LPCWSTR fName = plugin_file.wc_str();
1317 HANDLE handle = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING,
1318 FILE_ATTRIBUTE_NORMAL, 0);
1319 DWORD byteread, size = GetFileSize(handle, NULL);
1320 PVOID virtualpointer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
1321 bool status = ReadFile(handle, virtualpointer, size, &byteread, NULL);
1322 CloseHandle(handle);
1323 PIMAGE_NT_HEADERS ntheaders =
1324 (PIMAGE_NT_HEADERS)(PCHAR(virtualpointer) +
1325 PIMAGE_DOS_HEADER(virtualpointer)->e_lfanew);
1326 PIMAGE_SECTION_HEADER pSech =
1327 IMAGE_FIRST_SECTION(ntheaders); // Pointer to first section header
1328 PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // Pointer to import descriptor
1329 if (ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
1330 .Size !=
1331 0) /*if size of the table is 0 - Import Table does not exist */
1332 {
1333 pImportDescriptor =
1334 (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)virtualpointer +
1335 Rva2Offset(
1336 ntheaders->OptionalHeader
1337 .DataDirectory
1338 [IMAGE_DIRECTORY_ENTRY_IMPORT]
1339 .VirtualAddress,
1340 pSech, ntheaders));
1341 LPSTR libname[256];
1342 size_t i = 0;
1343 // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR or we find core
1344 // wxWidgets DLL
1345 while (pImportDescriptor->Name != 0) {
1346 // Get the name of each DLL
1347 libname[i] =
1348 (PCHAR)((DWORD_PTR)virtualpointer +
1349 Rva2Offset(pImportDescriptor->Name, pSech, ntheaders));
1350 // Check if the plugin DLL dependencey is same as main process wxWidgets
1351 // core DLL
1352 if (m_module_name.Find(libname[i]) != wxNOT_FOUND) {
1353 // Match found - plugin is compatible
1354 b_compat = true;
1355 wxLogMessage(wxString::Format(
1356 "Compatible wxWidgets plugin library found for %s: %s",
1357 plugin_file.c_str(), libname[i]));
1358 break;
1359 }
1360 pImportDescriptor++; // advance to next IMAGE_IMPORT_DESCRIPTOR
1361 i++;
1362 }
1363 } else {
1364 wxLogMessage(
1365 wxString::Format("No Import Table! in %s", plugin_file.c_str()));
1366 }
1367 if (virtualpointer) VirtualFree(virtualpointer, size, MEM_DECOMMIT);
1368 }
1369#endif
1370#if defined(__WXGTK__) || defined(__WXQT__)
1371#if defined(USE_LIBELF)
1372
1373 static bool b_own_info_queried = false;
1374 static bool b_own_info_usable = false;
1375 static ModuleInfo own_info;
1376 static ModuleInfo::DependencySet dependencies;
1377
1378 if (!b_own_info_queried) {
1379 dependencies.insert("libwx_baseu");
1380
1381 char exe_buf[100] = {0};
1382 ssize_t len = readlink("/proc/self/exe", exe_buf, 99);
1383 if (len > 0) {
1384 exe_buf[len] = '\0';
1385 wxString app_path(exe_buf);
1386 wxLogMessage("Executable path: %s", exe_buf);
1387 b_own_info_usable =
1388 ReadModuleInfoFromELF(app_path, dependencies, own_info);
1389 if (!b_own_info_usable) {
1390 wxLogMessage("Cannot get own info from: %s", exe_buf);
1391 }
1392 } else {
1393 wxLogMessage("Cannot get own executable path.");
1394 }
1395 b_own_info_queried = true;
1396 }
1397
1398 if (b_own_info_usable) {
1399 bool b_pi_info_usable = false;
1400 ModuleInfo pi_info;
1401 b_pi_info_usable =
1402 ReadModuleInfoFromELF(plugin_file, dependencies, pi_info);
1403 if (b_pi_info_usable) {
1404 b_compat = (pi_info.type_magic == own_info.type_magic);
1405
1406 // OSABI field on flatpak builds
1407 if ((pi_info.type_magic ^ own_info.type_magic) == 0x00030000) {
1408 b_compat = true;
1409 }
1410
1411 if (!b_compat) {
1412 pi_info.dependencies.clear();
1413 wxLogMessage(
1414 wxString::Format(" Plugin \"%s\" is of another binary "
1415 "flavor than the main module.",
1416 plugin_file));
1417 wxLogMessage("host magic: %.8x, plugin magic: %.8x",
1418 own_info.type_magic, pi_info.type_magic);
1419 }
1420 for (const auto& own_dependency : own_info.dependencies) {
1421 ModuleInfo::DependencyMap::const_iterator pi_dependency =
1422 pi_info.dependencies.find(own_dependency.first);
1423 if ((pi_dependency != pi_info.dependencies.end()) &&
1424 (pi_dependency->second != own_dependency.second)) {
1425 b_compat = false;
1426 wxLogMessage(
1427 " Plugin \"%s\" depends on library \"%s\", but the main "
1428 "module was built for \"%s\".",
1429 plugin_file, pi_dependency->second, own_dependency.second);
1430 break;
1431 }
1432 }
1433 } else {
1434 b_compat = false;
1435 wxLogMessage(
1436 wxString::Format(" Plugin \"%s\" could not be reliably "
1437 "checked for compatibility.",
1438 plugin_file));
1439 }
1440 } else {
1441 // Allow any plugin when own info is not available.
1442 b_compat = true;
1443 }
1444
1445 wxLogMessage("Plugin is compatible by elf library scan: %s",
1446 b_compat ? "true" : "false");
1447
1448 wxLog::FlushActive();
1449 return b_compat;
1450
1451#endif // LIBELF
1452
1453 // But Android Plugins do not include the wxlib specification in their ELF
1454 // file. So we assume Android Plugins are compatible....
1455#ifdef __ANDROID__
1456 return true;
1457#endif
1458
1459 // If libelf is not available, then we must use a simplistic file scan method.
1460 // This is easily fooled if the wxWidgets version in use is not exactly
1461 // recognized. File scan is 3x faster than the ELF scan method
1462
1463 FILE* f = fopen(plugin_file, "r");
1464 char strver[26]; // Enough space even for very big integers...
1465 if (f == NULL) {
1466 wxLogMessage("Plugin %s can't be opened", plugin_file);
1467 return false;
1468 }
1469 sprintf(strver,
1470#if defined(__WXGTK3__)
1471 "libwx_gtk3u_core-%i.%i"
1472#elif defined(__WXGTK20__)
1473 "libwx_gtk2u_core-%i.%i"
1474#elif defined(__WXQT__)
1475 "libwx_qtu_core-%i.%i"
1476#else
1477#error undefined plugin platform
1478#endif
1479 ,
1480 wxMAJOR_VERSION, wxMINOR_VERSION);
1481 b_compat = false;
1482
1483 size_t pos(0);
1484 size_t len(strlen(strver));
1485 int c;
1486 while ((c = fgetc(f)) != EOF) {
1487 if (c == strver[pos]) {
1488 if (++pos == len) {
1489 b_compat = true;
1490 break;
1491 }
1492 } else
1493 pos = 0;
1494 }
1495 fclose(f);
1496#endif // __WXGTK__ or __WXQT__
1497
1498 wxLogMessage("Plugin is compatible: %s", b_compat ? "true" : "false");
1499 return b_compat;
1500}
1501
1502PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file) {
1503 auto pic = new PlugInContainer;
1504 if (!LoadPlugIn(plugin_file, pic)) {
1505 delete pic;
1506 return nullptr;
1507 } else {
1508 return pic;
1509 }
1510}
1511
1512PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file,
1513 PlugInContainer* pic) {
1514 wxLogMessage(wxString("PluginLoader: Loading PlugIn: ") + plugin_file);
1515
1516 if (plugin_file.empty()) {
1517 wxLogMessage("Ignoring loading of empty path");
1518 return nullptr;
1519 }
1520
1521 if (!wxIsReadable(plugin_file)) {
1522 wxLogMessage("Ignoring unreadable plugin %s",
1523 plugin_file.ToStdString().c_str());
1524 LoadError le(LoadError::Type::Unreadable, plugin_file.ToStdString());
1525 load_errors.push_back(le);
1526 return nullptr;
1527 }
1528
1529 // Check if blacklisted, exit if so.
1530 auto sts =
1531 m_blacklist->get_status(pic->m_common_name.ToStdString(),
1532 pic->m_version_major, pic->m_version_minor);
1533 if (sts != plug_status::unblocked) {
1534 wxLogDebug("Refusing to load blacklisted plugin: %s",
1535 pic->m_common_name.ToStdString().c_str());
1536 return nullptr;
1537 }
1538 auto data = m_blacklist->get_library_data(plugin_file.ToStdString());
1539 if (!data.name.empty()) {
1540 wxLogDebug("Refusing to load blacklisted library: %s",
1541 plugin_file.ToStdString().c_str());
1542 return nullptr;
1543 }
1544 pic->m_plugin_file = plugin_file;
1545 pic->m_status =
1546 PluginStatus::Unmanaged; // Status is updated later, if necessary
1547
1548 // load the library
1549 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
1550 pic->m_library.Load(plugin_file);
1551
1552 if (!pic->m_library.IsLoaded()) {
1553 // Look in the Blacklist, try to match a filename, to give some kind of
1554 // message extract the probable plugin name
1555 wxFileName fn(plugin_file);
1556 std::string name = fn.GetName().ToStdString();
1557 auto found = m_blacklist->get_library_data(name);
1558 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1559 wxLogMessage("Ignoring blacklisted plugin %s", name.c_str());
1560 if (!found.name.empty()) {
1561 SemanticVersion v(found.major, found.minor);
1562 LoadError le(LoadError::Type::Unloadable, name, v);
1563 load_errors.push_back(le);
1564 } else {
1565 LoadError le(LoadError::Type::Unloadable, plugin_file.ToStdString());
1566 load_errors.push_back(le);
1567 }
1568 }
1569 wxLogMessage(wxString(" PluginLoader: Cannot load library: ") +
1570 plugin_file);
1571 return nullptr;
1572 }
1573
1574 // load the factory symbols
1575 const char* const FIX_LOADING =
1576 _("\n Install/uninstall plugin or remove file to mute message");
1577 create_t* create_plugin = (create_t*)pic->m_library.GetSymbol("create_pi");
1578 if (nullptr == create_plugin) {
1579 std::string msg(_(" PluginLoader: Cannot load symbol create_pi: "));
1580 wxLogMessage(msg + plugin_file);
1581 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1582 LoadError le(LoadError::Type::NoCreate, plugin_file.ToStdString());
1583 load_errors.push_back(le);
1584 }
1585 return nullptr;
1586 }
1587
1588 destroy_t* destroy_plugin =
1589 (destroy_t*)pic->m_library.GetSymbol("destroy_pi");
1590 pic->m_destroy_fn = destroy_plugin;
1591 if (nullptr == destroy_plugin) {
1592 wxLogMessage(" PluginLoader: Cannot load symbol destroy_pi: " +
1593 plugin_file);
1594 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1595 LoadError le(LoadError::Type::NoDestroy, plugin_file.ToStdString());
1596 load_errors.push_back(le);
1597 }
1598 return nullptr;
1599 }
1600
1601 // create an instance of the plugin class
1602 opencpn_plugin* plug_in = create_plugin(this);
1603
1604 int api_major = plug_in->GetAPIVersionMajor();
1605 int api_minor = plug_in->GetAPIVersionMinor();
1606 int api_ver = (api_major * 100) + api_minor;
1607 pic->m_api_version = api_ver;
1608
1609 int pi_major = plug_in->GetPlugInVersionMajor();
1610 int pi_minor = plug_in->GetPlugInVersionMinor();
1611 SemanticVersion pi_ver(pi_major, pi_minor, -1);
1612
1613 wxString pi_name = plug_in->GetCommonName();
1614
1615 wxLogDebug("blacklist: Get status for %s %d %d",
1616 pi_name.ToStdString().c_str(), pi_major, pi_minor);
1617 const auto status =
1618 m_blacklist->get_status(pi_name.ToStdString(), pi_major, pi_minor);
1619 if (status != plug_status::unblocked) {
1620 wxLogDebug("Ignoring blacklisted plugin.");
1621 if (status != plug_status::unloadable) {
1622 SemanticVersion v(pi_major, pi_minor);
1623 LoadError le(LoadError::Type::Blacklisted, pi_name.ToStdString(), v);
1624 load_errors.push_back(le);
1625 }
1626 return nullptr;
1627 }
1628
1629 switch (api_ver) {
1630 case 105:
1631 pic->m_pplugin = dynamic_cast<opencpn_plugin*>(plug_in);
1632 break;
1633
1634 case 106:
1635 pic->m_pplugin = dynamic_cast<opencpn_plugin_16*>(plug_in);
1636 break;
1637
1638 case 107:
1639 pic->m_pplugin = dynamic_cast<opencpn_plugin_17*>(plug_in);
1640 break;
1641
1642 case 108:
1643 pic->m_pplugin = dynamic_cast<opencpn_plugin_18*>(plug_in);
1644 break;
1645
1646 case 109:
1647 pic->m_pplugin = dynamic_cast<opencpn_plugin_19*>(plug_in);
1648 break;
1649
1650 case 110:
1651 pic->m_pplugin = dynamic_cast<opencpn_plugin_110*>(plug_in);
1652 break;
1653
1654 case 111:
1655 pic->m_pplugin = dynamic_cast<opencpn_plugin_111*>(plug_in);
1656 break;
1657
1658 case 112:
1659 pic->m_pplugin = dynamic_cast<opencpn_plugin_112*>(plug_in);
1660 break;
1661
1662 case 113:
1663 pic->m_pplugin = dynamic_cast<opencpn_plugin_113*>(plug_in);
1664 break;
1665
1666 case 114:
1667 pic->m_pplugin = dynamic_cast<opencpn_plugin_114*>(plug_in);
1668 break;
1669
1670 case 115:
1671 pic->m_pplugin = dynamic_cast<opencpn_plugin_115*>(plug_in);
1672 break;
1673
1674 case 116:
1675 pic->m_pplugin = dynamic_cast<opencpn_plugin_116*>(plug_in);
1676 break;
1677
1678 case 117:
1679 pic->m_pplugin = dynamic_cast<opencpn_plugin_117*>(plug_in);
1680 break;
1681
1682 case 118:
1683 pic->m_pplugin = dynamic_cast<opencpn_plugin_118*>(plug_in);
1684 break;
1685
1686 case 119:
1687 pic->m_pplugin = dynamic_cast<opencpn_plugin_119*>(plug_in);
1688 break;
1689
1690 case 120:
1691 pic->m_pplugin = dynamic_cast<opencpn_plugin_120*>(plug_in);
1692 break;
1693
1694 case 121:
1695 pic->m_pplugin = dynamic_cast<opencpn_plugin_121*>(plug_in);
1696 break;
1697
1698 case 122:
1699 pic->m_pplugin = dynamic_cast<opencpn_plugin_122*>(plug_in);
1700 break;
1701
1702 default:
1703 break;
1704 }
1705
1706 if (auto p = dynamic_cast<opencpn_plugin_117*>(plug_in)) {
1707 // For API 1.17+ use the version info in the plugin API in favor of
1708 // the version file created when installing plugin.
1709 pi_ver =
1710 SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1711 p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1712 p->GetPlugInVersionBuild());
1713 }
1714
1715 if (!pic->m_pplugin) {
1716 INFO_LOG << _("Incompatible plugin detected: ") << plugin_file << "\n";
1717 INFO_LOG << _(" API Version detected: ");
1718 INFO_LOG << api_major << "." << api_minor << "\n";
1719 INFO_LOG << _(" PlugIn Version detected: ") << pi_ver << "\n";
1720 if (m_blacklist->mark_unloadable(pi_name.ToStdString(), pi_ver.major,
1721 pi_ver.minor)) {
1722 LoadError le(LoadError::Type::Incompatible, pi_name.ToStdString(),
1723 pi_ver);
1724 load_errors.push_back(le);
1725 }
1726 return nullptr;
1727 }
1728 return pic;
1729}
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.
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.
obs::EventVar evt_load_plugin
Notified with a PlugInContainer* pointer when a plugin is loaded.
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.
obs::EventVar evt_deactivate_plugin
Notified with plugin name when it's deactivated.
void UpdateManagedPlugins(bool keep_orphans)
Update all managed plugins i.
obs::EventVar evt_update_chart_types
Notified without data after all plugins loaded ot updated.
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.
obs::EventVar evt_plugin_loadall_finalize
Emitted after all plugins are loaded.
void SetSetupOptions(const wxString &common_name, bool value)
Update m_has_setup_options state for plugin with given name.
obs::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.
obs::EventVar evt_pluglist_change
Notified without data when the GetPlugInArray() list is changed.
bool DeactivateAllPlugIns()
Deactivate all plugins.
PlugInContainer * LoadPlugIn(const wxString &plugin_file)
Load given plugin file from disk into GetPlugInArray() list.
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)
bool GetMode()
Return true if we are running in safe mode.
Definition safe_mode.cpp:59
#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.