summaryrefslogtreecommitdiffstats
path: root/src/lib-lua/dlua-script.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-lua/dlua-script.c')
-rw-r--r--src/lib-lua/dlua-script.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/src/lib-lua/dlua-script.c b/src/lib-lua/dlua-script.c
new file mode 100644
index 0000000..48fe286
--- /dev/null
+++ b/src/lib-lua/dlua-script.c
@@ -0,0 +1,453 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "istream.h"
+#include "sha1.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "eacces-error.h"
+#include "ioloop.h"
+#include "dlua-script-private.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+/* the registry entry with a pointer to struct dlua_script */
+#define LUA_SCRIPT_REGISTRY_KEY "DLUA_SCRIPT"
+
+#define LUA_SCRIPT_INIT_FN "script_init"
+#define LUA_SCRIPT_DEINIT_FN "script_deinit"
+
+struct event_category event_category_lua = {
+ .name = "lua",
+};
+
+static struct dlua_script *dlua_scripts = NULL;
+
+static int
+dlua_script_create_finish(struct dlua_script *script, const char **error_r);
+
+static void *dlua_alloc(void *ctx, void *ptr, size_t osize, size_t nsize)
+{
+ struct dlua_script *script =
+ (struct dlua_script*)ctx;
+
+ if (nsize == 0) {
+ p_free(script->pool, ptr);
+ return NULL;
+ } else {
+ return p_realloc(script->pool, ptr, osize, nsize);
+ }
+}
+
+static const char *dlua_reader(lua_State *L, void *ctx, size_t *size_r)
+{
+ struct dlua_script *script =
+ (struct dlua_script*)ctx;
+ const unsigned char *data;
+ i_stream_skip(script->in, script->last_read);
+ if (i_stream_read_more(script->in, &data, size_r) == -1 &&
+ script->in->stream_errno != 0) {
+ luaL_error(L, "read(%s) failed: %s",
+ script->filename,
+ i_stream_get_error(script->in));
+ *size_r = 0;
+ return NULL;
+ }
+ script->last_read = *size_r;
+ return (const char*)data;
+}
+
+struct dlua_script *dlua_script_from_state(lua_State *L)
+{
+ struct dlua_script *script;
+
+ /* get light pointer from globals */
+ lua_pushstring(L, LUA_SCRIPT_REGISTRY_KEY);
+ lua_gettable(L, LUA_REGISTRYINDEX);
+ script = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ i_assert(script != NULL);
+
+ return script;
+}
+
+int dlua_pcall(lua_State *L, const char *func_name, int nargs, int nresults,
+ const char **error_r)
+{
+ /* record the stack position */
+ int ret = 0, debugh_idx, top = lua_gettop(L) - nargs;
+
+ lua_getglobal(L, func_name);
+
+ if (lua_isfunction(L, -1)) {
+ /* stack on entry
+ args
+ func <-- top
+ */
+ /* move func name before arguments */
+ lua_insert(L, -(nargs + 1));
+ /* stack now
+ func
+ args <-- top
+ */
+ lua_getglobal(L, "debug");
+ lua_getfield(L, -1, "traceback");
+ lua_replace(L, -2);
+ /* stack now
+ func
+ args
+ traceback <-- top
+ */
+ /* move error handler before func name */
+ lua_insert(L, -(nargs + 2));
+ /* stack now
+ traceback
+ func
+ args <-- top
+ */
+ /* record where traceback is so it's easy to get rid of even
+ if LUA_MULTRET is used. */
+ debugh_idx = lua_gettop(L) - nargs - 1;
+ ret = lua_pcall(L, nargs, nresults, -(nargs + 2));
+ if (ret != LUA_OK) {
+ *error_r = t_strdup_printf("lua_pcall(%s, %d, %d) failed: %s",
+ func_name, nargs, nresults,
+ lua_tostring(L, -1));
+ /* Remove error and debug handler */
+ lua_pop(L, 2);
+ ret = -1;
+ } else {
+ /* remove debug handler from known location */
+ lua_remove(L, debugh_idx);
+ if (nresults == LUA_MULTRET)
+ nresults = lua_gettop(L) - top;
+ ret = nresults;
+ }
+ } else {
+ /* ensure stack is clean, remove function and arguments */
+ lua_pop(L, nargs + 1);
+ *error_r = t_strdup_printf("'%s' is not a function",
+ func_name);
+ ret = -1;
+ }
+#ifdef DEBUG
+ if ((ret == -1 && lua_gettop(L) != top) ||
+ (ret >= 0 &&
+ lua_gettop(L) != top + ret)) {
+ i_debug("LUA STACK UNCLEAN BEGIN for %s", func_name);
+ dlua_dump_stack(L);
+ i_debug("LUA STACK UNCLEAN END");
+ }
+#endif
+ /* enforce that stack is clean after call */
+ if (ret == -1)
+ i_assert(lua_gettop(L) == top);
+ else
+ i_assert(ret >= 0 && lua_gettop(L) == top + ret);
+ return ret;
+}
+
+static void dlua_call_deinit_function(struct dlua_script *script)
+{
+ const char *error;
+ if (!dlua_script_has_function(script, LUA_SCRIPT_DEINIT_FN))
+ return;
+ if (dlua_pcall(script->L, LUA_SCRIPT_DEINIT_FN, 0, 0, &error) < 0)
+ e_error(script->event, LUA_SCRIPT_DEINIT_FN"() failed: %s",
+ error);
+}
+
+int dlua_script_init(struct dlua_script *script, const char **error_r)
+{
+ if (script->init)
+ return 0;
+ script->init = TRUE;
+
+ if (dlua_script_create_finish(script, error_r) < 0)
+ return -1;
+
+ /* lets not fail on missing function... */
+ if (!dlua_script_has_function(script, LUA_SCRIPT_INIT_FN))
+ return 0;
+
+ int ret = 0;
+
+ if (dlua_pcall(script->L, LUA_SCRIPT_INIT_FN, 0, 1, error_r) < 0)
+ return -1;
+
+ if (lua_isinteger(script->L, -1)) {
+ ret = lua_tointeger(script->L, -1);
+ if (ret != 0) {
+ *error_r = "Script init failed";
+ ret = -1;
+ }
+ } else {
+ *error_r = LUA_SCRIPT_INIT_FN"() returned non-number";
+ ret = -1;
+ }
+
+ lua_pop(script->L, 1);
+
+ i_assert(lua_gettop(script->L) == 0);
+ return ret;
+}
+
+static int dlua_atpanic(lua_State *L)
+{
+ struct dlua_script *script = dlua_script_from_state(L);
+ const char *error = lua_tostring(script->L, -1);
+ i_panic("Lua script '%s': %s", script->filename, error);
+}
+
+static struct dlua_script *dlua_create_script(const char *name,
+ struct event *event_parent)
+{
+ pool_t pool = pool_allocfree_create(t_strdup_printf("lua script %s", name));
+ struct dlua_script *script = p_new(pool, struct dlua_script, 1);
+ script->pool = pool;
+ script->filename = p_strdup(pool, name);
+ /* lua API says that lua_newstate will return NULL only if it's out of
+ memory. this cannot really happen with our allocator as it will
+ call i_fatal_status anyways if it runs out of memory */
+ script->L = lua_newstate(dlua_alloc, script);
+ i_assert(script->L != NULL);
+ script->ref = 1;
+ lua_atpanic(script->L, dlua_atpanic);
+ luaL_openlibs(script->L);
+ script->event = event_create(event_parent);
+ event_add_str(script->event, "script", script->filename);
+ event_add_category(script->event, &event_category_lua);
+
+ dlua_init_thread_table(script);
+
+ DLLIST_PREPEND(&dlua_scripts, script);
+ return script;
+}
+
+static int dlua_run_script(struct dlua_script *script, const char **error_r)
+{
+ /* put the error handler before script being called */
+ lua_getglobal(script->L, "debug");
+ lua_getfield(script->L, -1, "traceback");
+ lua_replace(script->L, -2);
+ lua_insert(script->L, -2);
+
+ /* we don't want anything to be returned here */
+ /* stack before lua_pcall
+ debug.traceback
+ loaded script as function
+ */
+ int err = lua_pcall(script->L, 0, 0, 1);
+ if (err != LUA_OK) {
+ *error_r = t_strdup_printf("lua_pcall(%s) failed: %s",
+ script->filename,
+ lua_tostring(script->L, -1));
+ /* pop error and debug handler */
+ lua_pop(script->L, 2);
+ err = -1;
+ } else {
+ /* pop debug handler */
+ lua_pop(script->L, 1);
+ }
+ return err;
+}
+
+static int
+dlua_script_create_finish(struct dlua_script *script, const char **error_r)
+{
+ /* store pointer as light data to registry before calling the script */
+ lua_pushstring(script->L, LUA_SCRIPT_REGISTRY_KEY);
+ lua_pushlightuserdata(script->L, script);
+ lua_settable(script->L, LUA_REGISTRYINDEX);
+
+ if (dlua_run_script(script, error_r) < 0)
+ return -1;
+ i_assert(lua_gettop(script->L) == 0);
+ return 0;
+}
+
+int dlua_script_create_string(const char *str, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+ unsigned char scripthash[SHA1_RESULTLEN];
+ const char *fn;
+
+ *script_r = NULL;
+ sha1_get_digest(str, strlen(str), scripthash);
+ fn = binary_to_hex(scripthash, sizeof(scripthash));
+
+ script = dlua_create_script(fn, event_parent);
+ if (luaL_loadstring(script->L, str) == LUA_OK) {
+ *script_r = script;
+ return 0;
+ }
+ *error_r = t_strdup_printf("lua_load(<string>) failed: %s",
+ lua_tostring(script->L, -1));
+ lua_pop(script->L, 1);
+ dlua_script_unref(&script);
+ return -1;
+}
+
+int dlua_script_create_file(const char *file, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+
+ /* lua reports file access errors poorly */
+ if (access(file, O_RDONLY) < 0) {
+ if (errno == EACCES)
+ *error_r = eacces_error_get("access", file);
+ else
+ *error_r = t_strdup_printf("access(%s) failed: %m",
+ file);
+ return -1;
+ }
+
+ script = dlua_create_script(file, event_parent);
+ if (luaL_loadfile(script->L, file) != LUA_OK) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ file, lua_tostring(script->L, -1));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ *script_r = script;
+ return 0;
+}
+
+int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r,
+ struct event *event_parent, const char **error_r)
+{
+ struct dlua_script *script;
+ const char *filename = i_stream_get_name(is);
+
+ i_assert(filename != NULL && *filename != '\0');
+
+ script = dlua_create_script(filename, event_parent);
+ script->in = is;
+ script->filename = p_strdup(script->pool, filename);
+ if (lua_load(script->L, dlua_reader, script, filename, 0) != LUA_OK) {
+ *error_r = t_strdup_printf("lua_load(%s) failed: %s",
+ filename, lua_tostring(script->L, -1));
+ dlua_script_unref(&script);
+ return -1;
+ }
+
+ *script_r = script;
+ return 0;
+}
+
+static void dlua_script_destroy(struct dlua_script *script)
+{
+ dlua_call_deinit_function(script);
+
+ /* close all threads */
+ dlua_free_thread_table(script);
+
+ /* close base lua */
+ lua_close(script->L);
+
+ /* remove from list */
+ DLLIST_REMOVE(&dlua_scripts, script);
+
+ event_unref(&script->event);
+ /* then just release memory */
+ pool_unref(&script->pool);
+}
+
+void dlua_script_ref(struct dlua_script *script)
+{
+ i_assert(script->ref > 0);
+ script->ref++;
+}
+
+void dlua_script_unref(struct dlua_script **_script)
+{
+ struct dlua_script *script = *_script;
+ *_script = NULL;
+
+ if (script == NULL) return;
+
+ i_assert(script->ref > 0);
+ if (--script->ref > 0)
+ return;
+
+ dlua_script_destroy(script);
+}
+
+bool dlua_script_has_function(struct dlua_script *script, const char *fn)
+{
+ i_assert(script != NULL);
+ lua_getglobal(script->L, "_G");
+ lua_pushstring(script->L, fn);
+ lua_rawget(script->L, -2);
+ bool ret = lua_isfunction(script->L, -1);
+ lua_pop(script->L, 2);
+ return ret;
+}
+
+void dlua_set_members(lua_State *L, const struct dlua_table_values *values,
+ int idx)
+{
+ i_assert(L != NULL);
+ i_assert(lua_istable(L, idx));
+ while(values->name != NULL) {
+ switch(values->type) {
+ case DLUA_TABLE_VALUE_STRING:
+ lua_pushstring(L, values->v.s);
+ break;
+ case DLUA_TABLE_VALUE_INTEGER:
+ lua_pushnumber(L, values->v.i);
+ break;
+ case DLUA_TABLE_VALUE_DOUBLE:
+ lua_pushnumber(L, values->v.d);
+ break;
+ case DLUA_TABLE_VALUE_BOOLEAN:
+ lua_pushboolean(L, values->v.b);
+ break;
+ case DLUA_TABLE_VALUE_NULL:
+ lua_pushnil(L);
+ break;
+ default:
+ i_unreached();
+ }
+ lua_setfield(L, idx - 1, values->name);
+ values++;
+ }
+}
+
+void dlua_dump_stack(lua_State *L)
+{
+ /* get everything in stack */
+ int top = lua_gettop(L);
+ for (int i = 1; i <= top; i++) T_BEGIN { /* repeat for each level */
+ int t = lua_type(L, i);
+ string_t *line = t_str_new(32);
+ str_printfa(line, "#%d: ", i);
+ switch (t) {
+ case LUA_TSTRING: /* strings */
+ str_printfa(line, "`%s'", lua_tostring(L, i));
+ break;
+ case LUA_TBOOLEAN: /* booleans */
+ str_printfa(line, "`%s'", lua_toboolean(L, i) ? "true" : "false");
+ break;
+ case LUA_TNUMBER: /* numbers */
+ str_printfa(line, "%g", lua_tonumber(L, i));
+ break;
+ default: /* other values */
+ str_printfa(line, "%s", lua_typename(L, t));
+ break;
+ }
+ i_debug("%s", str_c(line));
+ } T_END;
+}
+
+/* assorted wrappers */
+void dlua_register(struct dlua_script *script, const char *name,
+ lua_CFunction f)
+{
+ lua_register(script->L, name, f);
+}