From 59a4c356dd840c3c5d6f562ca85fe197f8b53b35 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Fri, 20 Nov 2015 02:43:43 +0100
Subject: degesch: export input and IRC hooks
---
degesch.c | 479 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 463 insertions(+), 16 deletions(-)
diff --git a/degesch.c b/degesch.c
index ff48263..6b32f7b 100644
--- a/degesch.c
+++ b/degesch.c
@@ -7280,6 +7280,11 @@ irc_hook_insert (struct app_context *ctx, struct irc_hook *hook)
// --- Lua ---------------------------------------------------------------------
+// Each plugin has its own Lua state object, so that a/ they don't disturb each
+// other and b/ unloading a plugin releases all resources.
+//
+// References to internal objects (buffers, servers) are all weak.
+
#ifdef HAVE_LUA
struct lua_plugin
@@ -7303,6 +7308,427 @@ struct plugin_vtable lua_plugin_vtable =
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// The registry can be used as a cache for weakly referenced objects
+
+static bool
+lua_cache_get (lua_State *L, void *object)
+{
+ lua_rawgetp (L, LUA_REGISTRYINDEX, object);
+ if (lua_isnil (L, -1))
+ {
+ lua_pop (L, 1);
+ return false;
+ }
+ return true;
+}
+
+static void
+lua_cache_store (lua_State *L, void *object, int index)
+{
+ lua_pushvalue (L, index);
+ lua_rawsetp (L, LUA_REGISTRYINDEX, object);
+}
+
+static void
+lua_cache_invalidate (lua_State *L, void *object)
+{
+ lua_pushnil (L);
+ lua_rawsetp (L, LUA_REGISTRYINDEX, object);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for the Lua metatable
+
+struct lua_buffer
+{
+ struct lua_plugin *plugin; ///< The plugin we belong to
+ struct buffer *buffer; ///< The buffer
+ struct weak_ref_link *weak_ref; ///< A weak reference link
+};
+
+static int
+lua_buffer_gc (lua_State *L)
+{
+ struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
+ if (wrapper->buffer)
+ {
+ lua_cache_invalidate (L, wrapper->buffer);
+ buffer_weak_unref (wrapper->buffer, &wrapper->weak_ref);
+ wrapper->buffer = NULL;
+ }
+ return 0;
+}
+
+static int
+lua_buffer_log (lua_State *L)
+{
+ struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
+ luaL_argcheck (L, wrapper->buffer, 1, "dead reference used");
+ const char *message = luaL_checkstring (L, 2);
+
+ struct buffer *buffer = wrapper->buffer;
+ log_full (wrapper->plugin->ctx, buffer->server, buffer,
+ BUFFER_LINE_STATUS, "#s", message);
+ return 0;
+}
+
+static luaL_Reg lua_buffer_table[] =
+{
+ // TODO: some useful methods or values
+ { "__gc", lua_buffer_gc },
+ { "log", lua_buffer_log },
+ { NULL, NULL }
+};
+
+static void
+lua_buffer_invalidate (void *object, void *user_data)
+{
+ struct lua_buffer *wrapper = user_data;
+ wrapper->buffer = NULL;
+ wrapper->weak_ref = NULL;
+ // This can in theory call the GC, order isn't arbitrary here
+ lua_cache_invalidate (wrapper->plugin->L, object);
+}
+
+static void
+lua_plugin_push_buffer (struct lua_plugin *plugin, struct buffer *buffer)
+{
+ struct lua_State *L = plugin->L;
+ if (lua_cache_get (L, buffer))
+ return;
+
+ struct lua_buffer *wrapper = lua_newuserdata (L, sizeof *wrapper);
+ luaL_setmetatable (L, XLUA_BUFFER_METATABLE);
+ wrapper->plugin = plugin;
+ wrapper->buffer = buffer;
+ wrapper->weak_ref = buffer_weak_ref
+ (buffer, lua_buffer_invalidate, wrapper);
+ lua_cache_store (L, buffer, -1);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define XLUA_SERVER_METATABLE "server" ///< Identifier for the Lua metatable
+
+struct lua_server
+{
+ struct lua_plugin *plugin; ///< The plugin we belong to
+ struct server *server; ///< The server
+ struct weak_ref_link *weak_ref; ///< A weak reference link
+};
+
+static int
+lua_server_gc (lua_State *L)
+{
+ struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
+ if (wrapper->server)
+ {
+ lua_cache_invalidate (L, wrapper->server);
+ server_weak_unref (wrapper->server, &wrapper->weak_ref);
+ wrapper->server = NULL;
+ }
+ return 0;
+}
+
+static int
+lua_server_send (lua_State *L)
+{
+ struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
+ luaL_argcheck (L, wrapper->server, 1, "dead reference used");
+ const char *line = luaL_checkstring (L, 2);
+
+ irc_send (wrapper->server, "%s", line);
+ return 0;
+}
+
+static luaL_Reg lua_server_table[] =
+{
+ // TODO: some useful methods or values
+ { "__gc", lua_server_gc },
+ { "send", lua_server_send },
+ { NULL, NULL }
+};
+
+static void
+lua_server_invalidate (void *object, void *user_data)
+{
+ struct lua_server *wrapper = user_data;
+ wrapper->server = NULL;
+ wrapper->weak_ref = NULL;
+ // This can in theory call the GC, order isn't arbitrary here
+ lua_cache_invalidate (wrapper->plugin->L, object);
+}
+
+static void
+lua_plugin_push_server (struct lua_plugin *plugin, struct server *server)
+{
+ struct lua_State *L = plugin->L;
+ if (lua_cache_get (L, server))
+ return;
+
+ struct lua_server *wrapper = lua_newuserdata (L, sizeof *wrapper);
+ luaL_setmetatable (L, XLUA_SERVER_METATABLE);
+ wrapper->plugin = plugin;
+ wrapper->server = server;
+ wrapper->weak_ref = server_weak_ref
+ (server, lua_server_invalidate, wrapper);
+ lua_cache_store (L, server, -1);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#define XLUA_HOOK_METATABLE "hook" ///< Identifier for the Lua metatable
+
+enum lua_hook_type
+{
+ XLUA_HOOK_DEFUNCT, ///< No longer functional
+ XLUA_HOOK_INPUT, ///< Input hook
+ XLUA_HOOK_IRC, ///< IRC hook
+};
+
+struct lua_hook
+{
+ struct lua_plugin *plugin; ///< The plugin we belong to
+ enum lua_hook_type type; ///< Type of the hook
+ union
+ {
+ struct input_hook input_hook; ///< Input hook
+ struct irc_hook irc_hook; ///< IRC hook
+ }
+ data; ///< Hook data
+};
+
+static int
+lua_hook_unhook (lua_State *L)
+{
+ struct lua_hook *hook = luaL_checkudata (L, 1, XLUA_HOOK_METATABLE);
+ switch (hook->type)
+ {
+ case XLUA_HOOK_INPUT:
+ LIST_UNLINK (hook->plugin->ctx->input_hooks, &hook->data.input_hook);
+ break;
+ case XLUA_HOOK_IRC:
+ LIST_UNLINK (hook->plugin->ctx->irc_hooks, &hook->data.irc_hook);
+ break;
+ default:
+ hard_assert (!"invalid hook type");
+ case XLUA_HOOK_DEFUNCT:
+ break;
+ }
+
+ // The hook no longer has to stay alive
+ lua_pushnil (L);
+ lua_rawsetp (L, LUA_REGISTRYINDEX, hook);
+ hook->type = XLUA_HOOK_DEFUNCT;
+ return 0;
+}
+
+// The hook dies either when the plugin requests it or at plugin unload
+static luaL_Reg lua_hook_table[] =
+{
+ { "unhook", lua_hook_unhook },
+ { "__gc", lua_hook_unhook },
+ { NULL, NULL }
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// Append a traceback to all errors so that we can later extract it
+static int
+lua_plugin_error_handler (lua_State *L)
+{
+ luaL_traceback (L, L, luaL_checkstring (L, 1), 1);
+ return 1;
+}
+
+static bool
+lua_plugin_process_error (struct lua_plugin *self, const char *message,
+ struct error **e)
+{
+ struct str_vector v;
+ str_vector_init (&v);
+ cstr_split_ignore_empty (message, '\n', &v);
+
+ if (v.len < 2)
+ error_set (e, "%s", message);
+ else
+ {
+ error_set (e, "%s", v.vector[0]);
+ log_global_debug (self->ctx, "Lua: plugin \"#s\": #s",
+ self->super.name, v.vector[1]);
+ for (size_t i = 2; i < v.len; i++)
+ log_global_debug (self->ctx, " #s", v.vector[i]);
+ }
+
+ str_vector_free (&v);
+ return false;
+}
+
+// Convenience function; replaces the "original" string or produces an error
+static bool
+lua_plugin_handle_string_filter_result (struct lua_plugin *self,
+ int result, char **original, bool utf8, struct error **e)
+{
+ lua_State *L = self->L;
+ if (result)
+ return lua_plugin_process_error (self, lua_tostring (L, -1), e);
+ if (lua_isnil (L, -1))
+ return NULL;
+ if (!lua_isstring (L, -1))
+ FAIL ("must return either a string or nil");
+
+ size_t len;
+ const char *processed = lua_tolstring (L, -1, &len);
+ if (utf8 && !utf8_validate (processed, len))
+ FAIL ("must return valid UTF-8");
+
+ // Only replace the string if it's different
+ if (strcmp (processed, *original))
+ {
+ free (*original);
+ *original = xstrdup (processed);
+ }
+ return true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static char *
+lua_input_hook_filter (struct input_hook *self, struct buffer *buffer,
+ char *input)
+{
+ struct lua_hook *hook = (struct lua_hook *)
+ ((char *) self - offsetof (struct lua_hook, data.input_hook));
+ struct lua_plugin *plugin = hook->plugin;
+ lua_State *L = plugin->L;
+
+ lua_pushcfunction (L, lua_plugin_error_handler);
+
+ lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
+ lua_getuservalue (L, -1); // Retrieve function
+ lua_insert (L, -2); // Swap with thet hook
+
+ lua_plugin_push_buffer (plugin, buffer); // 2: buffer
+ lua_pushstring (L, input); // 3: input
+
+ struct error *e = NULL;
+ bool failed = !lua_plugin_handle_string_filter_result
+ (plugin, lua_pcall (L, 3, 1, -5), &input, true, &e);
+ lua_pop (L, 1);
+
+ if (failed)
+ {
+ log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s: #s",
+ plugin->super.name, "input hook", e->message);
+ error_free (e);
+ }
+ return input;
+}
+
+struct input_hook_vtable lua_input_hook_vtable =
+{
+ .filter = lua_input_hook_filter,
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static char *
+lua_irc_hook_filter (struct irc_hook *self, struct server *s, char *message)
+{
+ struct lua_hook *hook = (struct lua_hook *)
+ ((char *) self - offsetof (struct lua_hook, data.irc_hook));
+ struct lua_plugin *plugin = hook->plugin;
+ lua_State *L = plugin->L;
+
+ lua_pushcfunction (L, lua_plugin_error_handler);
+
+ lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
+ lua_getuservalue (L, -1); // Retrieve function
+ lua_insert (L, -2); // Swap with thet hook
+
+ lua_plugin_push_server (plugin, s); // 2: server
+ lua_pushstring (L, message); // 3: message
+
+ struct error *e = NULL;
+ bool failed = !lua_plugin_handle_string_filter_result
+ (plugin, lua_pcall (L, 3, 1, -5), &message, false, &e);
+ lua_pop (L, 1);
+
+ if (failed)
+ {
+ log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s: #s",
+ plugin->super.name, "IRC hook", e->message);
+ error_free (e);
+ }
+ return message;
+}
+
+struct irc_hook_vtable lua_irc_hook_vtable =
+{
+ .filter = lua_irc_hook_filter,
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct lua_hook *
+lua_plugin_push_hook
+ (struct lua_plugin *plugin, int callback_index, enum lua_hook_type type)
+{
+ struct lua_State *L = plugin->L;
+ struct lua_hook *hook = lua_newuserdata (L, sizeof *hook);
+ luaL_setmetatable (L, XLUA_HOOK_METATABLE);
+ memset (hook, 0, sizeof *hook);
+ hook->type = type;
+ hook->plugin = plugin;
+
+ // Associate the callback with the hook
+ lua_pushvalue (L, callback_index);
+ lua_setuservalue (L, -2);
+
+ // Make sure the hook doesn't get garbage collected and return it
+ lua_pushvalue (L, -1);
+ lua_rawsetp (L, LUA_REGISTRYINDEX, hook);
+ return hook;
+}
+
+static int
+lua_plugin_hook_input (lua_State *L)
+{
+ struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
+ luaL_checktype (L, 1, LUA_TFUNCTION);
+ lua_Integer priority = luaL_optinteger (L, 2, 0);
+
+ struct lua_hook *hook = lua_plugin_push_hook (plugin, 1, XLUA_HOOK_INPUT);
+ hook->data.input_hook.vtable = &lua_input_hook_vtable;
+ hook->data.input_hook.priority = priority;
+ input_hook_insert (plugin->ctx, &hook->data.input_hook);
+ return 1;
+}
+
+static int
+lua_plugin_hook_irc (lua_State *L)
+{
+ struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
+ luaL_checktype (L, 1, LUA_TFUNCTION);
+ lua_Integer priority = luaL_optinteger (L, 2, 0);
+
+ struct lua_hook *hook = lua_plugin_push_hook (plugin, 1, XLUA_HOOK_IRC);
+ hook->data.irc_hook.vtable = &lua_irc_hook_vtable;
+ hook->data.irc_hook.priority = priority;
+ irc_hook_insert (plugin->ctx, &hook->data.irc_hook);
+ return 1;
+}
+
+static luaL_Reg lua_plugin_library[] =
+{
+ { "hook_input", lua_plugin_hook_input },
+ { "hook_irc", lua_plugin_hook_irc },
+ { NULL, NULL },
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
static void *
lua_plugin_alloc (void *ud, void *ptr, size_t o_size, size_t n_size)
{
@@ -7326,10 +7752,22 @@ lua_plugin_panic (lua_State *L)
return 0;
}
-static luaL_Reg lua_plugin_library[] =
+static void
+lua_plugin_create_meta (lua_State *L, const char *name, luaL_Reg *fns)
{
- { NULL, NULL },
-};
+ luaL_newmetatable (L, name);
+ luaL_setfuncs (L, fns, 0);
+
+ // Otherwise any non-meta functions would be inaccessible
+ bool has_own_index = (lua_getfield (L, -1, "__index") != LUA_TNIL);
+ lua_pop (L, 1);
+ if (!has_own_index)
+ {
+ lua_pushvalue (L, -1);
+ lua_setfield (L, -2, "__index");
+ }
+ lua_pop (L, 1);
+}
static struct plugin *
lua_plugin_load (struct app_context *ctx, const char *filename,
@@ -7345,24 +7783,33 @@ lua_plugin_load (struct app_context *ctx, const char *filename,
lua_atpanic (L, lua_plugin_panic);
luaL_openlibs (L);
- luaL_newlib (L, lua_plugin_library);
- lua_setglobal (L, PROGRAM_NAME);
-
- int ret;
- if ((ret = luaL_loadfile (L, filename))
- || (ret = lua_pcall (L, 0, 0, 0)))
- {
- error_set (e, "%s: %s", "Lua", lua_tostring (L, -1));
- lua_close (L);
- return NULL;
- }
-
struct lua_plugin *plugin = xcalloc (1, sizeof *plugin);
plugin->super.name = NULL;
plugin->super.vtable = &lua_plugin_vtable;
plugin->ctx = ctx;
plugin->L = L;
- return &plugin->super;
+
+ // Register the degesch library with "plugin" as an upvalue
+ luaL_checkversion (L);
+ luaL_newlibtable (L, lua_plugin_library);
+ lua_pushlightuserdata (L, plugin);
+ luaL_setfuncs (L, lua_plugin_library, 1);
+ lua_setglobal (L, PROGRAM_NAME);
+
+ // Create metatables for our objects
+ lua_plugin_create_meta (L, XLUA_HOOK_METATABLE, lua_hook_table);
+ lua_plugin_create_meta (L, XLUA_BUFFER_METATABLE, lua_buffer_table);
+ lua_plugin_create_meta (L, XLUA_SERVER_METATABLE, lua_server_table);
+
+ int ret;
+ if (!(ret = luaL_loadfile (L, filename))
+ && !(ret = lua_pcall (L, 0, 0, 0)))
+ return &plugin->super;
+
+ error_set (e, "%s: %s", "Lua", lua_tostring (L, -1));
+ lua_close (L);
+ free (plugin);
+ return NULL;
}
#endif // HAVE_LUA
--
cgit v1.2.3-70-g09d2