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