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