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