diff --git a/common.c b/common.c index 0ae0a61..f76554d 100644 --- a/common.c +++ b/common.c @@ -38,7 +38,7 @@ #define FAIL(...) \ BLOCK_START \ error_set (e, __VA_ARGS__); \ - return false; \ + return 0; \ BLOCK_END // --- To be moved to liberty -------------------------------------------------- diff --git a/degesch.c b/degesch.c index f0700f2..1a2ae20 100644 --- a/degesch.c +++ b/degesch.c @@ -1352,6 +1352,30 @@ REF_COUNTABLE_METHODS (server) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +struct plugin +{ + LIST_HEADER (struct plugin) + + char *name; ///< Name of the plugin + struct plugin_vtable *vtable; ///< Methods +}; + +struct plugin_vtable +{ + /// Unregister and free the plugin including all relevant resources + void (*free) (struct plugin *self); +}; + +static void +plugin_destroy (struct plugin *self) +{ + self->vtable->free (self); + free (self->name); + free (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + struct app_context { bool no_colors; ///< Disable attribute printing @@ -1411,6 +1435,8 @@ struct app_context bool running_backlog_helper; ///< Running a backlog helper int terminal_suspended; ///< Terminal suspension level + + struct plugin *plugins; ///< Loaded plugins } *g_ctx; @@ -1482,6 +1508,10 @@ app_context_init (struct app_context *self) static void app_context_free (struct app_context *self) { + // Plugins can try to use of the other fields when destroyed + LIST_FOR_EACH (struct plugin, iter, self->plugins) + plugin_destroy (iter); + config_free (&self->config); for (size_t i = 0; i < ATTR_COUNT; i++) { @@ -1749,6 +1779,11 @@ static struct config_schema g_config_behaviour[] = .type = CONFIG_ITEM_INTEGER, .validate = config_validate_nonnegative, .default_ = "600" }, + + { .name = "plugin_autoload", + .comment = "Plugins to automatically load on start", + .type = CONFIG_ITEM_STRING_ARRAY, + .validate = config_validate_nonjunk_string }, {} }; @@ -7094,6 +7129,125 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name) } } +// --- Plugins ----------------------------------------------------------------- + +typedef struct plugin *(*plugin_load_fn) + (struct app_context *ctx, const char *filename, struct error **e); + +// We can potentially add support for other scripting languages if so desired, +// however this possibility is just a byproduct of abstraction +static plugin_load_fn g_plugin_loaders[] = +{ +}; + +static struct plugin * +plugin_load_from_filename (struct app_context *ctx, const char *filename, + struct error **e) +{ + struct plugin *plugin = NULL; + struct error *error = NULL; + for (size_t i = 0; i < N_ELEMENTS (g_plugin_loaders); i++) + if ((plugin = g_plugin_loaders[i](ctx, filename, &error)) || error) + break; + + if (error) + error_propagate (e, error); + else if (!plugin) + FAIL ("no plugin handler for \"%s\"", filename); + return plugin; +} + +static struct plugin * +plugin_find (struct app_context *ctx, const char *name) +{ + LIST_FOR_EACH (struct plugin, iter, ctx->plugins) + if (!strcmp (name, iter->name)) + return iter; + return NULL; +} + +static char * +plugin_resolve_relative_filename (const char *filename) +{ + struct str_vector paths; + str_vector_init (&paths); + get_xdg_data_dirs (&paths); + char *result = resolve_relative_filename_generic + (&paths, PROGRAM_NAME "/plugins/", filename); + str_vector_free (&paths); + return result; +} + +static struct plugin * +plugin_load_by_name (struct app_context *ctx, const char *name, + struct error **e) +{ + struct plugin *plugin = plugin_find (ctx, name); + if (plugin) + FAIL ("plugin already loaded"); + + // As a side effect, a plugin can be loaded multiple times by giving + // various relative or non-relative paths to the function; this is not + // supposed to be fool-proof though, that requires other mechanisms + char *filename = resolve_filename (name, plugin_resolve_relative_filename); + if (!filename) + FAIL ("file not found"); + + plugin = plugin_load_from_filename (ctx, filename, e); + free (filename); + return plugin; +} + +static void +plugin_load (struct app_context *ctx, const char *name) +{ + struct error *e = NULL; + struct plugin *plugin = plugin_load_by_name (ctx, name, &e); + if (plugin) + { + log_global_status (ctx, "Plugin \"#s\" loaded", name); + LIST_PREPEND (ctx->plugins, plugin); + plugin->name = xstrdup (name); + } + else + { + log_global_error (ctx, "Can't load plugin \"#s\": #s", + name, e->message); + error_free (e); + } +} + +static void +plugin_unload (struct app_context *ctx, const char *name) +{ + struct plugin *plugin = plugin_find (ctx, name); + if (!plugin) + log_global_error (ctx, "Can't unload plugin \"#s\": #s", + name, "plugin not loaded"); + else + { + log_global_status (ctx, "Plugin \"#s\" unloaded", name); + LIST_UNLINK (ctx->plugins, plugin); + plugin_destroy (plugin); + } +} + +static void +load_plugins (struct app_context *ctx) +{ + const char *plugins = get_config_string + (ctx->config.root, "behaviour.plugin_autoload"); + if (plugins) + { + struct str_vector v; + str_vector_init (&v); + cstr_split_ignore_empty (plugins, ',', &v); + for (size_t i = 0; i < v.len; i++) + plugin_load (ctx, v.vector[i]); + str_vector_free (&v); + } +} + // --- User input handling ----------------------------------------------------- // HANDLER_NEEDS_REG is primarily for message sending commands, @@ -7502,6 +7656,40 @@ handle_command_save (struct handler_args *a) return true; } +static void +show_plugin_list (struct app_context *ctx) +{ + log_global_indent (ctx, ""); + log_global_indent (ctx, "Plugins:"); + LIST_FOR_EACH (struct plugin, iter, ctx->plugins) + log_global_indent (ctx, " #s", iter->name); +} + +static bool +handle_command_plugin (struct handler_args *a) +{ + char *action = cut_word (&a->arguments); + if (!*action || !strcasecmp_ascii (action, "list")) + show_plugin_list (a->ctx); + else if (!strcasecmp_ascii (action, "load")) + { + if (!*a->arguments) + return false; + + plugin_load (a->ctx, cut_word (&a->arguments)); + } + else if (!strcasecmp_ascii (action, "unload")) + { + if (!*a->arguments) + return false; + + plugin_unload (a->ctx, cut_word (&a->arguments)); + } + else + return false; + return true; +} + static bool show_aliases_list (struct app_context *ctx) { @@ -8219,6 +8407,9 @@ g_command_handlers[] = { "save", "Save configuration", NULL, handle_command_save, 0 }, + { "plugin", "Manage plugins", + "list | load | unload ", + handle_command_plugin, 0 }, { "alias", "List or set aliases", "[ ]", @@ -10189,6 +10380,7 @@ main (int argc, char *argv[]) toggle_bracketed_paste (true); // Finally, we juice the configuration for some servers to create + load_plugins (&ctx); load_servers (&ctx); ctx.polling = true; diff --git a/liberty b/liberty index 649c351..0adcaf6 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 649c351560bf9fea9e7b889c117afa94a44150d6 +Subproject commit 0adcaf67c23fdc2a5082aa11aefd4fdc0aafd70a