Skip to content

Commit

Permalink
Plugin API: *_plugin_get_last_exception: Return pointer to last excep…
Browse files Browse the repository at this point in the history
…tion

To prevent function names from being mangled in the plugin API, these functions
are linked as C language functions (extern "C"). We do not throw exceptions from
these functions. Specifically, the `libdnf_plugin_new_instance` and
`dnf5_plugin_new_instance` functions return a null pointer on failure and
discard the exception. Thus, the caller (libdnf library or dnf5 application)
does not know the reason for the failure.

This modification extends the libdnf and dnf5 plugin library APIs with new
`libdnf_plugin_get_last_exception` and `dnf5_plugin_get_last_exception`
functions. The plugin can save the exception instead of discarding it. The new
functions then allow access to the saved exception.

Documentation string were improved.
  • Loading branch information
jrohel committed Feb 10, 2025
1 parent 7558a72 commit f2a4668
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 10 deletions.
1 change: 1 addition & 0 deletions bindings/libdnf5/plugin.i
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
%ignore libdnf_plugin_get_version;
%ignore libdnf_plugin_new_instance;
%ignore libdnf_plugin_delete_instance;
%ignore libdnf_plugin_get_last_exception;
%feature("director") IPlugin;
%include "libdnf5/plugin/iplugin.hpp"

Expand Down
28 changes: 24 additions & 4 deletions dnf5/include/dnf5/iplugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "defs.h"

#include <cstdint>
#include <exception>
#include <vector>

namespace dnf5 {
Expand Down Expand Up @@ -82,24 +83,43 @@ class DNF_PLUGIN_API IPlugin {

extern "C" {

/// Returns the version of the API required by the plugin.
/// Returns the version of the API implemented by the plugin.
/// Same result as IPlugin::get_api_version(), but can be called without creating an IPlugin instance.
///
/// @return API version implemented by the plugin.
DNF_PLUGIN_API dnf5::PluginAPIVersion dnf5_plugin_get_api_version(void);

/// Returns the name of the plugin. It can be called at any time.
/// Returns the name of the plugin.
/// Same result as IPlugin::get_name(), but can be called without creating an IPlugin instance.
///
/// @return Plugin name
DNF_PLUGIN_API const char * dnf5_plugin_get_name(void);

/// Returns the version of the plugin. It can be called at any time.
/// Returns the version of the plugin.
/// Same result as IPlugin::get_version(), but can be called without creating an IPlugin instance.
///
/// @return Plugin version
DNF_PLUGIN_API dnf5::PluginVersion dnf5_plugin_get_version(void);

/// Creates a new plugin instance. Passes the API version to the plugin.
/// Creates a new plugin instance.
/// On failure, returns `nullptr` and saves the exception.
///
/// @param aplication_version Version of dnf application.
/// @param context Reference to the application context.
/// @return Pointer to the new plugin instance or `nullptr`.
DNF_PLUGIN_API dnf5::IPlugin * dnf5_plugin_new_instance(
dnf5::ApplicationVersion application_version, dnf5::Context & context);

/// Deletes plugin instance.
///
/// @param plugin_instance Plugin instance to delete.
DNF_PLUGIN_API void dnf5_plugin_delete_instance(dnf5::IPlugin * plugin_instance);

/// Returns a pointer to `std::exception_ptr` containing the last caught exception.
/// If no exception has occurred yet, returns a pointer to an empty `std::exception_ptr`.
///
/// @return Pointer to the `std::exception_ptr` containing the last caught exception.
DNF_PLUGIN_API std::exception_ptr * dnf5_plugin_get_last_exception(void);
}

#endif
29 changes: 28 additions & 1 deletion dnf5/plugins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ class PluginLibrary : public Plugin {
using TGetVersionFunc = decltype(&dnf5_plugin_get_version);
using TNewInstanceFunc = decltype(&dnf5_plugin_new_instance);
using TDeleteInstanceFunc = decltype(&dnf5_plugin_delete_instance);
using TGetLastException = decltype(&dnf5_plugin_get_last_exception);
TGetApiVersionFunc get_api_version{nullptr};
TGetNameFunc get_name{nullptr};
TGetVersionFunc get_version{nullptr};
TNewInstanceFunc new_instance{nullptr};
TDeleteInstanceFunc delete_instance{nullptr};
TGetLastException get_last_exception{nullptr};
utils::Library library;
};

Expand All @@ -73,9 +75,34 @@ PluginLibrary::PluginLibrary(Context & context, const std::string & library_path
new_instance = reinterpret_cast<TNewInstanceFunc>(library.get_address("dnf5_plugin_new_instance"));
delete_instance = reinterpret_cast<TDeleteInstanceFunc>(library.get_address("dnf5_plugin_delete_instance"));

try {
get_last_exception = reinterpret_cast<TGetLastException>(library.get_address("dnf5_plugin_get_last_exception"));
} catch (const std::runtime_error &) {
// The original plugin API did not have the "dnf5_plugin_get_last_exception" function.
// To maintain compatibility with older plugins, the "dnf5_plugin_get_last_exception" function is optional.
}

iplugin_instance = new_instance(dnf5::get_application_version(), context);
if (!iplugin_instance) {
throw std::runtime_error("Failed to create a dnf plugin instance");
auto & logger = *context.get_base().get_logger();
std::runtime_error plugin_exception("Failed to create a dnf plugin instance");
if (get_last_exception) {
if (auto * last_exception = get_last_exception()) {
try {
if (*last_exception) {
std::rethrow_exception(*last_exception);
}
} catch (const std::exception & ex) {
*last_exception = nullptr; // We no longer need to save the exception in the plugin.
logger.error("dnf5_plugin_new_instance: {}", ex.what());
std::throw_with_nested(std::move(plugin_exception));
} catch (...) {
*last_exception = nullptr; // We no longer need to save the exception in the plugin.
std::throw_with_nested(std::move(plugin_exception));
}
}
}
throw std::move(plugin_exception);
}
}

Expand Down
29 changes: 25 additions & 4 deletions include/libdnf5/plugin/iplugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "libdnf5/defs.h"
#include "libdnf5/version.hpp"

#include <exception>
#include <string>
#include <vector>

Expand Down Expand Up @@ -142,24 +143,44 @@ class LIBDNF_PLUGIN_API IPlugin2_1 : public IPlugin {

extern "C" {

/// Returns the version of the API supported by the plugin.
/// Returns the version of the API implemented by the plugin.
/// Same result as IPlugin::get_api_version(), but can be called without creating an IPlugin instance.
///
/// @return API version implemented by the plugin.
LIBDNF_PLUGIN_API libdnf5::PluginAPIVersion libdnf_plugin_get_api_version(void);

/// Returns the name of the plugin. It can be called at any time.
/// Returns the name of the plugin.
/// Same result as IPlugin::get_name(), but can be called without creating an IPlugin instance.
///
/// @return Plugin name
LIBDNF_PLUGIN_API const char * libdnf_plugin_get_name(void);

/// Returns the version of the plugin. It can be called at any time.
/// Returns the version of the plugin.
/// Same result as IPlugin::get_version(), but can be called without creating an IPlugin instance.
///
/// @return Plugin version
LIBDNF_PLUGIN_API libdnf5::plugin::Version libdnf_plugin_get_version(void);

/// Creates a new plugin instance. Passes the API version to the plugin.
/// Creates a new plugin instance.
/// On failure, returns `nullptr` and saves the exception.
///
/// @param library_version Version of libdnf library.
/// @param data Private libdnf data passed to the plugin.
/// @param parser Parser with loaded plugin configuration file.
/// @return Pointer to the new plugin instance or `nullptr`.
LIBDNF_PLUGIN_API libdnf5::plugin::IPlugin * libdnf_plugin_new_instance(
libdnf5::LibraryVersion library_version, libdnf5::plugin::IPluginData & data, libdnf5::ConfigParser & parser);

/// Deletes plugin instance.
///
/// @param plugin_instance Plugin instance to delete.
LIBDNF_PLUGIN_API void libdnf_plugin_delete_instance(libdnf5::plugin::IPlugin * plugin_instance);

/// Returns a pointer to `std::exception_ptr` containing the last caught exception.
/// If no exception has occurred yet, returns a pointer to an empty `std::exception_ptr`.
///
/// @return Pointer to the `std::exception_ptr` containing the last caught exception.
LIBDNF_PLUGIN_API std::exception_ptr * libdnf_plugin_get_last_exception(void);
}

#endif
31 changes: 30 additions & 1 deletion libdnf5/plugin/plugins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ class PluginLibrary : public Plugin {
using TGetVersionFunc = decltype(&libdnf_plugin_get_version);
using TNewInstanceFunc = decltype(&libdnf_plugin_new_instance);
using TDeleteInstanceFunc = decltype(&libdnf_plugin_delete_instance);
using TGetLastException = decltype(&libdnf_plugin_get_last_exception);
TGetApiVersionFunc get_api_version{nullptr};
TGetNameFunc get_name{nullptr};
TGetVersionFunc get_version{nullptr};
TNewInstanceFunc new_instance{nullptr};
TDeleteInstanceFunc delete_instance{nullptr};
TGetLastException get_last_exception{nullptr};
utils::Library library;
};

Expand All @@ -75,9 +77,36 @@ PluginLibrary::PluginLibrary(Base & base, ConfigParser && parser, const std::str
get_version = reinterpret_cast<TGetVersionFunc>(library.get_address("libdnf_plugin_get_version"));
new_instance = reinterpret_cast<TNewInstanceFunc>(library.get_address("libdnf_plugin_new_instance"));
delete_instance = reinterpret_cast<TDeleteInstanceFunc>(library.get_address("libdnf_plugin_delete_instance"));

try {
get_last_exception =
reinterpret_cast<TGetLastException>(library.get_address("libdnf_plugin_get_last_exception"));
} catch (const utils::LibraryError &) {
// The original plugin API did not have the "libdnf_plugin_get_last_exception" function.
// To maintain compatibility with older plugins, the "libdnf_plugin_get_last_exception" function is optional.
}

iplugin_instance = new_instance(libdnf5::get_library_version(), get_iplugin_data(base), get_config_parser());
if (!iplugin_instance) {
throw PluginError(M_("Failed to create a libdnf plugin instance"));
auto & logger = *base.get_logger();
PluginError plugin_exception(M_("Failed to create a libdnf plugin instance"));
if (get_last_exception) {
if (auto * last_exception = get_last_exception()) {
try {
if (*last_exception) {
std::rethrow_exception(*last_exception);
}
} catch (const std::exception & ex) {
*last_exception = nullptr; // We no longer need to save the exception in the plugin.
logger.error("libdnf_plugin_new_instance: {}", ex.what());
std::throw_with_nested(std::move(plugin_exception));
} catch (...) {
*last_exception = nullptr; // We no longer need to save the exception in the plugin.
std::throw_with_nested(std::move(plugin_exception));
}
}
}
throw std::move(plugin_exception);
}
}

Expand Down

0 comments on commit f2a4668

Please sign in to comment.