degesch: export input and IRC hooks

This commit is contained in:
Přemysl Eric Janouch 2015-11-20 02:43:43 +01:00
parent c912726f49
commit 59a4c356dd
1 changed files with 463 additions and 16 deletions

479
degesch.c
View File

@ -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