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