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