/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikamodule.c * (C) 1999 Austin Donnelly * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "pikamodule.h" #include "libpika/libpika-intl.h" /** * SECTION: pikamodule * @title: PikaModule * @short_description: A #GTypeModule subclass which implements module * loading using #GModule. * @see_also: #GModule, #GTypeModule * * #PikaModule is a generic mechanism to dynamically load modules into * PIKA. It is a #GTypeModule subclass, implementing module loading * using #GModule. #PikaModule does not know which functionality is * implemented by the modules, it just provides a framework to get * arbitrary #GType implementations loaded from disk. **/ enum { PROP_0, PROP_AUTO_LOAD, PROP_ON_DISK, N_PROPS }; static GParamSpec *obj_props[N_PROPS] = { NULL, }; struct _PikaModulePrivate { GFile *file; /* path to the module */ gboolean auto_load; /* auto-load the module on creation */ gboolean verbose; /* verbose error reporting */ PikaModuleInfo *info; /* returned values from module_query */ PikaModuleState state; /* what's happened to the module */ gchar *last_error; gboolean on_disk; /* TRUE if file still exists */ GModule *module; /* handle on the module */ PikaModuleQueryFunc query_module; PikaModuleRegisterFunc register_module; }; static void pika_module_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_module_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_module_finalize (GObject *object); static gboolean pika_module_load (GTypeModule *module); static void pika_module_unload (GTypeModule *module); static gboolean pika_module_open (PikaModule *module); static gboolean pika_module_close (PikaModule *module); static void pika_module_set_last_error (PikaModule *module, const gchar *error_str); static PikaModuleInfo * pika_module_info_new (guint32 abi_version, const gchar *purpose, const gchar *author, const gchar *version, const gchar *copyright, const gchar *date); static PikaModuleInfo * pika_module_info_copy (const PikaModuleInfo *info); static void pika_module_info_free (PikaModuleInfo *info); G_DEFINE_TYPE_WITH_PRIVATE (PikaModule, pika_module, G_TYPE_TYPE_MODULE) #define parent_class pika_module_parent_class static void pika_module_class_init (PikaModuleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS (klass); object_class->set_property = pika_module_set_property; object_class->get_property = pika_module_get_property; object_class->finalize = pika_module_finalize; obj_props[PROP_AUTO_LOAD] = g_param_spec_boolean ("auto-load", "auto-load", "auto-load", FALSE, PIKA_PARAM_READWRITE); obj_props[PROP_ON_DISK] = g_param_spec_boolean ("on-disk", "on-disk", "on-disk", FALSE, PIKA_PARAM_READABLE); g_object_class_install_properties (object_class, N_PROPS, obj_props); module_class->load = pika_module_load; module_class->unload = pika_module_unload; } static void pika_module_init (PikaModule *module) { module->priv = pika_module_get_instance_private (module); module->priv->state = PIKA_MODULE_STATE_ERROR; } static void pika_module_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaModule *module = PIKA_MODULE (object); switch (property_id) { case PROP_AUTO_LOAD: pika_module_set_auto_load (module, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_module_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaModule *module = PIKA_MODULE (object); switch (property_id) { case PROP_AUTO_LOAD: g_value_set_boolean (value, pika_module_get_auto_load (module)); break; case PROP_ON_DISK: g_value_set_boolean (value, pika_module_is_on_disk (module)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_module_finalize (GObject *object) { PikaModule *module = PIKA_MODULE (object); g_clear_object (&module->priv->file); g_clear_pointer (&module->priv->info, pika_module_info_free); g_clear_pointer (&module->priv->last_error, g_free); G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean pika_module_load (GTypeModule *module) { PikaModule *pika_module = PIKA_MODULE (module); gpointer func; g_return_val_if_fail (pika_module->priv->file != NULL, FALSE); g_return_val_if_fail (pika_module->priv->module == NULL, FALSE); if (pika_module->priv->verbose) g_print ("Loading module '%s'\n", pika_file_get_utf8_name (pika_module->priv->file)); if (! pika_module_open (pika_module)) return FALSE; if (! pika_module_query_module (pika_module)) return FALSE; /* find the pika_module_register symbol */ if (! g_module_symbol (pika_module->priv->module, "pika_module_register", &func)) { pika_module_set_last_error (pika_module, "Missing pika_module_register() symbol"); g_message (_("Module '%s' load error: %s"), pika_file_get_utf8_name (pika_module->priv->file), pika_module->priv->last_error); pika_module_close (pika_module); pika_module->priv->state = PIKA_MODULE_STATE_ERROR; return FALSE; } pika_module->priv->register_module = func; if (! pika_module->priv->register_module (module)) { pika_module_set_last_error (pika_module, "pika_module_register() returned FALSE"); g_message (_("Module '%s' load error: %s"), pika_file_get_utf8_name (pika_module->priv->file), pika_module->priv->last_error); pika_module_close (pika_module); pika_module->priv->state = PIKA_MODULE_STATE_LOAD_FAILED; return FALSE; } pika_module->priv->state = PIKA_MODULE_STATE_LOADED; return TRUE; } static void pika_module_unload (GTypeModule *module) { PikaModule *pika_module = PIKA_MODULE (module); g_return_if_fail (pika_module->priv->module != NULL); if (pika_module->priv->verbose) g_print ("Unloading module '%s'\n", pika_file_get_utf8_name (pika_module->priv->file)); pika_module_close (pika_module); } /* public functions */ /** * pika_module_new: * @file: A #GFile pointing to a loadable module. * @auto_load: Pass %TRUE to exclude this module from auto-loading. * @verbose: Pass %TRUE to enable debugging output. * * Creates a new #PikaModule instance. * * Returns: The new #PikaModule object. **/ PikaModule * pika_module_new (GFile *file, gboolean auto_load, gboolean verbose) { PikaModule *module; g_return_val_if_fail (G_IS_FILE (file), NULL); g_return_val_if_fail (g_file_is_native (file), NULL); module = g_object_new (PIKA_TYPE_MODULE, NULL); module->priv->file = g_object_ref (file); module->priv->auto_load = auto_load ? TRUE : FALSE; module->priv->verbose = verbose ? TRUE : FALSE; if (module->priv->auto_load) { if (pika_module_load (G_TYPE_MODULE (module))) pika_module_unload (G_TYPE_MODULE (module)); } else { if (verbose) g_print ("Skipping module '%s'\n", pika_file_get_utf8_name (file)); module->priv->state = PIKA_MODULE_STATE_NOT_LOADED; } return module; } /** * pika_module_get_file: * @module: A #PikaModule * * Returns #GFile of the @module, * * Returns: (transfer none): The @module's #GFile. * * Since: 3.0 **/ GFile * pika_module_get_file (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), NULL); return module->priv->file; } /** * pika_module_set_auto_load: * @module: A #PikaModule * @auto_load: Pass %FALSE to exclude this module from auto-loading * * Sets the @auto_load property of the module * * Since: 3.0 **/ void pika_module_set_auto_load (PikaModule *module, gboolean auto_load) { g_return_if_fail (PIKA_IS_MODULE (module)); if (auto_load != module->priv->auto_load) { module->priv->auto_load = auto_load; g_object_notify_by_pspec (G_OBJECT (module), obj_props[PROP_AUTO_LOAD]); } } /** * pika_module_get_auto_load: * @module: A #PikaModule * * Returns whether this @module in automatically loaded at startup. * * Returns: The @module's 'auto_load' property. * * Since: 3.0 **/ gboolean pika_module_get_auto_load (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), FALSE); return module->priv->auto_load; } /** * pika_module_is_on_disk: * @module: A #PikaModule * * Returns: Whether the @module is present on diak. * * Since: 3.0 **/ gboolean pika_module_is_on_disk (PikaModule *module) { gboolean old_on_disk; g_return_val_if_fail (PIKA_IS_MODULE (module), FALSE); old_on_disk = module->priv->on_disk; module->priv->on_disk = (g_file_query_file_type (module->priv->file, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_REGULAR); if (module->priv->on_disk != old_on_disk) g_object_notify_by_pspec (G_OBJECT (module), obj_props[PROP_ON_DISK]); return module->priv->on_disk; } /** * pika_module_is_loaded: * @module: A #PikaModule * * Returns: Whether the @module is currently loaded. * * Since: 3.0 **/ gboolean pika_module_is_loaded (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), FALSE); return module->priv->module != NULL; } /** * pika_module_get_info: * @module: A #PikaModule * * Returns: (transfer none): The @module's #PikaModuleInfo as provided * by the actual module, or %NULL. * * Since: 3.0 **/ const PikaModuleInfo * pika_module_get_info (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), NULL); return module->priv->info; } /** * pika_module_get_state: * @module: A #PikaModule * * Returns: The @module's state. * * Since: 3.0 **/ PikaModuleState pika_module_get_state (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), PIKA_MODULE_STATE_ERROR); return module->priv->state; } /** * pika_module_get_last_error: * @module: A #PikaModule * * Returns: The @module's last error message. * * Since: 3.0 **/ const gchar * pika_module_get_last_error (PikaModule *module) { g_return_val_if_fail (PIKA_IS_MODULE (module), NULL); return module->priv->last_error; } /** * pika_module_query_module: * @module: A #PikaModule. * * Queries the module without actually registering any of the types it * may implement. After successful query, pika_module_get_info() can be * used to get further about the module. * * Returns: %TRUE on success. **/ gboolean pika_module_query_module (PikaModule *module) { const PikaModuleInfo *info; gboolean close_module = FALSE; gpointer func; g_return_val_if_fail (PIKA_IS_MODULE (module), FALSE); if (! module->priv->module) { if (! pika_module_open (module)) return FALSE; close_module = TRUE; } /* find the pika_module_query symbol */ if (! g_module_symbol (module->priv->module, "pika_module_query", &func)) { pika_module_set_last_error (module, "Missing pika_module_query() symbol"); g_message (_("Module '%s' load error: %s"), pika_file_get_utf8_name (module->priv->file), module->priv->last_error); pika_module_close (module); module->priv->state = PIKA_MODULE_STATE_ERROR; return FALSE; } module->priv->query_module = func; g_clear_pointer (&module->priv->info, pika_module_info_free); info = module->priv->query_module (G_TYPE_MODULE (module)); if (! info || info->abi_version != PIKA_MODULE_ABI_VERSION) { pika_module_set_last_error (module, info ? "module ABI version does not match" : "pika_module_query() returned NULL"); g_message (_("Module '%s' load error: %s"), pika_file_get_utf8_name (module->priv->file), module->priv->last_error); pika_module_close (module); module->priv->state = PIKA_MODULE_STATE_ERROR; return FALSE; } module->priv->info = pika_module_info_copy (info); if (close_module) return pika_module_close (module); return TRUE; } /** * pika_module_error_quark: * * This function is never called directly. Use PIKA_MODULE_ERROR() instead. * * Returns: the #GQuark that defines the PIKA module error domain. * * Since: 2.8 **/ GQuark pika_module_error_quark (void) { return g_quark_from_static_string ("pika-module-error-quark"); } /* private functions */ static gboolean pika_module_open (PikaModule *module) { gchar *path = g_file_get_path (module->priv->file); module->priv->module = g_module_open (path, 0); g_free (path); if (! module->priv->module) { module->priv->state = PIKA_MODULE_STATE_ERROR; pika_module_set_last_error (module, g_module_error ()); g_message (_("Module '%s' load error: %s"), pika_file_get_utf8_name (module->priv->file), module->priv->last_error); return FALSE; } return TRUE; } static gboolean pika_module_close (PikaModule *module) { g_module_close (module->priv->module); /* FIXME: error handling */ module->priv->module = NULL; module->priv->query_module = NULL; module->priv->register_module = NULL; module->priv->state = PIKA_MODULE_STATE_NOT_LOADED; return TRUE; } static void pika_module_set_last_error (PikaModule *module, const gchar *error_str) { if (module->priv->last_error) g_free (module->priv->last_error); module->priv->last_error = g_strdup (error_str); } /* PikaModuleInfo functions */ /** * pika_module_info_new: * @abi_version: The #PIKA_MODULE_ABI_VERSION the module was compiled against. * @purpose: The module's general purpose. * @author: The module's author. * @version: The module's version. * @copyright: The module's copyright. * @date: The module's release date. * * Creates a newly allocated #PikaModuleInfo struct. * * Returns: The new #PikaModuleInfo struct. **/ static PikaModuleInfo * pika_module_info_new (guint32 abi_version, const gchar *purpose, const gchar *author, const gchar *version, const gchar *copyright, const gchar *date) { PikaModuleInfo *info = g_slice_new0 (PikaModuleInfo); info->abi_version = abi_version; info->purpose = g_strdup (purpose); info->author = g_strdup (author); info->version = g_strdup (version); info->copyright = g_strdup (copyright); info->date = g_strdup (date); return info; } /** * pika_module_info_copy: * @info: The #PikaModuleInfo struct to copy. * * Copies a #PikaModuleInfo struct. * * Returns: The new copy. **/ static PikaModuleInfo * pika_module_info_copy (const PikaModuleInfo *info) { g_return_val_if_fail (info != NULL, NULL); return pika_module_info_new (info->abi_version, info->purpose, info->author, info->version, info->copyright, info->date); } /** * pika_module_info_free: * @info: The #PikaModuleInfo struct to free * * Frees the passed #PikaModuleInfo. **/ static void pika_module_info_free (PikaModuleInfo *info) { g_return_if_fail (info != NULL); g_free (info->purpose); g_free (info->author); g_free (info->version); g_free (info->copyright); g_free (info->date); g_slice_free (PikaModuleInfo, info); }