Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 : : * Author: Thun Lu <thun.lu@zohomail.cn>
4 : : * Repo: https://github.com/thun-res/vlink
5 : : * _ __ __ _ __
6 : : * | | / / / / (_) ____ / /__
7 : : * | | / / / / / / / __ \ / //_/
8 : : * | |/ / / /___ / / / / / / / ,<
9 : : * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 : : *
11 : : * Licensed under the Apache License, Version 2.0 (the "License");
12 : : * you may not use this file except in compliance with the License.
13 : : * You may obtain a copy of the License at
14 : : *
15 : : * http://www.apache.org/licenses/LICENSE-2.0
16 : : *
17 : : * Unless required by applicable law or agreed to in writing, software
18 : : * distributed under the License is distributed on an "AS IS" BASIS,
19 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 : : * See the License for the specific language governing permissions and
21 : : * limitations under the License.
22 : : */
23 : :
24 : : #include "./base/plugin.h"
25 : :
26 : : #include <algorithm>
27 : : #include <atomic>
28 : : #include <cctype>
29 : : #include <deque>
30 : : #include <filesystem>
31 : : #include <memory>
32 : : #include <mutex>
33 : : #include <shared_mutex>
34 : : #include <stdexcept>
35 : : #include <string>
36 : : #include <string_view>
37 : : #include <unordered_map>
38 : : #include <utility>
39 : :
40 : : #if defined(_WIN32) || defined(_WIN64)
41 : : #ifndef WIN32_LEAN_AND_MEAN
42 : : #define WIN32_LEAN_AND_MEAN
43 : : #define VLINK_PLUGIN_UNDEFINE_LEAN_AND_MEAN
44 : : #endif
45 : : #ifndef NOMINMAX
46 : : #define NOMINMAX
47 : : #define VLINK_PLUGIN_UNDEFINE_NOMINMAX
48 : : #endif
49 : : #include <windows.h>
50 : : #ifdef VLINK_PLUGIN_UNDEFINE_LEAN_AND_MEAN
51 : : #undef WIN32_LEAN_AND_MEAN
52 : : #undef VLINK_PLUGIN_UNDEFINE_LEAN_AND_MEAN
53 : : #endif
54 : : #ifdef VLINK_PLUGIN_UNDEFINE_NOMINMAX
55 : : #undef NOMINMAX
56 : : #undef VLINK_PLUGIN_UNDEFINE_NOMINMAX
57 : : #endif
58 : : #else
59 : : #include <dlfcn.h>
60 : : #endif
61 : :
62 : : #include "./base/helpers.h"
63 : : #include "./base/logger.h"
64 : : #include "./base/utils.h"
65 : :
66 : : namespace vlink {
67 : :
68 : 34 : [[maybe_unused]] static std::string get_current_dir() {
69 : : try {
70 [ + - + - ]: 68 : return std::filesystem::current_path().string();
71 : : } catch (std::filesystem::filesystem_error&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
72 : : return "."; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
73 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
74 : : }
75 : :
76 : 185 : [[maybe_unused]] static bool check_regular_file(const std::string& path) {
77 : : try {
78 [ + - + - ]: 185 : return std::filesystem::is_regular_file(path);
79 : : } catch (std::filesystem::filesystem_error&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
80 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
81 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
82 : : }
83 : :
84 : : // DynamicLibrary
85 : : class DynamicLibrary final {
86 : : public:
87 : : #if defined(_WIN32) || defined(_WIN64)
88 : : static constexpr const char* kFilenamePrefix = "";
89 : : static constexpr const char* kFilenameSuffix = ".dll";
90 : : using NativeHandle = HINSTANCE;
91 : : using NativeSymbol = FARPROC;
92 : : #elif defined(__APPLE__)
93 : : static constexpr const char* kFilenamePrefix = "lib";
94 : : static constexpr const char* kFilenameSuffix = ".dylib";
95 : : using NativeHandle = void*;
96 : : using NativeSymbol = void*;
97 : : #else
98 : : static constexpr const char* kFilenamePrefix = "lib";
99 : : static constexpr const char* kFilenameSuffix = ".so";
100 : : using NativeHandle = void*;
101 : : using NativeSymbol = void*;
102 : : #endif
103 : :
104 : : static_assert(std::is_pointer_v<NativeHandle>, "Expecting native handle to be a pointer.");
105 : : static_assert(std::is_pointer_v<NativeSymbol>, "Expecting native symbol to be a pointer.");
106 : :
107 : : class Exception : public std::runtime_error {
108 : : using std::runtime_error::runtime_error;
109 : : };
110 : :
111 : : class LoadError : public Exception {
112 : : using Exception::Exception;
113 : : };
114 : :
115 : : class SymbolError : public Exception {
116 : : using Exception::Exception;
117 : : };
118 : :
119 : 18 : explicit DynamicLibrary(const std::string& path) {
120 : 18 : handle_ = open_library(path.c_str());
121 : :
122 [ + + ]: 18 : if VUNLIKELY (!handle_) {
123 [ + - + - : 1 : throw LoadError("Could not load library \"" + path + "\"\n" + get_error_description());
+ - + - ]
124 : : }
125 : 17 : }
126 : :
127 : 17 : ~DynamicLibrary() {
128 [ + - ]: 17 : if VLIKELY (handle_) {
129 : 17 : close_library(handle_);
130 : : }
131 : 17 : }
132 : :
133 : : DynamicLibrary(DynamicLibrary&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
134 : :
135 : : DynamicLibrary& operator=(DynamicLibrary&& other) noexcept {
136 : : if VLIKELY (this != &other) {
137 : : std::swap(handle_, other.handle_);
138 : : }
139 : :
140 : : return *this;
141 : : }
142 : :
143 : : template <typename SignatureT>
144 : 32 : SignatureT* get_function(const std::string& name) const {
145 [ - + ]: 32 : if VUNLIKELY (!handle_) {
146 [ # # ]: 0 : throw std::logic_error("The dynamic library handle is null. This object may have been moved from.");
147 : : }
148 : :
149 : 32 : auto* symbol = locate_symbol(handle_, name.c_str());
150 : :
151 [ + + ]: 32 : if VUNLIKELY (!symbol) {
152 [ + - + - : 1 : throw SymbolError("Could not get symbol \"" + name + "\"\n" + get_error_description());
+ - + - ]
153 : : }
154 : :
155 : : #if defined(__GNUC__) && __GNUC__ >= 8
156 : : #pragma GCC diagnostic push
157 : : #pragma GCC diagnostic ignored "-Wcast-function-type"
158 : : #endif
159 : :
160 : 31 : return reinterpret_cast<SignatureT*>(symbol);
161 : :
162 : : #if defined(__GNUC__) && __GNUC__ >= 8
163 : : #pragma GCC diagnostic pop
164 : : #endif
165 : : }
166 : :
167 : : NativeHandle native_handle() const noexcept { return handle_; }
168 : :
169 : : private:
170 : 18 : static NativeHandle open_library(const char* path) noexcept {
171 : : #if defined(_WIN32) || defined(_WIN64)
172 : : return ::LoadLibraryA(path);
173 : : #else
174 : 18 : return ::dlopen(path, RTLD_NOW | RTLD_LOCAL);
175 : : #endif
176 : : }
177 : :
178 : 32 : static NativeSymbol locate_symbol(NativeHandle lib, const char* name) noexcept {
179 : : #if defined(_WIN32) || defined(_WIN64)
180 : : return ::GetProcAddress(lib, name);
181 : : #else
182 : 32 : return ::dlsym(lib, name);
183 : : #endif
184 : : }
185 : :
186 : 17 : static void close_library(NativeHandle lib) noexcept {
187 : : #if defined(_WIN32) || defined(_WIN64)
188 : : ::FreeLibrary(lib);
189 : : #else
190 : 17 : ::dlclose(lib);
191 : : #endif
192 : 17 : }
193 : :
194 : 2 : static std::string get_error_description() noexcept {
195 : : #if defined(_WIN32) || defined(_WIN64)
196 : : static constexpr size_t kBufferSize = 512;
197 : : const auto error_code = ::GetLastError();
198 : :
199 : : if (!error_code) {
200 : : return "No error reported by GetLastError";
201 : : }
202 : :
203 : : char description[kBufferSize];
204 : :
205 : : const auto language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
206 : : const DWORD length =
207 : : ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error_code, language, description, kBufferSize, nullptr);
208 : :
209 : : return (length == 0) ? "Unknown error (FormatMessage failed)" : description;
210 : : #else
211 : 2 : const auto* description = ::dlerror();
212 : :
213 [ + - ]: 2 : return (description == nullptr) ? "No error reported by dlerror" : description;
214 : : #endif
215 : : }
216 : :
217 : : NativeHandle handle_{nullptr};
218 : :
219 : : VLINK_DISALLOW_COPY_AND_ASSIGN(DynamicLibrary)
220 : : };
221 : :
222 : : // PluginEntry
223 : : struct PluginEntry final {
224 : : std::unique_ptr<DynamicLibrary> loader;
225 : : std::string plugin_complex_id;
226 : : Logger::Level log_level{Logger::kTrace};
227 : : };
228 : :
229 : : // Plugin::Impl
230 : : struct Plugin::Impl final {
231 : : std::atomic<Logger::Level> log_level{Logger::kTrace};
232 : : std::shared_mutex mtx;
233 : : std::unordered_map<std::string, std::shared_ptr<PluginEntry>> plugin_map;
234 : : };
235 : :
236 : : // Plugin
237 : 217 : Plugin::Plugin() : impl_(std::make_unique<Impl>()) {}
238 : :
239 : 217 : Plugin::~Plugin() = default;
240 : :
241 : 5 : void Plugin::set_log_level(Logger::Level level) { impl_->log_level.store(level, std::memory_order_release); }
242 : :
243 : 2 : Logger::Level Plugin::get_log_level() const { return impl_->log_level.load(std::memory_order_acquire); }
244 : :
245 : 34 : std::deque<std::string> Plugin::default_search_path() {
246 [ + - ]: 34 : const std::string current_dir = get_current_dir();
247 [ + - + - ]: 68 : const std::string plugin_dir_env = Utils::get_env("VLINK_PLUGIN_DIR");
248 : 34 : const std::string local_app_dir = Utils::get_app_dir();
249 : :
250 : : std::deque<std::string> search_path{
251 : : current_dir,
252 : : local_app_dir,
253 : : local_app_dir + "/../lib64",
254 : : local_app_dir + "/../lib",
255 : : local_app_dir + "/lib64",
256 : : local_app_dir + "/lib",
257 : : "/lib64", // LCOV_EXCL_LINE GCOVR_EXCL_LINE
258 : : "/lib",
259 [ + - + - : 374 : };
+ - + - +
- + - + -
+ - + - ]
260 : :
261 : 34 : const auto plugin_dirs = Helpers::split_any(plugin_dir_env);
262 : :
263 [ + + ]: 34 : if (!plugin_dirs.empty()) {
264 [ + - ]: 1 : search_path.insert(search_path.begin(), plugin_dirs.begin(), plugin_dirs.end());
265 : : }
266 : :
267 : 68 : return search_path;
268 : 34 : }
269 : :
270 : 82 : void Plugin::clear() {
271 [ + - ]: 82 : std::lock_guard lock(impl_->mtx);
272 : 82 : impl_->plugin_map.clear();
273 : 82 : }
274 : :
275 : 25 : bool Plugin::process_plugin_internal(const std::string& lib_name, const std::string& local_plugin_id,
276 : : uint16_t local_version_major, uint16_t local_version_minor,
277 : : const std::string& target_plugin_id, uint16_t target_version_major,
278 : : uint16_t target_version_minor, uint8_t log_level) {
279 [ + + ]: 25 : if (log_level <= Logger::kInfo) {
280 [ + - + - : 30 : VLOG_I("Plugin: ", lib_name, "@", local_plugin_id, "#", local_version_major, ".", local_version_minor, ".");
+ - ]
281 : : }
282 : :
283 [ + + + + : 25 : if VUNLIKELY (target_plugin_id.empty() || target_plugin_id != local_plugin_id) {
+ + ]
284 [ + - ]: 3 : if (log_level <= Logger::kError) {
285 [ + - + - : 6 : VLOG_E("Plugin: Plugin id mismatch: expected '", local_plugin_id, "', got '", target_plugin_id, "'.");
+ - ]
286 : : }
287 : :
288 : 3 : return false;
289 : : }
290 : :
291 [ + + + + : 22 : if VUNLIKELY (target_version_major != local_version_major || target_version_minor > local_version_minor) {
+ + ]
292 [ + - ]: 2 : if (log_level <= Logger::kError) {
293 [ + - + - : 4 : VLOG_E("Plugin: Version mismatch: local ", local_version_major, ".", local_version_minor, ", required ",
+ - ]
294 : : target_version_major, ".", target_version_minor, ".");
295 : : }
296 : :
297 : 2 : return false;
298 : : }
299 : :
300 : 20 : return true;
301 : : }
302 : :
303 : 40 : Plugin::Handle Plugin::load_and_create(const std::string& plugin_id, const std::string& lib_name,
304 : : uint16_t version_major, uint16_t version_minor, const std::string& dir_name,
305 : : const std::deque<std::string>& search_paths, const std::string& function_name,
306 : : std::shared_ptr<PluginEntry>* plugin_entry) {
307 : 40 : const auto log_level = impl_->log_level.load(std::memory_order_acquire);
308 : :
309 [ - + ]: 40 : if VUNLIKELY (plugin_id.empty()) {
310 : : if (log_level <= Logger::kError) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
311 : : VLOG_E("Plugin: Plugin id is empty."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
312 : : }
313 : :
314 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
315 : : }
316 : :
317 [ + + ]: 40 : if VUNLIKELY (lib_name.empty()) {
318 [ + - ]: 1 : if (log_level <= Logger::kError) {
319 [ + - + - : 2 : VLOG_E("Plugin: Lib name is empty.");
+ - ]
320 : : }
321 : :
322 : 1 : return nullptr;
323 : : }
324 : :
325 [ + - + - ]: 39 : std::string plugin_complex_id = lib_name + "@" + plugin_id;
326 : :
327 : : {
328 [ + - ]: 39 : std::unique_lock load_lock(impl_->mtx);
329 : :
330 [ + - + + ]: 39 : if VUNLIKELY (impl_->plugin_map.count(plugin_complex_id) != 0U) {
331 [ + - ]: 1 : if (log_level <= Logger::kError) {
332 [ + - + - : 2 : VLOG_E("Plugin: Already loaded (", plugin_complex_id, ").");
+ - ]
333 : : }
334 : :
335 : 1 : return nullptr;
336 : : }
337 [ + + ]: 39 : }
338 : :
339 : 38 : std::string plugin_path;
340 : :
341 : : {
342 : 38 : bool has_filename_suffix = vlink::Helpers::has_endwith(lib_name, DynamicLibrary::kFilenameSuffix);
343 : : #if defined(_WIN32) || defined(_WIN64)
344 : : const std::string_view filename_suffix = DynamicLibrary::kFilenameSuffix;
345 : :
346 : : if (!has_filename_suffix && lib_name.size() >= filename_suffix.size()) {
347 : : has_filename_suffix =
348 : : std::equal(filename_suffix.begin(), filename_suffix.end(), lib_name.end() - filename_suffix.size(),
349 : : [](unsigned char lhs, unsigned char rhs) { return std::tolower(lhs) == std::tolower(rhs); });
350 : : }
351 : : #endif
352 : 38 : const bool has_path_separator = lib_name.find_first_of("/\\") != std::string::npos;
353 [ + + - + ]: 38 : const bool path_like = has_path_separator || has_filename_suffix;
354 : :
355 [ + + ]: 38 : if (path_like) {
356 [ + - - + ]: 3 : if (check_regular_file(lib_name)) {
357 [ # # # # : 0 : plugin_path = has_path_separator ? lib_name : get_current_dir() + "/" + lib_name;
# # # # #
# # # # #
# # # # #
# ]
358 : : }
359 : : } else {
360 [ + - + - ]: 35 : std::string plugin_name = DynamicLibrary::kFilenamePrefix + lib_name + DynamicLibrary::kFilenameSuffix;
361 : 35 : std::string check_path;
362 : :
363 [ + + ]: 199 : for (const auto& path : search_paths) {
364 [ + - ]: 182 : check_path = path;
365 [ + - ]: 182 : check_path += '/';
366 : :
367 [ + + ]: 182 : if (!dir_name.empty()) {
368 [ + - ]: 1 : check_path += dir_name;
369 [ + - ]: 1 : check_path += '/';
370 : : }
371 : :
372 [ + - ]: 182 : check_path += plugin_name;
373 : :
374 [ + - + + ]: 182 : if (check_regular_file(check_path)) {
375 : 18 : plugin_path = std::move(check_path);
376 : 18 : break;
377 : : }
378 : : }
379 : 35 : }
380 : : }
381 : :
382 : 38 : Handle handle = nullptr;
383 : :
384 [ + + ]: 38 : if VUNLIKELY (plugin_path.empty()) {
385 [ + + ]: 20 : if (log_level <= Logger::kError) {
386 [ + - + - : 38 : VLOG_E("Plugin: Cannot find plugin (", plugin_complex_id, ").");
+ - ]
387 : : }
388 : :
389 : 20 : return handle;
390 : : }
391 : :
392 [ + + ]: 18 : if (log_level <= Logger::kTrace) {
393 [ - + - - : 16 : VLOG_T("Plugin: Loading plugin: ", plugin_path, ".");
- - ]
394 : : }
395 : :
396 : : try {
397 [ + - ]: 18 : auto entry = std::make_shared<PluginEntry>();
398 [ + + ]: 18 : entry->loader = std::make_unique<DynamicLibrary>(std::move(plugin_path));
399 [ + - ]: 17 : entry->plugin_complex_id = plugin_complex_id;
400 : 17 : entry->log_level = log_level;
401 : :
402 : : auto create_function =
403 [ + + ]: 17 : entry->loader->get_function<Handle(const char*, const char*, uint16_t, uint16_t, uint8_t)>(function_name);
404 : :
405 [ - + ]: 16 : if VUNLIKELY (!create_function) {
406 : : // LCOV_EXCL_START GCOVR_EXCL_START
407 : : if (log_level <= Logger::kError) {
408 : : VLOG_E("Plugin: Cannot find symbol function to create (", plugin_complex_id, ").");
409 : : }
410 : :
411 : : return handle;
412 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
413 : : }
414 : :
415 [ + - ]: 16 : handle = create_function(lib_name.c_str(), plugin_id.c_str(), version_major, version_minor, log_level);
416 : :
417 [ + + ]: 16 : if VUNLIKELY (!handle) {
418 [ + - ]: 1 : if (log_level <= Logger::kError) {
419 [ + - + - : 2 : VLOG_E("Plugin: Failed to create handle (", plugin_complex_id, ").");
+ - ]
420 : : }
421 : :
422 : 1 : return handle;
423 : : }
424 : :
425 [ + + ]: 15 : if (log_level <= Logger::kTrace) {
426 [ - + - - : 13 : VLOG_T("Plugin: Loaded successfully (", plugin_complex_id, ").");
- - ]
427 : : }
428 : :
429 : 15 : bool inserted = false;
430 : :
431 : : {
432 [ + - ]: 15 : std::unique_lock load_lock(impl_->mtx);
433 [ + - ]: 15 : auto [iter, emplaced] = impl_->plugin_map.emplace(plugin_complex_id, entry);
434 : : (void)iter;
435 : 15 : inserted = emplaced;
436 : 15 : }
437 : :
438 [ - + ]: 15 : if VUNLIKELY (!inserted) {
439 : : destroy(entry, handle); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
440 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
441 : : }
442 : :
443 [ + - ]: 15 : if (plugin_entry) {
444 : 15 : *plugin_entry = entry;
445 : : }
446 : :
447 : 15 : return handle;
448 [ - + ]: 20 : } catch (const DynamicLibrary::Exception& e) {
449 [ + - ]: 2 : if (log_level <= Logger::kError) {
450 [ + - + - : 4 : VLOG_E("Plugin: Failed to load plugin (", plugin_complex_id, "): ", e.what(), ".");
+ - ]
451 : : }
452 : :
453 : 2 : return handle;
454 : 2 : }
455 : 39 : }
456 : :
457 : 4 : bool Plugin::unload(const std::string& plugin_complex_id) {
458 : 4 : const auto log_level = impl_->log_level.load(std::memory_order_acquire);
459 : :
460 [ - + ]: 4 : if VUNLIKELY (plugin_complex_id.empty()) {
461 : : if (log_level <= Logger::kError) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
462 : : VLOG_E("Plugin: Plugin id is empty."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
463 : : }
464 : :
465 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
466 : : }
467 : :
468 : 4 : bool erased = false;
469 : :
470 : : {
471 [ + - ]: 4 : std::lock_guard lock(impl_->mtx);
472 [ + - ]: 4 : erased = impl_->plugin_map.erase(plugin_complex_id) != 0U;
473 : 4 : }
474 : :
475 [ + + ]: 4 : if VUNLIKELY (!erased) {
476 [ + + ]: 2 : if (log_level <= Logger::kError) {
477 [ + - + - : 2 : VLOG_E("Plugin: Not loaded (", plugin_complex_id, ").");
+ - ]
478 : : }
479 : :
480 : 2 : return false;
481 : : }
482 : :
483 : 2 : return true;
484 : : }
485 : :
486 : 6 : bool Plugin::has_loaded(const std::string& plugin_complex_id) {
487 [ + - ]: 6 : std::shared_lock lock(impl_->mtx);
488 [ + - ]: 12 : return impl_->plugin_map.count(plugin_complex_id) != 0;
489 : 6 : }
490 : :
491 : 15 : bool Plugin::destroy(std::shared_ptr<PluginEntry> plugin_entry, Handle handle, const std::string& function_name) {
492 [ + - - + : 15 : if VUNLIKELY (!plugin_entry || !plugin_entry->loader || !handle) {
+ - - + -
+ ]
493 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
494 : : }
495 : :
496 : : try {
497 [ + - ]: 15 : auto destroy_function = plugin_entry->loader->get_function<bool(Handle)>(function_name);
498 : :
499 [ - + ]: 15 : if VUNLIKELY (!destroy_function) {
500 : : // LCOV_EXCL_START GCOVR_EXCL_START
501 : : if (plugin_entry->log_level <= Logger::kError) {
502 : : VLOG_E("Plugin: Cannot find symbol function to destroy (", plugin_entry->plugin_complex_id, ").");
503 : : }
504 : :
505 : : return false;
506 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
507 : : }
508 : :
509 [ + - - + ]: 15 : if VUNLIKELY (!destroy_function(handle)) {
510 : : // LCOV_EXCL_START GCOVR_EXCL_START
511 : : if (plugin_entry->log_level <= Logger::kError) {
512 : : VLOG_E("Plugin: Failed to destroy handle (", plugin_entry->plugin_complex_id, ").");
513 : : }
514 : :
515 : : return false;
516 : : }
517 : : } catch (const std::exception& e) {
518 : : if (plugin_entry->log_level <= Logger::kError) {
519 : : VLOG_E("Plugin: Failed to destroy handle (", plugin_entry->plugin_complex_id, "): ", e.what(), ".");
520 : : }
521 : :
522 : : return false;
523 : : } catch (...) {
524 : : if (plugin_entry->log_level <= Logger::kError) {
525 : : VLOG_E("Plugin: Failed to destroy handle (", plugin_entry->plugin_complex_id, "): non-std exception.");
526 : : }
527 : :
528 : : return false;
529 : : }
530 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
531 : :
532 : 15 : return true;
533 : : }
534 : :
535 : : } // namespace vlink
|