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