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 auto name = pic->m_pplugin->GetCommonName().ToStdString();
883 }
884 return true;
885}
886
888 auto pic = GetContainer(pd, plugin_array);
889 if (!pic) {
890 wxLogError("Attempt to deactivate non-existing plugin %s",
891 pd.m_common_name.ToStdString());
892 return false;
893 }
894 return DeactivatePlugIn(pic);
895}
896
898 if (ix >= plugin_array.GetCount()) {
899 wxLogWarning("Attempt to remove non-existing plugin %d", ix);
900 return false;
901 }
902 PlugInContainer* pic = plugin_array[ix];
903 if (!DeactivatePlugIn(pic)) {
904 return false;
905 }
906 if (pic->m_pplugin) {
907 pic->m_destroy_fn(pic->m_pplugin);
908 }
909
910 delete pic; // This will unload the PlugIn via DTOR of pic->m_library
911 plugin_array.RemoveAt(ix);
912 return true;
913}
914
915static std::string VersionFromManifest(const std::string& plugin_name) {
916 std::string version;
917 std::string path = PluginHandler::VersionPath(plugin_name);
918 if (!path.empty() && wxFileName::IsFileReadable(path)) {
919 std::ifstream stream;
920 stream.open(path, std::ifstream::in);
921 stream >> version;
922 }
923 return version;
924}
925
928 using namespace std;
929 if (name.empty()) return {};
930
931 auto import_path = PluginHandler::ImportedMetadataPath(name.c_str());
932 if (isRegularFile(import_path.c_str())) {
933 std::ifstream f(import_path.c_str());
934 std::stringstream ss;
935 ss << f.rdbuf();
937 ParsePlugin(ss.str(), pd);
938 pd.is_imported = true;
939 return pd;
940 }
941
943 vector<PluginMetadata> matches;
944 copy_if(available.begin(), available.end(), back_inserter(matches),
945 [name](const PluginMetadata& md) { return md.name == name; });
946 if (matches.size() == 0) return {};
947 if (matches.size() == 1) return matches[0]; // only one found with given name
948
949 auto version = VersionFromManifest(name);
950 auto predicate = [version](const PluginMetadata& md) {
951 return version == md.version;
952 };
953 auto found = find_if(matches.begin(), matches.end(), predicate);
954 return found != matches.end() ? *found : matches[0];
955}
956
959 using namespace std;
960 if (name.empty()) return {};
961
963 vector<PluginMetadata> matches;
964 copy_if(available.begin(), available.end(), back_inserter(matches),
965 [name](const PluginMetadata& md) { return md.name == name; });
966 if (matches.size() == 0) return {};
967 if (matches.size() == 1) return matches[0]; // only one found with given name
968
969 // Check for any later version available in the catalog
970 auto version = SemanticVersion::parse(VersionFromManifest(name));
971 auto rv = matches[0];
972 for (auto p : matches) {
973 auto catVersion = SemanticVersion::parse(p.version);
974 if (catVersion > version) {
975 version = catVersion;
976 rv = p;
977 }
978 }
979
980 return rv;
981}
982
985 const PluginMetadata& md) {
986 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
987 plugin->m_common_name.Lower());
988 bool is_system = found != SYSTEM_PLUGINS.end();
989
990 std::string installed = VersionFromManifest(md.name);
991 plugin->m_manifest_version = installed;
992 auto installedVersion = SemanticVersion::parse(installed);
993 auto metaVersion = SemanticVersion::parse(md.version);
994
995 if (is_system)
996 plugin->m_status = PluginStatus::System;
997 else if (plugin->m_status == PluginStatus::Imported)
998 ; // plugin->m_status = PluginStatus::Imported;
999 else if (installedVersion < metaVersion)
1000 plugin->m_status = PluginStatus::ManagedInstalledUpdateAvailable;
1001 else if (installedVersion == metaVersion)
1002 plugin->m_status = PluginStatus::ManagedInstalledCurrentVersion;
1003 else
1004 plugin->m_status = PluginStatus::ManagedInstalledDowngradeAvailable;
1005
1006 if (!is_system && md.is_orphan) plugin->m_status = PluginStatus::Unmanaged;
1007
1008 plugin->m_managed_metadata = md;
1009}
1010
1011void PluginLoader::UpdateManagedPlugins(bool keep_orphans) {
1012 std::vector<PlugInContainer*> loaded_plugins;
1013 for (auto& p : plugin_array) loaded_plugins.push_back(p);
1014
1015 // Initiate status to "unmanaged" or "system" on all plugins
1016 for (auto& p : loaded_plugins) {
1017 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
1018 p->m_common_name.Lower().ToStdString());
1019 bool is_system = found != SYSTEM_PLUGINS.end();
1020 p->m_status = is_system ? PluginStatus::System : PluginStatus::Unmanaged;
1021 }
1022 if (!keep_orphans) {
1023 // Remove any inactive/uninstalled managed plugins that are no longer
1024 // available in the current catalog Usually due to reverting from
1025 // Alpha/Beta catalog back to master
1026 auto predicate = [](const PlugInContainer* pd) -> bool {
1027 const auto md(
1028 PluginLoader::MetadataByName(pd->m_common_name.ToStdString()));
1029 return md.name.empty() && !md.is_imported && !pd->m_pplugin &&
1030 !IsSystemPluginName(pd->m_common_name.ToStdString());
1031 };
1032 auto end =
1033 std::remove_if(loaded_plugins.begin(), loaded_plugins.end(), predicate);
1034 loaded_plugins.erase(end, loaded_plugins.end());
1035 }
1036
1037 // Update from the catalog metadata
1038 for (auto& plugin : loaded_plugins) {
1039 auto md =
1040 PluginLoader::LatestMetadataByName(plugin->m_common_name.ToStdString());
1041 if (!md.name.empty()) {
1042 auto import_path = PluginHandler::ImportedMetadataPath(md.name.c_str());
1043 md.is_imported = isRegularFile(import_path.c_str());
1044 if (md.is_imported) {
1045 plugin->m_status = PluginStatus::Imported;
1046 } else if (isRegularFile(PluginHandler::FileListPath(md.name).c_str())) {
1047 // This is an installed plugin
1048 PluginLoader::UpdatePlugin(plugin, md);
1049 } else if (IsSystemPluginName(md.name)) {
1050 plugin->m_status = PluginStatus::System;
1051 } else if (md.is_orphan) {
1052 plugin->m_status = PluginStatus::Unmanaged;
1053 } else if (plugin->m_api_version) {
1054 // If the plugin is actually loaded, but the new plugin is known not
1055 // to be installed, then it must be a legacy plugin loaded.
1056 plugin->m_status = PluginStatus::LegacyUpdateAvailable;
1057 plugin->m_managed_metadata = md;
1058 } else {
1059 // Otherwise, this is an uninstalled managed plugin.
1060 plugin->m_status = PluginStatus::ManagedInstallAvailable;
1061 }
1062 }
1063 }
1064
1065 plugin_array.Clear();
1066 for (const auto& p : loaded_plugins) plugin_array.Add(p);
1068}
1069
1071 bool rv = true;
1072 while (plugin_array.GetCount()) {
1073 if (!UnLoadPlugIn(0)) {
1074 rv = false;
1075 }
1076 }
1077 return rv;
1078}
1079
1081 for (auto* pic : plugin_array) {
1082 if (pic && pic->m_enabled && pic->m_init_state) DeactivatePlugIn(pic);
1083 }
1084 return true;
1085}
1086
1087#ifdef __WXMSW__
1088/*Convert Virtual Address to File Offset */
1089DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt) {
1090 size_t i = 0;
1091 PIMAGE_SECTION_HEADER pSeh;
1092 if (rva == 0) {
1093 return (rva);
1094 }
1095 pSeh = psh;
1096 for (i = 0; i < pnt->FileHeader.NumberOfSections; i++) {
1097 if (rva >= pSeh->VirtualAddress &&
1098 rva < pSeh->VirtualAddress + pSeh->Misc.VirtualSize) {
1099 break;
1100 }
1101 pSeh++;
1102 }
1103 return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
1104}
1105#endif
1106
1108public:
1109 ModuleInfo() : type_magic(0) {}
1110 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, DependencySet);
1111 WX_DECLARE_HASH_MAP(wxString, wxString, wxStringHash, wxStringEqual,
1112 DependencyMap);
1113
1114 uint64_t type_magic;
1115 DependencyMap dependencies;
1116};
1117
1118#ifdef USE_LIBELF
1119bool ReadModuleInfoFromELF(const wxString& file,
1120 const ModuleInfo::DependencySet& dependencies,
1121 ModuleInfo& info) {
1122 static bool b_libelf_initialized = false;
1123 static bool b_libelf_usable = false;
1124
1125 if (b_libelf_usable) {
1126 // Nothing to do.
1127 } else if (b_libelf_initialized) {
1128 return false;
1129 } else if (elf_version(EV_CURRENT) == EV_NONE) {
1130 b_libelf_initialized = true;
1131 b_libelf_usable = false;
1132 wxLogError("LibELF is outdated.");
1133 return false;
1134 } else {
1135 b_libelf_initialized = true;
1136 b_libelf_usable = true;
1137 }
1138
1139 int file_handle;
1140 Elf* elf_handle = nullptr;
1141 GElf_Ehdr elf_file_header;
1142 Elf_Scn* elf_section_handle = nullptr;
1143
1144 file_handle = open(file, O_RDONLY);
1145 if (file_handle == -1) {
1146 wxLogMessage("Could not open file \"%s\" for reading: %s", file,
1147 strerror(errno));
1148 goto FailureEpilogue;
1149 }
1150
1151 elf_handle = elf_begin(file_handle, ELF_C_READ, nullptr);
1152 if (elf_handle == nullptr) {
1153 wxLogMessage("Could not get ELF structures from \"%s\".", file);
1154 goto FailureEpilogue;
1155 }
1156
1157 if (gelf_getehdr(elf_handle, &elf_file_header) != &elf_file_header) {
1158 wxLogMessage("Could not get ELF file header from \"%s\".", file);
1159 goto FailureEpilogue;
1160 }
1161
1162 switch (elf_file_header.e_type) {
1163 case ET_EXEC:
1164 case ET_DYN:
1165 break;
1166 default:
1167 wxLogMessage(wxString::Format(
1168 "Module \"%s\" is not an executable or shared library.", file));
1169 goto FailureEpilogue;
1170 }
1171
1172 info.type_magic =
1173 (static_cast<uint64_t>(elf_file_header.e_ident[EI_CLASS])
1174 << 0) | // ELF class (32/64).
1175 (static_cast<uint64_t>(elf_file_header.e_ident[EI_DATA])
1176 << 8) | // Endianness.
1177 (static_cast<uint64_t>(elf_file_header.e_ident[EI_OSABI])
1178 << 16) | // OS ABI (Linux, FreeBSD, etc.).
1179 (static_cast<uint64_t>(elf_file_header.e_ident[EI_ABIVERSION])
1180 << 24) | // OS ABI version.
1181 (static_cast<uint64_t>(elf_file_header.e_machine)
1182 << 32) | // Instruction set.
1183 0;
1184
1185 while ((elf_section_handle = elf_nextscn(elf_handle, elf_section_handle)) !=
1186 nullptr) {
1187 GElf_Shdr elf_section_header;
1188 Elf_Data* elf_section_data = nullptr;
1189 size_t elf_section_entry_count = 0;
1190
1191 if (gelf_getshdr(elf_section_handle, &elf_section_header) !=
1192 &elf_section_header) {
1193 wxLogMessage("Could not get ELF section header from \"%s\".", file);
1194 goto FailureEpilogue;
1195 } else if (elf_section_header.sh_type != SHT_DYNAMIC) {
1196 continue;
1197 }
1198
1199 elf_section_data = elf_getdata(elf_section_handle, nullptr);
1200 if (elf_section_data == nullptr) {
1201 wxLogMessage("Could not get ELF section data from \"%s\".", file);
1202 goto FailureEpilogue;
1203 }
1204
1205 if ((elf_section_data->d_size == 0) ||
1206 (elf_section_header.sh_entsize == 0)) {
1207 wxLogMessage("Got malformed ELF section metadata from \"%s\".", file);
1208 goto FailureEpilogue;
1209 }
1210
1211 elf_section_entry_count =
1212 elf_section_data->d_size / elf_section_header.sh_entsize;
1213 for (size_t elf_section_entry_index = 0;
1214 elf_section_entry_index < elf_section_entry_count;
1215 ++elf_section_entry_index) {
1216 GElf_Dyn elf_dynamic_entry;
1217 const char* elf_dynamic_entry_name = nullptr;
1218 if (gelf_getdyn(elf_section_data,
1219 static_cast<int>(elf_section_entry_index),
1220 &elf_dynamic_entry) != &elf_dynamic_entry) {
1221 wxLogMessage("Could not get ELF dynamic_section entry from \"%s\".",
1222 file);
1223 goto FailureEpilogue;
1224 } else if (elf_dynamic_entry.d_tag != DT_NEEDED) {
1225 continue;
1226 }
1227 elf_dynamic_entry_name = elf_strptr(
1228 elf_handle, elf_section_header.sh_link, elf_dynamic_entry.d_un.d_val);
1229 if (elf_dynamic_entry_name == nullptr) {
1230 wxLogMessage(wxString::Format("Could not get %s %s from \"%s\".", "ELF",
1231 "string entry", file));
1232 goto FailureEpilogue;
1233 }
1234 wxString name_full(elf_dynamic_entry_name);
1235 wxString name_part(elf_dynamic_entry_name,
1236 strcspn(elf_dynamic_entry_name, "-."));
1237 if (dependencies.find(name_part) != dependencies.end()) {
1238 info.dependencies.insert(
1239 ModuleInfo::DependencyMap::value_type(name_part, name_full));
1240 }
1241 }
1242 }
1243
1244 goto SuccessEpilogue;
1245
1246SuccessEpilogue:
1247 elf_end(elf_handle);
1248 close(file_handle);
1249 return true;
1250
1251FailureEpilogue:
1252 if (elf_handle != nullptr) elf_end(elf_handle);
1253 if (file_handle >= 0) close(file_handle);
1254 wxLog::FlushActive();
1255 return false;
1256}
1257#endif // USE_LIBELF
1258
1259bool PluginLoader::CheckPluginCompatibility(const wxString& plugin_file) {
1260 bool b_compat = false;
1261#ifdef __WXOSX__
1262 // TODO: Actually do some tests (In previous versions b_compat was initialized
1263 // to true, so the actual behavior was exactly like this)
1264 b_compat = true;
1265#endif
1266#ifdef __WXMSW__
1267 // For Windows we identify the dll file containing the core wxWidgets
1268 // functions Later we will compare this with the file containing the wxWidgets
1269 // functions used by plugins. If these file names match exactly then we
1270 // assume the plugin is compatible. By using the file names we avoid having to
1271 // hard code the file name into the OpenCPN sources. This makes it easier to
1272 // update wxWigets versions without editing sources. NOTE: this solution may
1273 // not follow symlinks but almost no one uses simlinks for wxWidgets dlls
1274
1275 // Only go through this process once per instance of O.
1276 if (!m_found_wxwidgets) {
1277 DWORD myPid = GetCurrentProcessId();
1278 HANDLE hProcess =
1279 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, myPid);
1280 if (hProcess == NULL) {
1281 wxLogMessage(wxString::Format("Cannot identify running process for %s",
1282 plugin_file.c_str()));
1283 } else {
1284 // Find namme of wxWidgets core DLL used by the current process
1285 // so we can compare it to the one used by the plugin
1286 HMODULE hMods[1024];
1287 DWORD cbNeeded;
1288 if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
1289 for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
1290 TCHAR szModName[MAX_PATH];
1291 if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
1292 sizeof(szModName) / sizeof(TCHAR))) {
1293 m_module_name = szModName;
1294 if (m_module_name.Find("wxmsw") != wxNOT_FOUND) {
1295 if (m_module_name.Find("_core_") != wxNOT_FOUND) {
1296 m_found_wxwidgets = true;
1297 wxLogMessage(wxString::Format("Found wxWidgets core DLL: %s",
1298 m_module_name.c_str()));
1299 break;
1300 }
1301 }
1302 }
1303 }
1304 } else {
1305 wxLogMessage(wxString::Format("Cannot enumerate process modules for %s",
1306 plugin_file.c_str()));
1307 }
1308 if (hProcess) CloseHandle(hProcess);
1309 }
1310 }
1311 if (!m_found_wxwidgets) {
1312 wxLogMessage(wxString::Format("Cannot identify wxWidgets core DLL for %s",
1313 plugin_file.c_str()));
1314 } else {
1315 LPCWSTR fName = plugin_file.wc_str();
1316 HANDLE handle = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING,
1317 FILE_ATTRIBUTE_NORMAL, 0);
1318 DWORD byteread, size = GetFileSize(handle, NULL);
1319 PVOID virtualpointer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
1320 bool status = ReadFile(handle, virtualpointer, size, &byteread, NULL);
1321 CloseHandle(handle);
1322 PIMAGE_NT_HEADERS ntheaders =
1323 (PIMAGE_NT_HEADERS)(PCHAR(virtualpointer) +
1324 PIMAGE_DOS_HEADER(virtualpointer)->e_lfanew);
1325 PIMAGE_SECTION_HEADER pSech =
1326 IMAGE_FIRST_SECTION(ntheaders); // Pointer to first section header
1327 PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // Pointer to import descriptor
1328 if (ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
1329 .Size !=
1330 0) /*if size of the table is 0 - Import Table does not exist */
1331 {
1332 pImportDescriptor =
1333 (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)virtualpointer +
1334 Rva2Offset(
1335 ntheaders->OptionalHeader
1336 .DataDirectory
1337 [IMAGE_DIRECTORY_ENTRY_IMPORT]
1338 .VirtualAddress,
1339 pSech, ntheaders));
1340 LPSTR libname[256];
1341 size_t i = 0;
1342 // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR or we find core
1343 // wxWidgets DLL
1344 while (pImportDescriptor->Name != 0) {
1345 // Get the name of each DLL
1346 libname[i] =
1347 (PCHAR)((DWORD_PTR)virtualpointer +
1348 Rva2Offset(pImportDescriptor->Name, pSech, ntheaders));
1349 // Check if the plugin DLL dependencey is same as main process wxWidgets
1350 // core DLL
1351 if (m_module_name.Find(libname[i]) != wxNOT_FOUND) {
1352 // Match found - plugin is compatible
1353 b_compat = true;
1354 wxLogMessage(wxString::Format(
1355 "Compatible wxWidgets plugin library found for %s: %s",
1356 plugin_file.c_str(), libname[i]));
1357 break;
1358 }
1359 pImportDescriptor++; // advance to next IMAGE_IMPORT_DESCRIPTOR
1360 i++;
1361 }
1362 } else {
1363 wxLogMessage(
1364 wxString::Format("No Import Table! in %s", plugin_file.c_str()));
1365 }
1366 if (virtualpointer) VirtualFree(virtualpointer, size, MEM_DECOMMIT);
1367 }
1368#endif
1369#if defined(__WXGTK__) || defined(__WXQT__)
1370#if defined(USE_LIBELF)
1371
1372 static bool b_own_info_queried = false;
1373 static bool b_own_info_usable = false;
1374 static ModuleInfo own_info;
1375 static ModuleInfo::DependencySet dependencies;
1376
1377 if (!b_own_info_queried) {
1378 dependencies.insert("libwx_baseu");
1379
1380 char exe_buf[100] = {0};
1381 ssize_t len = readlink("/proc/self/exe", exe_buf, 99);
1382 if (len > 0) {
1383 exe_buf[len] = '\0';
1384 wxString app_path(exe_buf);
1385 wxLogMessage("Executable path: %s", exe_buf);
1386 b_own_info_usable =
1387 ReadModuleInfoFromELF(app_path, dependencies, own_info);
1388 if (!b_own_info_usable) {
1389 wxLogMessage("Cannot get own info from: %s", exe_buf);
1390 }
1391 } else {
1392 wxLogMessage("Cannot get own executable path.");
1393 }
1394 b_own_info_queried = true;
1395 }
1396
1397 if (b_own_info_usable) {
1398 bool b_pi_info_usable = false;
1399 ModuleInfo pi_info;
1400 b_pi_info_usable =
1401 ReadModuleInfoFromELF(plugin_file, dependencies, pi_info);
1402 if (b_pi_info_usable) {
1403 b_compat = (pi_info.type_magic == own_info.type_magic);
1404
1405 // OSABI field on flatpak builds
1406 if ((pi_info.type_magic ^ own_info.type_magic) == 0x00030000) {
1407 b_compat = true;
1408 }
1409
1410 if (!b_compat) {
1411 pi_info.dependencies.clear();
1412 wxLogMessage(
1413 wxString::Format(" Plugin \"%s\" is of another binary "
1414 "flavor than the main module.",
1415 plugin_file));
1416 wxLogMessage("host magic: %.8x, plugin magic: %.8x",
1417 own_info.type_magic, pi_info.type_magic);
1418 }
1419 for (const auto& own_dependency : own_info.dependencies) {
1420 ModuleInfo::DependencyMap::const_iterator pi_dependency =
1421 pi_info.dependencies.find(own_dependency.first);
1422 if ((pi_dependency != pi_info.dependencies.end()) &&
1423 (pi_dependency->second != own_dependency.second)) {
1424 b_compat = false;
1425 wxLogMessage(
1426 " Plugin \"%s\" depends on library \"%s\", but the main "
1427 "module was built for \"%s\".",
1428 plugin_file, pi_dependency->second, own_dependency.second);
1429 break;
1430 }
1431 }
1432 } else {
1433 b_compat = false;
1434 wxLogMessage(
1435 wxString::Format(" Plugin \"%s\" could not be reliably "
1436 "checked for compatibility.",
1437 plugin_file));
1438 }
1439 } else {
1440 // Allow any plugin when own info is not available.
1441 b_compat = true;
1442 }
1443
1444 wxLogMessage("Plugin is compatible by elf library scan: %s",
1445 b_compat ? "true" : "false");
1446
1447 wxLog::FlushActive();
1448 return b_compat;
1449
1450#endif // LIBELF
1451
1452 // But Android Plugins do not include the wxlib specification in their ELF
1453 // file. So we assume Android Plugins are compatible....
1454#ifdef __ANDROID__
1455 return true;
1456#endif
1457
1458 // If libelf is not available, then we must use a simplistic file scan method.
1459 // This is easily fooled if the wxWidgets version in use is not exactly
1460 // recognized. File scan is 3x faster than the ELF scan method
1461
1462 FILE* f = fopen(plugin_file, "r");
1463 char strver[26]; // Enough space even for very big integers...
1464 if (f == NULL) {
1465 wxLogMessage("Plugin %s can't be opened", plugin_file);
1466 return false;
1467 }
1468 sprintf(strver,
1469#if defined(__WXGTK3__)
1470 "libwx_gtk3u_core-%i.%i"
1471#elif defined(__WXGTK20__)
1472 "libwx_gtk2u_core-%i.%i"
1473#elif defined(__WXQT__)
1474 "libwx_qtu_core-%i.%i"
1475#else
1476#error undefined plugin platform
1477#endif
1478 ,
1479 wxMAJOR_VERSION, wxMINOR_VERSION);
1480 b_compat = false;
1481
1482 size_t pos(0);
1483 size_t len(strlen(strver));
1484 int c;
1485 while ((c = fgetc(f)) != EOF) {
1486 if (c == strver[pos]) {
1487 if (++pos == len) {
1488 b_compat = true;
1489 break;
1490 }
1491 } else
1492 pos = 0;
1493 }
1494 fclose(f);
1495#endif // __WXGTK__ or __WXQT__
1496
1497 wxLogMessage("Plugin is compatible: %s", b_compat ? "true" : "false");
1498 return b_compat;
1499}
1500
1501PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file) {
1502 auto pic = new PlugInContainer;
1503 if (!LoadPlugIn(plugin_file, pic)) {
1504 delete pic;
1505 return nullptr;
1506 } else {
1507 return pic;
1508 }
1509}
1510
1511PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file,
1512 PlugInContainer* pic) {
1513 wxLogMessage(wxString("PluginLoader: Loading PlugIn: ") + plugin_file);
1514
1515 if (plugin_file.empty()) {
1516 wxLogMessage("Ignoring loading of empty path");
1517 return nullptr;
1518 }
1519
1520 if (!wxIsReadable(plugin_file)) {
1521 wxLogMessage("Ignoring unreadable plugin %s",
1522 plugin_file.ToStdString().c_str());
1523 LoadError le(LoadError::Type::Unreadable, plugin_file.ToStdString());
1524 load_errors.push_back(le);
1525 return nullptr;
1526 }
1527
1528 // Check if blacklisted, exit if so.
1529 auto sts =
1530 m_blacklist->get_status(pic->m_common_name.ToStdString(),
1531 pic->m_version_major, pic->m_version_minor);
1532 if (sts != plug_status::unblocked) {
1533 wxLogDebug("Refusing to load blacklisted plugin: %s",
1534 pic->m_common_name.ToStdString().c_str());
1535 return nullptr;
1536 }
1537 auto data = m_blacklist->get_library_data(plugin_file.ToStdString());
1538 if (!data.name.empty()) {
1539 wxLogDebug("Refusing to load blacklisted library: %s",
1540 plugin_file.ToStdString().c_str());
1541 return nullptr;
1542 }
1543 pic->m_plugin_file = plugin_file;
1544 pic->m_status =
1545 PluginStatus::Unmanaged; // Status is updated later, if necessary
1546
1547 // load the library
1548 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
1549 pic->m_library.Load(plugin_file);
1550
1551 if (!pic->m_library.IsLoaded()) {
1552 // Look in the Blacklist, try to match a filename, to give some kind of
1553 // message extract the probable plugin name
1554 wxFileName fn(plugin_file);
1555 std::string name = fn.GetName().ToStdString();
1556 auto found = m_blacklist->get_library_data(name);
1557 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1558 wxLogMessage("Ignoring blacklisted plugin %s", name.c_str());
1559 if (!found.name.empty()) {
1560 SemanticVersion v(found.major, found.minor);
1561 LoadError le(LoadError::Type::Unloadable, name, v);
1562 load_errors.push_back(le);
1563 } else {
1564 LoadError le(LoadError::Type::Unloadable, plugin_file.ToStdString());
1565 load_errors.push_back(le);
1566 }
1567 }
1568 wxLogMessage(wxString(" PluginLoader: Cannot load library: ") +
1569 plugin_file);
1570 return nullptr;
1571 }
1572
1573 // load the factory symbols
1574 const char* const FIX_LOADING =
1575 _("\n Install/uninstall plugin or remove file to mute message");
1576 create_t* create_plugin = (create_t*)pic->m_library.GetSymbol("create_pi");
1577 if (nullptr == create_plugin) {
1578 std::string msg(_(" PluginLoader: Cannot load symbol create_pi: "));
1579 wxLogMessage(msg + plugin_file);
1580 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1581 LoadError le(LoadError::Type::NoCreate, plugin_file.ToStdString());
1582 load_errors.push_back(le);
1583 }
1584 return nullptr;
1585 }
1586
1587 destroy_t* destroy_plugin =
1588 (destroy_t*)pic->m_library.GetSymbol("destroy_pi");
1589 pic->m_destroy_fn = destroy_plugin;
1590 if (nullptr == destroy_plugin) {
1591 wxLogMessage(" PluginLoader: Cannot load symbol destroy_pi: " +
1592 plugin_file);
1593 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1594 LoadError le(LoadError::Type::NoDestroy, plugin_file.ToStdString());
1595 load_errors.push_back(le);
1596 }
1597 return nullptr;
1598 }
1599
1600 // create an instance of the plugin class
1601 opencpn_plugin* plug_in = create_plugin(this);
1602
1603 int api_major = plug_in->GetAPIVersionMajor();
1604 int api_minor = plug_in->GetAPIVersionMinor();
1605 int api_ver = (api_major * 100) + api_minor;
1606 pic->m_api_version = api_ver;
1607
1608 int pi_major = plug_in->GetPlugInVersionMajor();
1609 int pi_minor = plug_in->GetPlugInVersionMinor();
1610 SemanticVersion pi_ver(pi_major, pi_minor, -1);
1611
1612 wxString pi_name = plug_in->GetCommonName();
1613
1614 wxLogDebug("blacklist: Get status for %s %d %d",
1615 pi_name.ToStdString().c_str(), pi_major, pi_minor);
1616 const auto status =
1617 m_blacklist->get_status(pi_name.ToStdString(), pi_major, pi_minor);
1618 if (status != plug_status::unblocked) {
1619 wxLogDebug("Ignoring blacklisted plugin.");
1620 if (status != plug_status::unloadable) {
1621 SemanticVersion v(pi_major, pi_minor);
1622 LoadError le(LoadError::Type::Blacklisted, pi_name.ToStdString(), v);
1623 load_errors.push_back(le);
1624 }
1625 return nullptr;
1626 }
1627
1628 switch (api_ver) {
1629 case 105:
1630 pic->m_pplugin = dynamic_cast<opencpn_plugin*>(plug_in);
1631 break;
1632
1633 case 106:
1634 pic->m_pplugin = dynamic_cast<opencpn_plugin_16*>(plug_in);
1635 break;
1636
1637 case 107:
1638 pic->m_pplugin = dynamic_cast<opencpn_plugin_17*>(plug_in);
1639 break;
1640
1641 case 108:
1642 pic->m_pplugin = dynamic_cast<opencpn_plugin_18*>(plug_in);
1643 break;
1644
1645 case 109:
1646 pic->m_pplugin = dynamic_cast<opencpn_plugin_19*>(plug_in);
1647 break;
1648
1649 case 110:
1650 pic->m_pplugin = dynamic_cast<opencpn_plugin_110*>(plug_in);
1651 break;
1652
1653 case 111:
1654 pic->m_pplugin = dynamic_cast<opencpn_plugin_111*>(plug_in);
1655 break;
1656
1657 case 112:
1658 pic->m_pplugin = dynamic_cast<opencpn_plugin_112*>(plug_in);
1659 break;
1660
1661 case 113:
1662 pic->m_pplugin = dynamic_cast<opencpn_plugin_113*>(plug_in);
1663 break;
1664
1665 case 114:
1666 pic->m_pplugin = dynamic_cast<opencpn_plugin_114*>(plug_in);
1667 break;
1668
1669 case 115:
1670 pic->m_pplugin = dynamic_cast<opencpn_plugin_115*>(plug_in);
1671 break;
1672
1673 case 116:
1674 pic->m_pplugin = dynamic_cast<opencpn_plugin_116*>(plug_in);
1675 break;
1676
1677 case 117:
1678 pic->m_pplugin = dynamic_cast<opencpn_plugin_117*>(plug_in);
1679 break;
1680
1681 case 118:
1682 pic->m_pplugin = dynamic_cast<opencpn_plugin_118*>(plug_in);
1683 break;
1684
1685 case 119:
1686 pic->m_pplugin = dynamic_cast<opencpn_plugin_119*>(plug_in);
1687 break;
1688
1689 case 120:
1690 pic->m_pplugin = dynamic_cast<opencpn_plugin_120*>(plug_in);
1691 break;
1692
1693 case 121:
1694 pic->m_pplugin = dynamic_cast<opencpn_plugin_121*>(plug_in);
1695 break;
1696
1697 case 122:
1698 pic->m_pplugin = dynamic_cast<opencpn_plugin_122*>(plug_in);
1699 break;
1700
1701 default:
1702 break;
1703 }
1704
1705 if (auto p = dynamic_cast<opencpn_plugin_117*>(plug_in)) {
1706 // For API 1.17+ use the version info in the plugin API in favor of
1707 // the version file created when installing plugin.
1708 pi_ver =
1709 SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1710 p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1711 p->GetPlugInVersionBuild());
1712 }
1713
1714 if (!pic->m_pplugin) {
1715 INFO_LOG << _("Incompatible plugin detected: ") << plugin_file << "\n";
1716 INFO_LOG << _(" API Version detected: ");
1717 INFO_LOG << api_major << "." << api_minor << "\n";
1718 INFO_LOG << _(" PlugIn Version detected: ") << pi_ver << "\n";
1719 if (m_blacklist->mark_unloadable(pi_name.ToStdString(), pi_ver.major,
1720 pi_ver.minor)) {
1721 LoadError le(LoadError::Type::Incompatible, pi_name.ToStdString(),
1722 pi_ver);
1723 load_errors.push_back(le);
1724 }
1725 return nullptr;
1726 }
1727 return pic;
1728}
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.
EventVar evt_deactivate_plugin
Notified with plugin name when it's deactivated.
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.