Blame src/contrib/nm-vpn-plugin-utils.c

Packit Service dff8e4
/* SPDX-License-Identifier: LGPL-2.1-or-later */
Packit Service dff8e4
/*
Packit Service dff8e4
 * Copyright (C) 2016, 2018 Red Hat, Inc.
Packit Service dff8e4
 */
Packit Service dff8e4
Packit Service dff8e4
#include "libnm-client-aux-extern/nm-default-client.h"
Packit Service dff8e4
Packit Service dff8e4
#include "nm-vpn-plugin-utils.h"
Packit Service dff8e4
Packit Service dff8e4
#include <dlfcn.h>
Packit Service dff8e4
Packit Service dff8e4
/*****************************************************************************/
Packit Service dff8e4
Packit Service dff8e4
NMVpnEditor *
Packit Service dff8e4
nm_vpn_plugin_utils_load_editor(const char *                  module_name,
Packit Service dff8e4
                                const char *                  factory_name,
Packit Service dff8e4
                                NMVpnPluginUtilsEditorFactory editor_factory,
Packit Service dff8e4
                                NMVpnEditorPlugin *           editor_plugin,
Packit Service dff8e4
                                NMConnection *                connection,
Packit Service dff8e4
                                gpointer                      user_data,
Packit Service dff8e4
                                GError **                     error)
Packit Service dff8e4
Packit Service dff8e4
{
Packit Service dff8e4
    static struct {
Packit Service dff8e4
        gpointer factory;
Packit Service dff8e4
        void *   dl_module;
Packit Service dff8e4
        char *   module_name;
Packit Service dff8e4
        char *   factory_name;
Packit Service dff8e4
    } cached = {0};
Packit Service dff8e4
    NMVpnEditor * editor;
Packit Service dff8e4
    gs_free char *module_path = NULL;
Packit Service dff8e4
    gs_free char *dirname     = NULL;
Packit Service dff8e4
    Dl_info       plugin_info;
Packit Service dff8e4
Packit Service dff8e4
    g_return_val_if_fail(module_name, NULL);
Packit Service dff8e4
    g_return_val_if_fail(factory_name && factory_name[0], NULL);
Packit Service dff8e4
    g_return_val_if_fail(editor_factory, NULL);
Packit Service dff8e4
    g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(editor_plugin), NULL);
Packit Service dff8e4
    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
Packit Service dff8e4
    g_return_val_if_fail(!error || !*error, NULL);
Packit Service dff8e4
Packit Service dff8e4
    if (!g_path_is_absolute(module_name)) {
Packit Service dff8e4
        /*
Packit Service dff8e4
         * Load an editor from the same directory this plugin is in.
Packit Service dff8e4
         * Ideally, we'd get our .so name from the NMVpnEditorPlugin if it
Packit Service dff8e4
         * would just have a property with it...
Packit Service dff8e4
         */
Packit Service dff8e4
        if (!dladdr(nm_vpn_plugin_utils_load_editor, &plugin_info)) {
Packit Service dff8e4
            /* Really a "can not happen" scenario. */
Packit Service dff8e4
            g_set_error(error,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR_FAILED,
Packit Service dff8e4
                        _("unable to get editor plugin name: %s"),
Packit Service dff8e4
                        dlerror());
Packit Service dff8e4
        }
Packit Service dff8e4
Packit Service dff8e4
        dirname     = g_path_get_dirname(plugin_info.dli_fname);
Packit Service dff8e4
        module_path = g_build_filename(dirname, module_name, NULL);
Packit Service dff8e4
    } else {
Packit Service dff8e4
        module_path = g_strdup(module_name);
Packit Service dff8e4
    }
Packit Service dff8e4
Packit Service dff8e4
    /* we really expect this function to be called with unchanging @module_name
Packit Service dff8e4
     * and @factory_name. And we only want to load the module once, hence it would
Packit Service dff8e4
     * be more complicated to accept changing @module_name/@factory_name arguments.
Packit Service dff8e4
     *
Packit Service dff8e4
     * The reason for only loading once is that due to glib types, we cannot create a
Packit Service dff8e4
     * certain type-name more then once, so loading the same module or another version
Packit Service dff8e4
     * of the same module will fail horribly as both try to create a GType with the same
Packit Service dff8e4
     * name.
Packit Service dff8e4
     *
Packit Service dff8e4
     * Only support loading once, any future calls will reuse the handle. To simplify
Packit Service dff8e4
     * that, we enforce that the @factory_name and @module_name is the same. */
Packit Service dff8e4
    if (cached.factory) {
Packit Service dff8e4
        g_return_val_if_fail(cached.dl_module, NULL);
Packit Service dff8e4
        g_return_val_if_fail(cached.factory_name && nm_streq0(cached.factory_name, factory_name),
Packit Service dff8e4
                             NULL);
Packit Service dff8e4
        g_return_val_if_fail(cached.module_name && nm_streq0(cached.module_name, module_name),
Packit Service dff8e4
                             NULL);
Packit Service dff8e4
    } else {
Packit Service dff8e4
        gpointer factory;
Packit Service dff8e4
        void *   dl_module;
Packit Service dff8e4
Packit Service dff8e4
        dl_module = dlopen(module_path, RTLD_LAZY | RTLD_LOCAL);
Packit Service dff8e4
        if (!dl_module) {
Packit Service dff8e4
            if (!g_file_test(module_path, G_FILE_TEST_EXISTS)) {
Packit Service dff8e4
                g_set_error(error,
Packit Service dff8e4
                            G_FILE_ERROR,
Packit Service dff8e4
                            G_FILE_ERROR_NOENT,
Packit Service dff8e4
                            _("missing plugin file \"%s\""),
Packit Service dff8e4
                            module_path);
Packit Service dff8e4
                return NULL;
Packit Service dff8e4
            }
Packit Service dff8e4
            g_set_error(error,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR_FAILED,
Packit Service dff8e4
                        _("cannot load editor plugin: %s"),
Packit Service dff8e4
                        dlerror());
Packit Service dff8e4
            return NULL;
Packit Service dff8e4
        }
Packit Service dff8e4
Packit Service dff8e4
        factory = dlsym(dl_module, factory_name);
Packit Service dff8e4
        if (!factory) {
Packit Service dff8e4
            g_set_error(error,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR,
Packit Service dff8e4
                        NM_VPN_PLUGIN_ERROR_FAILED,
Packit Service dff8e4
                        _("cannot load factory %s from plugin: %s"),
Packit Service dff8e4
                        factory_name,
Packit Service dff8e4
                        dlerror());
Packit Service dff8e4
            dlclose(dl_module);
Packit Service dff8e4
            return NULL;
Packit Service dff8e4
        }
Packit Service dff8e4
Packit Service dff8e4
        /* we cannot ever unload the module because it creates glib types, which
Packit Service dff8e4
         * cannot be unregistered.
Packit Service dff8e4
         *
Packit Service dff8e4
         * Thus we just leak the dl_module handle indefinitely. */
Packit Service dff8e4
        cached.factory      = factory;
Packit Service dff8e4
        cached.dl_module    = dl_module;
Packit Service dff8e4
        cached.module_name  = g_strdup(module_name);
Packit Service dff8e4
        cached.factory_name = g_strdup(factory_name);
Packit Service dff8e4
    }
Packit Service dff8e4
Packit Service dff8e4
    editor = editor_factory(cached.factory, editor_plugin, connection, user_data, error);
Packit Service dff8e4
    if (!editor) {
Packit Service dff8e4
        if (error && !*error) {
Packit Service dff8e4
            g_set_error_literal(error,
Packit Service dff8e4
                                NM_VPN_PLUGIN_ERROR,
Packit Service dff8e4
                                NM_VPN_PLUGIN_ERROR_FAILED,
Packit Service dff8e4
                                _("unknown error creating editor instance"));
Packit Service dff8e4
            g_return_val_if_reached(NULL);
Packit Service dff8e4
        }
Packit Service dff8e4
        return NULL;
Packit Service dff8e4
    }
Packit Service dff8e4
Packit Service dff8e4
    g_return_val_if_fail(NM_IS_VPN_EDITOR(editor), NULL);
Packit Service dff8e4
    return editor;
Packit Service dff8e4
}