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 m_on_activate_cb(pic);
653
654 auto pbm0 = pic->m_pplugin->GetPlugInBitmap();
655 if (!pbm0->IsOk()) {
656 pbm0 = (wxBitmap*)GetPluginDefaultIcon();
657 }
658 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
659 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
660
661 if (!pic->m_enabled && pic->m_destroy_fn) {
662 pic->m_destroy_fn(pic->m_pplugin);
663 pic->m_destroy_fn = nullptr;
664 pic->m_pplugin = nullptr;
665 pic->m_init_state = false;
666 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
667 }
668
669 // Check to see if the plugin just processed has an associated catalog
670 // entry understanding that SYSTEM plugins have no metadata by design
671 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
672 pic->m_common_name.Lower());
673 bool is_system = found != SYSTEM_PLUGINS.end();
674
675 if (!is_system) {
677 wxString name = pic->m_common_name;
678 auto it = find_if(
679 available.begin(), available.end(),
680 [name](const PluginMetadata& md) { return md.name == name; });
681
682 if (it == available.end()) {
683 // Installed plugin is an orphan....
684 // Add a stub metadata entry to the active CatalogHandler context
685 // to satisfy minimal PIM functionality
686
687 auto oprhan_metadata = CreateMetadata(pic);
688 auto catalogHdlr = CatalogHandler::GetInstance();
689 catalogHdlr->AddMetadataToActiveContext(oprhan_metadata);
690 }
691 }
692
693 } else { // No pic->m_pplugin
694 wxLogMessage(
695 " PluginLoader: Unloading invalid PlugIn, API version %d ",
696 pic->m_api_version);
697 pic->m_destroy_fn(pic->m_pplugin);
698
699 LoadError le(LoadError::Type::Unloadable, file_name.ToStdString());
700 delete pic;
701 load_errors.push_back(le);
702 return false;
703 }
704 } else { // pic == 0
705 return false;
706 }
707 ClearLoadStamp(plugin_loadstamp.ToStdString());
708 return true;
709}
710
711// Helper function: loads all plugins from a single directory
712bool PluginLoader::LoadPlugInDirectory(const wxString& plugin_dir,
713 bool load_enabled) {
715 m_plugin_location = plugin_dir;
716
717 wxString msg("PluginLoader searching for PlugIns in location ");
718 msg += m_plugin_location;
719 wxLogMessage(msg);
720
721#ifdef __WXMSW__
722 wxString pispec = "*_pi.dll";
723#elif defined(__WXOSX__)
724 wxString pispec = "*_pi.dylib";
725#else
726 wxString pispec = "*_pi.so";
727#endif
728
729 if (!::wxDirExists(m_plugin_location)) {
730 msg = m_plugin_location;
731 msg.Prepend(" Directory ");
732 msg.Append(" does not exist.");
733 wxLogMessage(msg);
734 return false;
735 }
736
737 if (!g_BasePlatform->isPlatformCapable(PLATFORM_CAP_PLUGINS)) return false;
738
739 wxArrayString file_list;
740
741 int get_flags = wxDIR_FILES | wxDIR_DIRS;
742#ifdef __WXMSW__
743#ifdef _DEBUG
744 get_flags = wxDIR_FILES;
745#endif
746#endif
747
748#ifdef __ANDROID__
749 get_flags = wxDIR_FILES; // No subdirs, especially "/files" where PlugIns are
750 // initially placed in APK
751#endif
752
753 bool ret =
754 false; // return true if at least one new plugins gets loaded/unloaded
755 wxDir::GetAllFiles(m_plugin_location, &file_list, pispec, get_flags);
756
757 wxLogMessage("Found %d candidates", (int)file_list.GetCount());
758 for (auto& file_name : file_list) {
759 wxLog::FlushActive();
760
761 LoadPluginCandidate(file_name, load_enabled);
762 }
763
764 // Scrub the plugin array...
765 // Here, looking for duplicates caused by new installation of a plugin
766 // We want to remove the previous entry representing the uninstalled packaged
767 // plugin metadata
768 for (unsigned int i = 0; i < plugin_array.GetCount(); i++) {
769 PlugInContainer* pic = plugin_array[i];
770 for (unsigned int j = i + 1; j < plugin_array.GetCount(); j++) {
771 PlugInContainer* pict = plugin_array[j];
772
773 if (pic->m_common_name == pict->m_common_name) {
774 if (pic->m_plugin_file.IsEmpty())
775 plugin_array.Item(i)->m_status = PluginStatus::PendingListRemoval;
776 else
777 plugin_array.Item(j)->m_status = PluginStatus::PendingListRemoval;
778 }
779 }
780 }
781
782 // Remove any list items marked
783 size_t i = 0;
784 while ((i >= 0) && (i < plugin_array.GetCount())) {
785 PlugInContainer* pict = plugin_array.Item(i);
786 if (pict->m_status == PluginStatus::PendingListRemoval) {
787 plugin_array.RemoveAt(i);
788 i = 0;
789 } else
790 i++;
791 }
792
793 return ret;
794}
795
797 bool bret = false;
798
799 for (const auto& pic : plugin_array) {
800 // Try to confirm that the m_pplugin member points to a valid plugin
801 // image...
802 if (pic->m_pplugin) {
803 auto ppl = dynamic_cast<opencpn_plugin*>(pic->m_pplugin);
804 if (!ppl) {
805 pic->m_pplugin = nullptr;
806 pic->m_init_state = false;
807 }
808 }
809
810 // Installed and loaded?
811 if (!pic->m_pplugin) { // Needs a reload?
812 if (pic->m_enabled) {
813 PluginStatus stat = pic->m_status;
814 PlugInContainer* newpic = LoadPlugIn(pic->m_plugin_file, pic);
815 if (newpic) {
816 pic->m_status = stat;
817 pic->m_enabled = true;
818 }
819 } else
820 continue;
821 }
822
823 if (pic->m_enabled && !pic->m_init_state && pic->m_pplugin) {
824 wxString msg("PluginLoader: Initializing PlugIn: ");
825 msg += pic->m_plugin_file;
826 wxLogMessage(msg);
828 pic->m_has_setup_options = false;
829 pic->m_cap_flag = pic->m_pplugin->Init();
830 pic->m_pplugin->SetDefaults();
831 pic->m_init_state = true;
832 ProcessLateInit(pic);
833 pic->m_short_description = pic->m_pplugin->GetShortDescription();
834 pic->m_long_description = pic->m_pplugin->GetLongDescription();
835 pic->m_version_major = pic->m_pplugin->GetPlugInVersionMajor();
836 pic->m_version_minor = pic->m_pplugin->GetPlugInVersionMinor();
837 wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
838 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
839 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
840 m_on_activate_cb(pic);
841 bret = true;
842 } else if (!pic->m_enabled && pic->m_init_state) {
843 // Save a local copy of the plugin icon before unloading
844 wxBitmap* pbm0 = pic->m_pplugin->GetPlugInBitmap();
845 pic->m_bitmap = wxBitmap(pbm0->GetSubBitmap(
846 wxRect(0, 0, pbm0->GetWidth(), pbm0->GetHeight())));
847
848 bret = DeactivatePlugIn(pic);
849 if (pic->m_pplugin) pic->m_destroy_fn(pic->m_pplugin);
850 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
851 pic->m_pplugin = nullptr;
852 pic->m_init_state = false;
853 pic->m_has_setup_options = false;
854 }
855 }
857 return bret;
858}
859
861 if (!pic) return false;
862 if (pic->m_init_state) {
863 wxString msg("PluginLoader: Deactivating PlugIn: ");
864 wxLogMessage(msg + pic->m_plugin_file);
865 m_on_deactivate_cb(pic);
866 pic->m_init_state = false;
867 pic->m_pplugin->DeInit();
868 }
869 return true;
870}
871
873 auto pic = GetContainer(pd, plugin_array);
874 if (!pic) {
875 wxLogError("Attempt to deactivate non-existing plugin %s",
876 pd.m_common_name.ToStdString());
877 return false;
878 }
879 return DeactivatePlugIn(pic);
880}
881
883 if (ix >= plugin_array.GetCount()) {
884 wxLogWarning("Attempt to remove non-existing plugin %d", ix);
885 return false;
886 }
887 PlugInContainer* pic = plugin_array[ix];
888 if (!DeactivatePlugIn(pic)) {
889 return false;
890 }
891 if (pic->m_pplugin) {
892 pic->m_destroy_fn(pic->m_pplugin);
893 }
894
895 delete pic; // This will unload the PlugIn via DTOR of pic->m_library
896 plugin_array.RemoveAt(ix);
897 return true;
898}
899
900static std::string VersionFromManifest(const std::string& plugin_name) {
901 std::string version;
902 std::string path = PluginHandler::VersionPath(plugin_name);
903 if (!path.empty() && wxFileName::IsFileReadable(path)) {
904 std::ifstream stream;
905 stream.open(path, std::ifstream::in);
906 stream >> version;
907 }
908 return version;
909}
910
913 using namespace std;
914 if (name.empty()) return {};
915
916 auto import_path = PluginHandler::ImportedMetadataPath(name.c_str());
917 if (isRegularFile(import_path.c_str())) {
918 std::ifstream f(import_path.c_str());
919 std::stringstream ss;
920 ss << f.rdbuf();
922 ParsePlugin(ss.str(), pd);
923 pd.is_imported = true;
924 return pd;
925 }
926
928 vector<PluginMetadata> matches;
929 copy_if(available.begin(), available.end(), back_inserter(matches),
930 [name](const PluginMetadata& md) { return md.name == name; });
931 if (matches.size() == 0) return {};
932 if (matches.size() == 1) return matches[0]; // only one found with given name
933
934 auto version = VersionFromManifest(name);
935 auto predicate = [version](const PluginMetadata& md) {
936 return version == md.version;
937 };
938 auto found = find_if(matches.begin(), matches.end(), predicate);
939 return found != matches.end() ? *found : matches[0];
940}
941
944 using namespace std;
945 if (name.empty()) return {};
946
948 vector<PluginMetadata> matches;
949 copy_if(available.begin(), available.end(), back_inserter(matches),
950 [name](const PluginMetadata& md) { return md.name == name; });
951 if (matches.size() == 0) return {};
952 if (matches.size() == 1) return matches[0]; // only one found with given name
953
954 // Check for any later version available in the catalog
955 auto version = SemanticVersion::parse(VersionFromManifest(name));
956 auto rv = matches[0];
957 for (auto p : matches) {
958 auto catVersion = SemanticVersion::parse(p.version);
959 if (catVersion > version) {
960 version = catVersion;
961 rv = p;
962 }
963 }
964
965 return rv;
966}
967
970 const PluginMetadata& md) {
971 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
972 plugin->m_common_name.Lower());
973 bool is_system = found != SYSTEM_PLUGINS.end();
974
975 std::string installed = VersionFromManifest(md.name);
976 plugin->m_manifest_version = installed;
977 auto installedVersion = SemanticVersion::parse(installed);
978 auto metaVersion = SemanticVersion::parse(md.version);
979
980 if (is_system)
981 plugin->m_status = PluginStatus::System;
982 else if (plugin->m_status == PluginStatus::Imported)
983 ; // plugin->m_status = PluginStatus::Imported;
984 else if (installedVersion < metaVersion)
985 plugin->m_status = PluginStatus::ManagedInstalledUpdateAvailable;
986 else if (installedVersion == metaVersion)
987 plugin->m_status = PluginStatus::ManagedInstalledCurrentVersion;
988 else
989 plugin->m_status = PluginStatus::ManagedInstalledDowngradeAvailable;
990
991 if (!is_system && md.is_orphan) plugin->m_status = PluginStatus::Unmanaged;
992
993 plugin->m_managed_metadata = md;
994}
995
996void PluginLoader::UpdateManagedPlugins(bool keep_orphans) {
997 std::vector<PlugInContainer*> loaded_plugins;
998 for (auto& p : plugin_array) loaded_plugins.push_back(p);
999
1000 // Initiate status to "unmanaged" or "system" on all plugins
1001 for (auto& p : loaded_plugins) {
1002 auto found = std::find(SYSTEM_PLUGINS.begin(), SYSTEM_PLUGINS.end(),
1003 p->m_common_name.Lower().ToStdString());
1004 bool is_system = found != SYSTEM_PLUGINS.end();
1005 p->m_status = is_system ? PluginStatus::System : PluginStatus::Unmanaged;
1006 }
1007 if (!keep_orphans) {
1008 // Remove any inactive/uninstalled managed plugins that are no longer
1009 // available in the current catalog Usually due to reverting from
1010 // Alpha/Beta catalog back to master
1011 auto predicate = [](const PlugInContainer* pd) -> bool {
1012 const auto md(
1013 PluginLoader::MetadataByName(pd->m_common_name.ToStdString()));
1014 return md.name.empty() && !md.is_imported && !pd->m_pplugin &&
1015 !IsSystemPluginName(pd->m_common_name.ToStdString());
1016 };
1017 auto end =
1018 std::remove_if(loaded_plugins.begin(), loaded_plugins.end(), predicate);
1019 loaded_plugins.erase(end, loaded_plugins.end());
1020 }
1021
1022 // Update from the catalog metadata
1023 for (auto& plugin : loaded_plugins) {
1024 auto md =
1025 PluginLoader::LatestMetadataByName(plugin->m_common_name.ToStdString());
1026 if (!md.name.empty()) {
1027 auto import_path = PluginHandler::ImportedMetadataPath(md.name.c_str());
1028 md.is_imported = isRegularFile(import_path.c_str());
1029 if (md.is_imported) {
1030 plugin->m_status = PluginStatus::Imported;
1031 } else if (isRegularFile(PluginHandler::FileListPath(md.name).c_str())) {
1032 // This is an installed plugin
1033 PluginLoader::UpdatePlugin(plugin, md);
1034 } else if (IsSystemPluginName(md.name)) {
1035 plugin->m_status = PluginStatus::System;
1036 } else if (md.is_orphan) {
1037 plugin->m_status = PluginStatus::Unmanaged;
1038 } else if (plugin->m_api_version) {
1039 // If the plugin is actually loaded, but the new plugin is known not
1040 // to be installed, then it must be a legacy plugin loaded.
1041 plugin->m_status = PluginStatus::LegacyUpdateAvailable;
1042 plugin->m_managed_metadata = md;
1043 } else {
1044 // Otherwise, this is an uninstalled managed plugin.
1045 plugin->m_status = PluginStatus::ManagedInstallAvailable;
1046 }
1047 }
1048 }
1049
1050 plugin_array.Clear();
1051 for (const auto& p : loaded_plugins) plugin_array.Add(p);
1053}
1054
1056 bool rv = true;
1057 while (plugin_array.GetCount()) {
1058 if (!UnLoadPlugIn(0)) {
1059 rv = false;
1060 }
1061 }
1062 return rv;
1063}
1064
1066 for (auto* pic : plugin_array) {
1067 if (pic && pic->m_enabled && pic->m_init_state) DeactivatePlugIn(pic);
1068 }
1069 return true;
1070}
1071
1072#ifdef __WXMSW__
1073/*Convert Virtual Address to File Offset */
1074DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt) {
1075 size_t i = 0;
1076 PIMAGE_SECTION_HEADER pSeh;
1077 if (rva == 0) {
1078 return (rva);
1079 }
1080 pSeh = psh;
1081 for (i = 0; i < pnt->FileHeader.NumberOfSections; i++) {
1082 if (rva >= pSeh->VirtualAddress &&
1083 rva < pSeh->VirtualAddress + pSeh->Misc.VirtualSize) {
1084 break;
1085 }
1086 pSeh++;
1087 }
1088 return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
1089}
1090#endif
1091
1093public:
1094 ModuleInfo() : type_magic(0) {}
1095 WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, DependencySet);
1096 WX_DECLARE_HASH_MAP(wxString, wxString, wxStringHash, wxStringEqual,
1097 DependencyMap);
1098
1099 uint64_t type_magic;
1100 DependencyMap dependencies;
1101};
1102
1103#ifdef USE_LIBELF
1104bool ReadModuleInfoFromELF(const wxString& file,
1105 const ModuleInfo::DependencySet& dependencies,
1106 ModuleInfo& info) {
1107 static bool b_libelf_initialized = false;
1108 static bool b_libelf_usable = false;
1109
1110 if (b_libelf_usable) {
1111 // Nothing to do.
1112 } else if (b_libelf_initialized) {
1113 return false;
1114 } else if (elf_version(EV_CURRENT) == EV_NONE) {
1115 b_libelf_initialized = true;
1116 b_libelf_usable = false;
1117 wxLogError("LibELF is outdated.");
1118 return false;
1119 } else {
1120 b_libelf_initialized = true;
1121 b_libelf_usable = true;
1122 }
1123
1124 int file_handle;
1125 Elf* elf_handle = nullptr;
1126 GElf_Ehdr elf_file_header;
1127 Elf_Scn* elf_section_handle = nullptr;
1128
1129 file_handle = open(file, O_RDONLY);
1130 if (file_handle == -1) {
1131 wxLogMessage("Could not open file \"%s\" for reading: %s", file,
1132 strerror(errno));
1133 goto FailureEpilogue;
1134 }
1135
1136 elf_handle = elf_begin(file_handle, ELF_C_READ, nullptr);
1137 if (elf_handle == nullptr) {
1138 wxLogMessage("Could not get ELF structures from \"%s\".", file);
1139 goto FailureEpilogue;
1140 }
1141
1142 if (gelf_getehdr(elf_handle, &elf_file_header) != &elf_file_header) {
1143 wxLogMessage("Could not get ELF file header from \"%s\".", file);
1144 goto FailureEpilogue;
1145 }
1146
1147 switch (elf_file_header.e_type) {
1148 case ET_EXEC:
1149 case ET_DYN:
1150 break;
1151 default:
1152 wxLogMessage(wxString::Format(
1153 "Module \"%s\" is not an executable or shared library.", file));
1154 goto FailureEpilogue;
1155 }
1156
1157 info.type_magic =
1158 (static_cast<uint64_t>(elf_file_header.e_ident[EI_CLASS])
1159 << 0) | // ELF class (32/64).
1160 (static_cast<uint64_t>(elf_file_header.e_ident[EI_DATA])
1161 << 8) | // Endianness.
1162 (static_cast<uint64_t>(elf_file_header.e_ident[EI_OSABI])
1163 << 16) | // OS ABI (Linux, FreeBSD, etc.).
1164 (static_cast<uint64_t>(elf_file_header.e_ident[EI_ABIVERSION])
1165 << 24) | // OS ABI version.
1166 (static_cast<uint64_t>(elf_file_header.e_machine)
1167 << 32) | // Instruction set.
1168 0;
1169
1170 while ((elf_section_handle = elf_nextscn(elf_handle, elf_section_handle)) !=
1171 nullptr) {
1172 GElf_Shdr elf_section_header;
1173 Elf_Data* elf_section_data = nullptr;
1174 size_t elf_section_entry_count = 0;
1175
1176 if (gelf_getshdr(elf_section_handle, &elf_section_header) !=
1177 &elf_section_header) {
1178 wxLogMessage("Could not get ELF section header from \"%s\".", file);
1179 goto FailureEpilogue;
1180 } else if (elf_section_header.sh_type != SHT_DYNAMIC) {
1181 continue;
1182 }
1183
1184 elf_section_data = elf_getdata(elf_section_handle, nullptr);
1185 if (elf_section_data == nullptr) {
1186 wxLogMessage("Could not get ELF section data from \"%s\".", file);
1187 goto FailureEpilogue;
1188 }
1189
1190 if ((elf_section_data->d_size == 0) ||
1191 (elf_section_header.sh_entsize == 0)) {
1192 wxLogMessage("Got malformed ELF section metadata from \"%s\".", file);
1193 goto FailureEpilogue;
1194 }
1195
1196 elf_section_entry_count =
1197 elf_section_data->d_size / elf_section_header.sh_entsize;
1198 for (size_t elf_section_entry_index = 0;
1199 elf_section_entry_index < elf_section_entry_count;
1200 ++elf_section_entry_index) {
1201 GElf_Dyn elf_dynamic_entry;
1202 const char* elf_dynamic_entry_name = nullptr;
1203 if (gelf_getdyn(elf_section_data,
1204 static_cast<int>(elf_section_entry_index),
1205 &elf_dynamic_entry) != &elf_dynamic_entry) {
1206 wxLogMessage("Could not get ELF dynamic_section entry from \"%s\".",
1207 file);
1208 goto FailureEpilogue;
1209 } else if (elf_dynamic_entry.d_tag != DT_NEEDED) {
1210 continue;
1211 }
1212 elf_dynamic_entry_name = elf_strptr(
1213 elf_handle, elf_section_header.sh_link, elf_dynamic_entry.d_un.d_val);
1214 if (elf_dynamic_entry_name == nullptr) {
1215 wxLogMessage(wxString::Format("Could not get %s %s from \"%s\".", "ELF",
1216 "string entry", file));
1217 goto FailureEpilogue;
1218 }
1219 wxString name_full(elf_dynamic_entry_name);
1220 wxString name_part(elf_dynamic_entry_name,
1221 strcspn(elf_dynamic_entry_name, "-."));
1222 if (dependencies.find(name_part) != dependencies.end()) {
1223 info.dependencies.insert(
1224 ModuleInfo::DependencyMap::value_type(name_part, name_full));
1225 }
1226 }
1227 }
1228
1229 goto SuccessEpilogue;
1230
1231SuccessEpilogue:
1232 elf_end(elf_handle);
1233 close(file_handle);
1234 return true;
1235
1236FailureEpilogue:
1237 if (elf_handle != nullptr) elf_end(elf_handle);
1238 if (file_handle >= 0) close(file_handle);
1239 wxLog::FlushActive();
1240 return false;
1241}
1242#endif // USE_LIBELF
1243
1244bool PluginLoader::CheckPluginCompatibility(const wxString& plugin_file) {
1245 bool b_compat = false;
1246#ifdef __WXOSX__
1247 // TODO: Actually do some tests (In previous versions b_compat was initialized
1248 // to true, so the actual behavior was exactly like this)
1249 b_compat = true;
1250#endif
1251#ifdef __WXMSW__
1252 // For Windows we identify the dll file containing the core wxWidgets
1253 // functions Later we will compare this with the file containing the wxWidgets
1254 // functions used by plugins. If these file names match exactly then we
1255 // assume the plugin is compatible. By using the file names we avoid having to
1256 // hard code the file name into the OpenCPN sources. This makes it easier to
1257 // update wxWigets versions without editing sources. NOTE: this solution may
1258 // not follow symlinks but almost no one uses simlinks for wxWidgets dlls
1259
1260 // Only go through this process once per instance of O.
1261 if (!m_found_wxwidgets) {
1262 DWORD myPid = GetCurrentProcessId();
1263 HANDLE hProcess =
1264 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, myPid);
1265 if (hProcess == NULL) {
1266 wxLogMessage(wxString::Format("Cannot identify running process for %s",
1267 plugin_file.c_str()));
1268 } else {
1269 // Find namme of wxWidgets core DLL used by the current process
1270 // so we can compare it to the one used by the plugin
1271 HMODULE hMods[1024];
1272 DWORD cbNeeded;
1273 if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
1274 for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
1275 TCHAR szModName[MAX_PATH];
1276 if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
1277 sizeof(szModName) / sizeof(TCHAR))) {
1278 m_module_name = szModName;
1279 if (m_module_name.Find("wxmsw") != wxNOT_FOUND) {
1280 if (m_module_name.Find("_core_") != wxNOT_FOUND) {
1281 m_found_wxwidgets = true;
1282 wxLogMessage(wxString::Format("Found wxWidgets core DLL: %s",
1283 m_module_name.c_str()));
1284 break;
1285 }
1286 }
1287 }
1288 }
1289 } else {
1290 wxLogMessage(wxString::Format("Cannot enumerate process modules for %s",
1291 plugin_file.c_str()));
1292 }
1293 if (hProcess) CloseHandle(hProcess);
1294 }
1295 }
1296 if (!m_found_wxwidgets) {
1297 wxLogMessage(wxString::Format("Cannot identify wxWidgets core DLL for %s",
1298 plugin_file.c_str()));
1299 } else {
1300 LPCWSTR fName = plugin_file.wc_str();
1301 HANDLE handle = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING,
1302 FILE_ATTRIBUTE_NORMAL, 0);
1303 DWORD byteread, size = GetFileSize(handle, NULL);
1304 PVOID virtualpointer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
1305 bool status = ReadFile(handle, virtualpointer, size, &byteread, NULL);
1306 CloseHandle(handle);
1307 PIMAGE_NT_HEADERS ntheaders =
1308 (PIMAGE_NT_HEADERS)(PCHAR(virtualpointer) +
1309 PIMAGE_DOS_HEADER(virtualpointer)->e_lfanew);
1310 PIMAGE_SECTION_HEADER pSech =
1311 IMAGE_FIRST_SECTION(ntheaders); // Pointer to first section header
1312 PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // Pointer to import descriptor
1313 if (ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
1314 .Size !=
1315 0) /*if size of the table is 0 - Import Table does not exist */
1316 {
1317 pImportDescriptor =
1318 (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)virtualpointer +
1319 Rva2Offset(
1320 ntheaders->OptionalHeader
1321 .DataDirectory
1322 [IMAGE_DIRECTORY_ENTRY_IMPORT]
1323 .VirtualAddress,
1324 pSech, ntheaders));
1325 LPSTR libname[256];
1326 size_t i = 0;
1327 // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR or we find core
1328 // wxWidgets DLL
1329 while (pImportDescriptor->Name != 0) {
1330 // Get the name of each DLL
1331 libname[i] =
1332 (PCHAR)((DWORD_PTR)virtualpointer +
1333 Rva2Offset(pImportDescriptor->Name, pSech, ntheaders));
1334 // Check if the plugin DLL dependencey is same as main process wxWidgets
1335 // core DLL
1336 if (m_module_name.Find(libname[i]) != wxNOT_FOUND) {
1337 // Match found - plugin is compatible
1338 b_compat = true;
1339 wxLogMessage(wxString::Format(
1340 "Compatible wxWidgets plugin library found for %s: %s",
1341 plugin_file.c_str(), libname[i]));
1342 break;
1343 }
1344 pImportDescriptor++; // advance to next IMAGE_IMPORT_DESCRIPTOR
1345 i++;
1346 }
1347 } else {
1348 wxLogMessage(
1349 wxString::Format("No Import Table! in %s", plugin_file.c_str()));
1350 }
1351 if (virtualpointer) VirtualFree(virtualpointer, size, MEM_DECOMMIT);
1352 }
1353#endif
1354#if defined(__WXGTK__) || defined(__WXQT__)
1355#if defined(USE_LIBELF)
1356
1357 static bool b_own_info_queried = false;
1358 static bool b_own_info_usable = false;
1359 static ModuleInfo own_info;
1360 static ModuleInfo::DependencySet dependencies;
1361
1362 if (!b_own_info_queried) {
1363 dependencies.insert("libwx_baseu");
1364
1365 char exe_buf[100] = {0};
1366 ssize_t len = readlink("/proc/self/exe", exe_buf, 99);
1367 if (len > 0) {
1368 exe_buf[len] = '\0';
1369 wxString app_path(exe_buf);
1370 wxLogMessage("Executable path: %s", exe_buf);
1371 b_own_info_usable =
1372 ReadModuleInfoFromELF(app_path, dependencies, own_info);
1373 if (!b_own_info_usable) {
1374 wxLogMessage("Cannot get own info from: %s", exe_buf);
1375 }
1376 } else {
1377 wxLogMessage("Cannot get own executable path.");
1378 }
1379 b_own_info_queried = true;
1380 }
1381
1382 if (b_own_info_usable) {
1383 bool b_pi_info_usable = false;
1384 ModuleInfo pi_info;
1385 b_pi_info_usable =
1386 ReadModuleInfoFromELF(plugin_file, dependencies, pi_info);
1387 if (b_pi_info_usable) {
1388 b_compat = (pi_info.type_magic == own_info.type_magic);
1389
1390 // OSABI field on flatpak builds
1391 if ((pi_info.type_magic ^ own_info.type_magic) == 0x00030000) {
1392 b_compat = true;
1393 }
1394
1395 if (!b_compat) {
1396 pi_info.dependencies.clear();
1397 wxLogMessage(
1398 wxString::Format(" Plugin \"%s\" is of another binary "
1399 "flavor than the main module.",
1400 plugin_file));
1401 wxLogMessage("host magic: %.8x, plugin magic: %.8x",
1402 own_info.type_magic, pi_info.type_magic);
1403 }
1404 for (const auto& own_dependency : own_info.dependencies) {
1405 ModuleInfo::DependencyMap::const_iterator pi_dependency =
1406 pi_info.dependencies.find(own_dependency.first);
1407 if ((pi_dependency != pi_info.dependencies.end()) &&
1408 (pi_dependency->second != own_dependency.second)) {
1409 b_compat = false;
1410 wxLogMessage(
1411 " Plugin \"%s\" depends on library \"%s\", but the main "
1412 "module was built for \"%s\".",
1413 plugin_file, pi_dependency->second, own_dependency.second);
1414 break;
1415 }
1416 }
1417 } else {
1418 b_compat = false;
1419 wxLogMessage(
1420 wxString::Format(" Plugin \"%s\" could not be reliably "
1421 "checked for compatibility.",
1422 plugin_file));
1423 }
1424 } else {
1425 // Allow any plugin when own info is not available.
1426 b_compat = true;
1427 }
1428
1429 wxLogMessage("Plugin is compatible by elf library scan: %s",
1430 b_compat ? "true" : "false");
1431
1432 wxLog::FlushActive();
1433 return b_compat;
1434
1435#endif // LIBELF
1436
1437 // But Android Plugins do not include the wxlib specification in their ELF
1438 // file. So we assume Android Plugins are compatible....
1439#ifdef __ANDROID__
1440 return true;
1441#endif
1442
1443 // If libelf is not available, then we must use a simplistic file scan method.
1444 // This is easily fooled if the wxWidgets version in use is not exactly
1445 // recognized. File scan is 3x faster than the ELF scan method
1446
1447 FILE* f = fopen(plugin_file, "r");
1448 char strver[26]; // Enough space even for very big integers...
1449 if (f == NULL) {
1450 wxLogMessage("Plugin %s can't be opened", plugin_file);
1451 return false;
1452 }
1453 sprintf(strver,
1454#if defined(__WXGTK3__)
1455 "libwx_gtk3u_core-%i.%i"
1456#elif defined(__WXGTK20__)
1457 "libwx_gtk2u_core-%i.%i"
1458#elif defined(__WXQT__)
1459 "libwx_qtu_core-%i.%i"
1460#else
1461#error undefined plugin platform
1462#endif
1463 ,
1464 wxMAJOR_VERSION, wxMINOR_VERSION);
1465 b_compat = false;
1466
1467 size_t pos(0);
1468 size_t len(strlen(strver));
1469 int c;
1470 while ((c = fgetc(f)) != EOF) {
1471 if (c == strver[pos]) {
1472 if (++pos == len) {
1473 b_compat = true;
1474 break;
1475 }
1476 } else
1477 pos = 0;
1478 }
1479 fclose(f);
1480#endif // __WXGTK__ or __WXQT__
1481
1482 wxLogMessage("Plugin is compatible: %s", b_compat ? "true" : "false");
1483 return b_compat;
1484}
1485
1486PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file) {
1487 auto pic = new PlugInContainer;
1488 if (!LoadPlugIn(plugin_file, pic)) {
1489 delete pic;
1490 return nullptr;
1491 } else {
1492 return pic;
1493 }
1494}
1495
1496PlugInContainer* PluginLoader::LoadPlugIn(const wxString& plugin_file,
1497 PlugInContainer* pic) {
1498 wxLogMessage(wxString("PluginLoader: Loading PlugIn: ") + plugin_file);
1499
1500 if (plugin_file.empty()) {
1501 wxLogMessage("Ignoring loading of empty path");
1502 return nullptr;
1503 }
1504
1505 if (!wxIsReadable(plugin_file)) {
1506 wxLogMessage("Ignoring unreadable plugin %s",
1507 plugin_file.ToStdString().c_str());
1508 LoadError le(LoadError::Type::Unreadable, plugin_file.ToStdString());
1509 load_errors.push_back(le);
1510 return nullptr;
1511 }
1512
1513 // Check if blacklisted, exit if so.
1514 auto sts =
1515 m_blacklist->get_status(pic->m_common_name.ToStdString(),
1516 pic->m_version_major, pic->m_version_minor);
1517 if (sts != plug_status::unblocked) {
1518 wxLogDebug("Refusing to load blacklisted plugin: %s",
1519 pic->m_common_name.ToStdString().c_str());
1520 return nullptr;
1521 }
1522 auto data = m_blacklist->get_library_data(plugin_file.ToStdString());
1523 if (!data.name.empty()) {
1524 wxLogDebug("Refusing to load blacklisted library: %s",
1525 plugin_file.ToStdString().c_str());
1526 return nullptr;
1527 }
1528 pic->m_plugin_file = plugin_file;
1529 pic->m_status =
1530 PluginStatus::Unmanaged; // Status is updated later, if necessary
1531
1532 // load the library
1533 if (pic->m_library.IsLoaded()) pic->m_library.Unload();
1534 pic->m_library.Load(plugin_file);
1535
1536 if (!pic->m_library.IsLoaded()) {
1537 // Look in the Blacklist, try to match a filename, to give some kind of
1538 // message extract the probable plugin name
1539 wxFileName fn(plugin_file);
1540 std::string name = fn.GetName().ToStdString();
1541 auto found = m_blacklist->get_library_data(name);
1542 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1543 wxLogMessage("Ignoring blacklisted plugin %s", name.c_str());
1544 if (!found.name.empty()) {
1545 SemanticVersion v(found.major, found.minor);
1546 LoadError le(LoadError::Type::Unloadable, name, v);
1547 load_errors.push_back(le);
1548 } else {
1549 LoadError le(LoadError::Type::Unloadable, plugin_file.ToStdString());
1550 load_errors.push_back(le);
1551 }
1552 }
1553 wxLogMessage(wxString(" PluginLoader: Cannot load library: ") +
1554 plugin_file);
1555 return nullptr;
1556 }
1557
1558 // load the factory symbols
1559 const char* const FIX_LOADING =
1560 _("\n Install/uninstall plugin or remove file to mute message");
1561 create_t* create_plugin = (create_t*)pic->m_library.GetSymbol("create_pi");
1562 if (nullptr == create_plugin) {
1563 std::string msg(_(" PluginLoader: Cannot load symbol create_pi: "));
1564 wxLogMessage(msg + plugin_file);
1565 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1566 LoadError le(LoadError::Type::NoCreate, plugin_file.ToStdString());
1567 load_errors.push_back(le);
1568 }
1569 return nullptr;
1570 }
1571
1572 destroy_t* destroy_plugin =
1573 (destroy_t*)pic->m_library.GetSymbol("destroy_pi");
1574 pic->m_destroy_fn = destroy_plugin;
1575 if (nullptr == destroy_plugin) {
1576 wxLogMessage(" PluginLoader: Cannot load symbol destroy_pi: " +
1577 plugin_file);
1578 if (m_blacklist->mark_unloadable(plugin_file.ToStdString())) {
1579 LoadError le(LoadError::Type::NoDestroy, plugin_file.ToStdString());
1580 load_errors.push_back(le);
1581 }
1582 return nullptr;
1583 }
1584
1585 // create an instance of the plugin class
1586 opencpn_plugin* plug_in = create_plugin(this);
1587
1588 int api_major = plug_in->GetAPIVersionMajor();
1589 int api_minor = plug_in->GetAPIVersionMinor();
1590 int api_ver = (api_major * 100) + api_minor;
1591 pic->m_api_version = api_ver;
1592
1593 int pi_major = plug_in->GetPlugInVersionMajor();
1594 int pi_minor = plug_in->GetPlugInVersionMinor();
1595 SemanticVersion pi_ver(pi_major, pi_minor, -1);
1596
1597 wxString pi_name = plug_in->GetCommonName();
1598
1599 wxLogDebug("blacklist: Get status for %s %d %d",
1600 pi_name.ToStdString().c_str(), pi_major, pi_minor);
1601 const auto status =
1602 m_blacklist->get_status(pi_name.ToStdString(), pi_major, pi_minor);
1603 if (status != plug_status::unblocked) {
1604 wxLogDebug("Ignoring blacklisted plugin.");
1605 if (status != plug_status::unloadable) {
1606 SemanticVersion v(pi_major, pi_minor);
1607 LoadError le(LoadError::Type::Blacklisted, pi_name.ToStdString(), v);
1608 load_errors.push_back(le);
1609 }
1610 return nullptr;
1611 }
1612
1613 switch (api_ver) {
1614 case 105:
1615 pic->m_pplugin = dynamic_cast<opencpn_plugin*>(plug_in);
1616 break;
1617
1618 case 106:
1619 pic->m_pplugin = dynamic_cast<opencpn_plugin_16*>(plug_in);
1620 break;
1621
1622 case 107:
1623 pic->m_pplugin = dynamic_cast<opencpn_plugin_17*>(plug_in);
1624 break;
1625
1626 case 108:
1627 pic->m_pplugin = dynamic_cast<opencpn_plugin_18*>(plug_in);
1628 break;
1629
1630 case 109:
1631 pic->m_pplugin = dynamic_cast<opencpn_plugin_19*>(plug_in);
1632 break;
1633
1634 case 110:
1635 pic->m_pplugin = dynamic_cast<opencpn_plugin_110*>(plug_in);
1636 break;
1637
1638 case 111:
1639 pic->m_pplugin = dynamic_cast<opencpn_plugin_111*>(plug_in);
1640 break;
1641
1642 case 112:
1643 pic->m_pplugin = dynamic_cast<opencpn_plugin_112*>(plug_in);
1644 break;
1645
1646 case 113:
1647 pic->m_pplugin = dynamic_cast<opencpn_plugin_113*>(plug_in);
1648 break;
1649
1650 case 114:
1651 pic->m_pplugin = dynamic_cast<opencpn_plugin_114*>(plug_in);
1652 break;
1653
1654 case 115:
1655 pic->m_pplugin = dynamic_cast<opencpn_plugin_115*>(plug_in);
1656 break;
1657
1658 case 116:
1659 pic->m_pplugin = dynamic_cast<opencpn_plugin_116*>(plug_in);
1660 break;
1661
1662 case 117:
1663 pic->m_pplugin = dynamic_cast<opencpn_plugin_117*>(plug_in);
1664 break;
1665
1666 case 118:
1667 pic->m_pplugin = dynamic_cast<opencpn_plugin_118*>(plug_in);
1668 break;
1669
1670 case 119:
1671 pic->m_pplugin = dynamic_cast<opencpn_plugin_119*>(plug_in);
1672 break;
1673
1674 case 120:
1675 pic->m_pplugin = dynamic_cast<opencpn_plugin_120*>(plug_in);
1676 break;
1677
1678 case 121:
1679 pic->m_pplugin = dynamic_cast<opencpn_plugin_121*>(plug_in);
1680 break;
1681
1682 default:
1683 break;
1684 }
1685
1686 if (auto p = dynamic_cast<opencpn_plugin_117*>(plug_in)) {
1687 // For API 1.17+ use the version info in the plugin API in favor of
1688 // the version file created when installing plugin.
1689 pi_ver =
1690 SemanticVersion(pi_major, pi_minor, p->GetPlugInVersionPatch(),
1691 p->GetPlugInVersionPost(), p->GetPlugInVersionPre(),
1692 p->GetPlugInVersionBuild());
1693 }
1694
1695 if (!pic->m_pplugin) {
1696 INFO_LOG << _("Incompatible plugin detected: ") << plugin_file << "\n";
1697 INFO_LOG << _(" API Version detected: ");
1698 INFO_LOG << api_major << "." << api_minor << "\n";
1699 INFO_LOG << _(" PlugIn Version detected: ") << pi_ver << "\n";
1700 if (m_blacklist->mark_unloadable(pi_name.ToStdString(), pi_ver.major,
1701 pi_ver.minor)) {
1702 LoadError le(LoadError::Type::Incompatible, pi_name.ToStdString(),
1703 pi_ver);
1704 load_errors.push_back(le);
1705 }
1706 return nullptr;
1707 }
1708 return pic;
1709}
Plugin catalog management: Build the runtime catalog, handling downloads as required.
Datatypes and methods to parse ocpn-plugins.xml XML data, either complete catalog or a single plugin.
wxString & GetPluginDir()
The original in-tree plugin directory, sometimes not user-writable.
wxString & DefaultPrivateDataDir()
Return dir path for opencpn.log, etc., does not respect -c option.
Wrapper for configuration variables which lives in a wxBaseConfig object.
void Notify() override
Notify all listeners, no data supplied.
Error condition when loading a plugin.
Data for a loaded plugin, including dl-loaded library.
Basic data for a loaded plugin, trivially copyable.
wxString m_plugin_filename
The short file path.
wxString m_plugin_file
The full file path.
int m_cap_flag
PlugIn Capabilities descriptor.
PlugInData(const PluginMetadata &md)
Create a container with applicable fields defined from metadata.
wxString m_common_name
A common name string for the plugin.
bool m_has_setup_options
Has run NotifySetupOptionsPlugin()
std::string Key() const
sort key.
std::string m_manifest_version
As detected from manifest.
wxDateTime m_plugin_modification
used to detect upgraded plugins
wxString m_version_str
Complete version as of semantic_vers.
Handle plugin install from remote repositories and local operations to Uninstall and list plugins.
std::vector< PluginMetadata > getCompatiblePlugins()
Return list of available, unique and compatible plugins from configured XML catalog.
static std::string ImportedMetadataPath(std::string name)
Return path to imported metadata for given plugin.
static std::string VersionPath(std::string name)
Return path to file containing version for given plugin.
static std::string FileListPath(std::string name)
Return path to installation manifest for given plugin.
static PluginHandler * GetInstance()
Singleton factory.
PluginLoader is a backend module without any direct GUI functionality.
bool LoadAllPlugIns(bool enabled_plugins, bool keep_orphans=false)
Update catalog with imported metadata and load all plugin library files.
bool IsPlugInAvailable(const wxString &commonName)
Return true if a plugin with given name exists in GetPlugInArray()
static void MarkAsLoadable(const std::string &library_path)
Mark a library file (complete path) as loadable i.
static PluginMetadata LatestMetadataByName(const std::string &name)
Find highest versioned metadata for given plugin.
EventVar evt_load_plugin
Notified with a PlugInContainer* pointer when a plugin is loaded.
void UpdateManagedPlugins(bool keep_orphans)
Update all managed plugins i.
EventVar evt_pluglist_change
Notified without data when the GetPlugInArray() list is changed.
EventVar evt_plugin_loadall_finalize
Emitted after all plugins are loaded.
static std::string GetPluginVersion(const PlugInData pd, std::function< const PluginMetadata(const std::string &)> get_metadata)
Return version string for a plugin, possibly with an "Imported" suffix.
bool UnLoadPlugIn(size_t ix)
Unload, delete and remove item ix in GetPlugInArray().
void SortPlugins(int(*cmp_func)(PlugInContainer **, PlugInContainer **))
Sort GetPluginArray().
static void UpdatePlugin(PlugInContainer *plugin, const PluginMetadata &md)
Update PlugInContainer status using data from PluginMetadata and manifest.
void SetSetupOptions(const wxString &common_name, bool value)
Update m_has_setup_options state for plugin with given name.
EventVar evt_load_directory
Notified without data when loader starts loading from a new directory.
void SetEnabled(const wxString &common_name, bool enabled)
Update enabled/disabled state for plugin with given name.
void SetToolboxPanel(const wxString &common_name, bool value)
Update m_toolbox_panel state for plugin with given name.
static PluginMetadata MetadataByName(const std::string &name)
Find metadata for given plugin.
bool DeactivatePlugIn(PlugInContainer *pic)
Deactivate given plugin.
bool UnLoadAllPlugIns()
Unload allplugins i.
void RemovePlugin(const PlugInData &pd)
Remove a plugin from *GetPluginArray().
bool UpdatePlugIns()
Update the GetPlugInArray() list by reloading all plugins from disk.
void ShowPreferencesDialog(const PlugInData &pd, wxWindow *parent)
Display the preferences dialog for a plugin.
bool CheckPluginCompatibility(const wxString &plugin_file)
Check plugin compatibiliet w r t library type.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
bool DeactivateAllPlugIns()
Deactivate all plugins.
PlugInContainer * LoadPlugIn(const wxString &plugin_file)
Load given plugin file from disk into GetPlugInArray() list.
EventVar evt_update_chart_types
Notified without data after all plugins loaded ot updated.
std::vector< std::string > Libdirs()
List of directories from which we load plugins.
static PluginPaths * GetInstance()
Return the singleton instance.
std::vector< std::string > Bindirs()
'List of directories for plugin binary helpers.
virtual void OnSetupOptions(void)
Allows plugin to add pages to global Options dialog.
Base class for OpenCPN plugins.
virtual void ShowPreferencesDialog(wxWindow *parent)
Shows the plugin preferences dialog.
virtual wxBitmap * GetPlugInBitmap()
Get the plugin's icon bitmap.
virtual int Init(void)
Initialize the plugin and declare its capabilities.
virtual bool DeInit(void)
Clean up plugin resources.
virtual void SetDefaults(void)
Sets plugin default options.
virtual wxString GetShortDescription()
Get a brief description of the plugin.
virtual wxString GetCommonName()
Get the plugin's common (short) name.
virtual int GetPlugInVersionMajor()
Returns the major version number of the plugin itself.
virtual int GetAPIVersionMinor()
Returns the minor version number of the plugin API that this plugin supports.
virtual int GetAPIVersionMajor()
Returns the major version number of the plugin API that this plugin supports.
virtual wxString GetLongDescription()
Get detailed plugin information.
virtual int GetPlugInVersionMinor()
Returns the minor version number of the plugin itself.
Global variables reflecting command line options and arguments.
Global variables stored in configuration file.
Enhanced logging interface on top of wx/log.h.
std::string tolower(const std::string &input)
Return copy of s with all characters converted to lower case.
bool exists(const std::string &name)
std::string join(std::vector< std::string > v, char c)
Return a single string being the concatenation of all elements in v with character c in between.
void mkdir(const std::string path)
Notify()/Listen() configuration variable wrapper.
#define WANTS_LATE_INIT
Delay full plugin initialization until system is ready.
#define INSTALLS_TOOLBOX_PAGE
Plugin will add pages to the toolbox/settings dialog.
Miscellaneous utilities, many of which string related.
PLugin 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.