aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common.c2
-rw-r--r--degesch.c192
m---------liberty0
3 files changed, 193 insertions, 1 deletions
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 <name> | unload <name>",
+ handle_command_plugin, 0 },
{ "alias", "List or set aliases",
"[<name> <definition>]",
@@ -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
-Subproject 649c351560bf9fea9e7b889c117afa94a44150d
+Subproject 0adcaf67c23fdc2a5082aa11aefd4fdc0aafd70