From: Aki Tuomi Date: Sat, 11 Nov 2017 09:55:21 +0000 (+0200) Subject: lib-lua: Add lua helper library X-Git-Tag: 2.3.0.rc1~410 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e88e76e782853e826d00524ee1133e64fb69b3d9;p=thirdparty%2Fdovecot%2Fcore.git lib-lua: Add lua helper library Provides a base lua library for lua extensibility --- diff --git a/configure.ac b/configure.ac index 4ee09d8a86..b3034cc5a6 100644 --- a/configure.ac +++ b/configure.ac @@ -844,6 +844,7 @@ src/lib-imap-urlauth/Makefile src/lib-index/Makefile src/lib-lda/Makefile src/lib-ldap/Makefile +src/lib-lua/Makefile src/lib-mail/Makefile src/lib-master/Makefile src/lib-ntlm/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index e4d602fbd8..fb8f7b3481 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,9 @@ if HAVE_LDAP LIB_LDAP=lib-ldap endif +if HAVE_LUA +LIB_LUA=lib-lua +endif LIBDOVECOT_SUBDIRS = \ lib-test \ @@ -29,6 +32,7 @@ SUBDIRS = \ lib-dict-extra \ lib-dovecot \ $(LIB_LDAP) \ + $(LIB_LUA) \ lib-fts \ lib-imap-client \ lib-imap-urlauth \ diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am new file mode 100644 index 0000000000..d24475ceab --- /dev/null +++ b/src/lib-lua/Makefile.am @@ -0,0 +1,39 @@ +noinst_LTLIBRARIES = libdovecot-lua.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + $(LUA_CFLAGS) + +libdovecot_lua_la_SOURCES = \ + dlua-script.c \ + dlua-dovecot.c \ + dlua-compat.c +libdovecot_lua_la_DEPENDENCIES = ../lib-dovecot/libdovecot.la +libdovecot_lua_la_LIBADD = ../lib-dovecot/libdovecot.la $(LUA_LIBS) + +headers = \ + dlua-script.h \ + dlua-script-private.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +test_programs = test-lua + +noinst_PROGRAMS = $(test_programs) + +test_libs =\ + libdovecot-lua.la \ + ../lib-dovecot/libdovecot.la + +test_lua_SOURCES = test-lua.c +test_lua_CFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) +test_lua_LDFLAGS = $(BINARY_LDFLAGS) +test_lua_LDADD = $(test_libs) +test_lua_DEPENDENCIES = $(test_libs) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-lua/dlua-compat.c b/src/lib-lua/dlua-compat.c new file mode 100644 index 0000000000..3b3908f99e --- /dev/null +++ b/src/lib-lua/dlua-compat.c @@ -0,0 +1,25 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dlua-script-private.h" + +#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 +void luaL_setmetatable (lua_State *L, const char *tname) { + luaL_checkstack(L, 1, "not enough stack slots"); + luaL_getmetatable(L, tname); + lua_setmetatable(L, -2); +} + +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkstack(L, nup+1, "too many upvalues"); + for (; l->name != NULL; l++) { + int i; + lua_pushstring(L, l->name); + for (i = 0; i < nup; i++) + lua_pushvalue(L, -(nup+1)); + lua_pushcclosure(L, l->func, nup); + lua_settable(L, -(nup + 3)); + } + lua_pop(L, nup); +} +#endif diff --git a/src/lib-lua/dlua-dovecot.c b/src/lib-lua/dlua-dovecot.c new file mode 100644 index 0000000000..9f58878189 --- /dev/null +++ b/src/lib-lua/dlua-dovecot.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "dlua-script-private.h" + +#define LUA_SCRIPT_DOVECOT "dovecot" + +static int dlua_i_debug(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *msg = luaL_checkstring(script->L, 1); + i_debug("%s", msg); + return 0; +} + +static int dlua_i_info(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *msg = luaL_checkstring(script->L, 1); + i_info("%s", msg); + return 0; +} + +static int dlua_i_warning(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *msg = luaL_checkstring(script->L, 1); + i_warning("%s", msg); + return 0; +} + +static int dlua_i_error(lua_State *L) +{ + struct dlua_script *script = dlua_script_from_state(L); + const char *msg = luaL_checkstring(script->L, 1); + i_error("%s", msg); + return 0; +} + +static luaL_Reg lua_dovecot_methods[] = { + { "i_debug", dlua_i_debug }, + { "i_info", dlua_i_info }, + { "i_warning", dlua_i_warning }, + { "i_error", dlua_i_error }, + { NULL, NULL } +}; + +void dlua_getdovecot(struct dlua_script *script) +{ + lua_getglobal(script->L, LUA_SCRIPT_DOVECOT); +} + +void dlua_dovecot_register(struct dlua_script *script) +{ + /* Create table for holding values */ + lua_newtable(script->L); + + /* push new metatable to stack */ + luaL_newmetatable(script->L, LUA_SCRIPT_DOVECOT); + /* this will register functions to the metatable itself */ + luaL_setfuncs(script->L, lua_dovecot_methods, 0); + /* point __index to self */ + lua_pushvalue(script->L, -1); + lua_setfield(script->L, -1, "__index"); + /* set table's metatable, pops stack */ + lua_setmetatable(script->L, -2); + + /* register table as global */ + lua_setglobal(script->L, LUA_SCRIPT_DOVECOT); +} diff --git a/src/lib-lua/dlua-script-private.h b/src/lib-lua/dlua-script-private.h new file mode 100644 index 0000000000..6676477434 --- /dev/null +++ b/src/lib-lua/dlua-script-private.h @@ -0,0 +1,84 @@ +#ifndef LUA_SCRIPT_PRIVATE_H +#define LUA_SCRIPT_PRIVATE_H 1 + +#include "dlua-script.h" +#include "lualib.h" +#include "lauxlib.h" + +#if !defined(LUA_VERSION_NUM) +#define lua_setfield(L, i, k) (lua_pushstring(L, k), lua_settable(L, i)) +#define lua_getref(L, ref) lua_rawgeti(L, LUA_REGISTRYINDEX, ref); +#define luaL_unref(L, ref) luaL_unref(L, LUA_REGISTRYINDEX, ref); +#endif + +#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 +#define luaL_newmetatable(L, tn) \ + (luaL_newmetatable(L, tn) ? (lua_pushstring(L, tn), lua_setfield(L, -2, "__name"), 1) : 0) +#define luaL_newlibtable(L, l) (lua_createtable(L, 0, sizeof(l)/sizeof(*(l))-1)) +#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_register(L, NULL, l)) +#define lua_load(L, r, s, fn, m) lua_load(L, r, s, fn) +void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); +void luaL_setmetatable (lua_State *L, const char *tname); +#endif + +/* consistency helpers */ +#define lua_isstring(L, n) (lua_isstring(L, n) == 1) +#define lua_isnumber(L, n) (lua_isnumber(L, n) == 1) +#define lua_toboolean(L, n) (lua_toboolean(L, n) == 1) + +#define DLUA_TABLE_STRING(n, s) { .name = n, .type = DLUA_TABLE_VALUE_STRING, .v.s = s } +#define DLUA_TABLE_INTEGER(n, i) { .name = n, .type = DLUA_TABLE_VALUE_INTEGER, .v.i = i } +#define DLUA_TABLE_ENUM(n) { .name = #n, .type = DLUA_TABLE_VALUE_INTEGER, .v.i = n } +#define DLUA_TABLE_DOUBLE(n, d) { .name = n, .type = DLUA_TABLE_VALUE_DOUBLE, .v.d = d } +#define DLUA_TABLE_BOOLEAN(n, b) { .name = n, .type = DLUA_TABLE_VALUE_BOOLEAN, .v.b = b } +#define DLUA_TABLE_NULL(n, s) { .name = n, .type = DLUA_TABLE_VALUE_NULL } +#define DLUA_TABLE_END { .name = NULL } + +struct dlua_script { + struct dlua_script *prev,*next; + pool_t pool; + + lua_State *L; + + const char *filename; + struct istream *in; + ssize_t last_read; + + unsigned int ref; + bool init:1; +}; + +enum dlua_table_value_type { + DLUA_TABLE_VALUE_STRING = 0, + DLUA_TABLE_VALUE_INTEGER, + DLUA_TABLE_VALUE_DOUBLE, + DLUA_TABLE_VALUE_BOOLEAN, + DLUA_TABLE_VALUE_NULL +}; + +struct dlua_table_values { + const char *name; + enum dlua_table_value_type type; + union { + const char *s; + ptrdiff_t i; + double d; + bool b; + } v; +}; + +/* Get dlua_script from lua_State */ +struct dlua_script *dlua_script_from_state(lua_State *L); + +/* register 'dovecot' global */ +void dlua_dovecot_register(struct dlua_script *script); + +/* push 'dovecot' global on top of stack */ +void dlua_getdovecot(struct dlua_script *script); + +/* assign values to table on idx */ +void dlua_setmembers(struct dlua_script *script, + const struct dlua_table_values *values, int idx); + + +#endif diff --git a/src/lib-lua/dlua-script.c b/src/lib-lua/dlua-script.c new file mode 100644 index 0000000000..f94913665c --- /dev/null +++ b/src/lib-lua/dlua-script.c @@ -0,0 +1,344 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "istream.h" +#include "sha1.h" +#include "hex-binary.h" +#include "eacces-error.h" +#include "dlua-script-private.h" + +#include +#include + +#define LUA_SCRIPT_INIT_FN "script_init" +#define LUA_SCRIPT_DEINIT_FN "script_deinit" + +static struct dlua_script *dlua_scripts = NULL; + +static const char *dlua_errstr(int err) +{ + switch(err) { +#ifdef LUA_OK + case LUA_OK: + return "ok"; +#endif + case LUA_YIELD: + return "yield"; + case LUA_ERRRUN: + return "runtime error"; + case LUA_ERRSYNTAX: + return "syntax error"; + case LUA_ERRMEM: + return "out of memory"; +#ifdef LUA_ERRGCMM + case LUA_ERRGCMM: + return "gc management error"; +#endif + case LUA_ERRERR: + return "error while handling error"; +#ifdef LUA_ERRFILE + case LUA_ERRFILE: + return "error loading file"; +#endif + default: + return "unknown error"; + } +} + +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, t_strdup_printf("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; + for(script = dlua_scripts; script != NULL; script = script->next) + if (script->L == L) + return script; + i_unreached(); +} + +int dlua_script_init(struct dlua_script *script, const char **error_r) +{ + int ret = 0; + + if (script->init) + return 0; + script->init = TRUE; + + /* see if there is a symbol for init */ + lua_getglobal(script->L, LUA_SCRIPT_INIT_FN); + + if (lua_isfunction(script->L, -1)) { + ret = lua_pcall(script->L, 0, 1, 0); + if (ret != 0) { + *error_r = t_strdup_printf("lua_pcall("LUA_SCRIPT_INIT_FN") failed: %s", + lua_tostring(script->L, -1)); + ret = -1; + } else if (lua_isnumber(script->L, -1)) { + ret = lua_tointeger(script->L, -1); + if (ret != 0) + *error_r = "Script init failed"; + } else { + *error_r = t_strdup_printf(LUA_SCRIPT_INIT_FN "() returned non-number"); + ret = -1; + } + } + + lua_pop(script->L, 1); + return ret; +} + +static struct dlua_script *dlua_create_script(const char *name) +{ + 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; + luaL_openlibs(script->L); + + return script; +} + +static int dlua_run_script(struct dlua_script *script, const char **error_r) +{ + int err = lua_pcall(script->L, 0, 0, 0); + if (err != 0) { + *error_r = t_strdup_printf("lua_pcall(%s) failed: %s", + script->filename, + lua_tostring(script->L, -1)); + lua_pop(script->L,1); + return -1; + } + return 0; +} + +static struct dlua_script * +dlua_script_find_previous_script(const char *filename) +{ + struct dlua_script *script; + for(script = dlua_scripts; script != NULL; script = script->next) + if (strcmp(script->filename, filename)==0) + return script; + return NULL; +} + +static int +dlua_script_create_finish(struct dlua_script *script, struct dlua_script **script_r, + const char **error_r) +{ + if (dlua_run_script(script, error_r) < 0) { + dlua_script_unref(&script); + return -1; + } + + DLLIST_PREPEND(&dlua_scripts, script); + + *script_r = script; + + return 0; +} + +int dlua_script_create_string(const char *str, struct dlua_script **script_r, + const char **error_r) +{ + struct dlua_script *script; + int err; + 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)); + + if ((script = dlua_script_find_previous_script(fn)) != NULL) { + dlua_script_ref(script); + *script_r = script; + return 0; + } + + script = dlua_create_script(fn); + if ((err = luaL_loadstring(script->L, str)) != 0) { + *error_r = t_strdup_printf("lua_load() failed: %s", + dlua_errstr(err)); + dlua_script_unref(&script); + return -1; + } + + return dlua_script_create_finish(script, script_r, error_r); +} + +int dlua_script_create_file(const char *file, struct dlua_script **script_r, + const char **error_r) +{ + struct dlua_script *script; + int err; + + if ((script = dlua_script_find_previous_script(file)) != NULL) { + dlua_script_ref(script); + *script_r = script; + return 0; + } + + /* 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); + if ((err = luaL_loadfile(script->L, file)) != 0) { + *error_r = t_strdup_printf("lua_load(%s) failed: %s", + file, dlua_errstr(err)); + dlua_script_unref(&script); + return -1; + } + + return dlua_script_create_finish(script, script_r, error_r); +} + +int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r, + const char **error_r) +{ + struct dlua_script *script; + const char *filename = i_stream_get_name(is); + int err; + + i_assert(filename != NULL && *filename != '\0'); + + if ((script = dlua_script_find_previous_script(filename)) != NULL) { + dlua_script_ref(script); + *script_r = script; + return 0; + } + + script = dlua_create_script(filename); + script->in = is; + script->filename = p_strdup(script->pool, filename); + if ((err = lua_load(script->L, dlua_reader, script, filename, 0)) < 0) { + *error_r = t_strdup_printf("lua_load(%s) failed: %s", + filename, dlua_errstr(err)); + dlua_script_unref(&script); + return -1; + } + + return dlua_script_create_finish(script, script_r, error_r); +} + +static void dlua_script_destroy(struct dlua_script *script) +{ + /* courtesy call */ + int ret; + /* see if there is a symbol for deinit */ + lua_getglobal(script->L, LUA_SCRIPT_DEINIT_FN); + if (lua_isfunction(script->L, -1)) { + ret = lua_pcall(script->L, 0, 0, 0); + if (ret != 0) { + i_warning("lua_pcall("LUA_SCRIPT_DEINIT_FN") failed: %s", + lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + } + } else { + lua_pop(script->L, 1); + } + lua_close(script->L); + /* 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, fn); + bool ret = lua_isfunction(script->L, -1); + lua_pop(script->L, 1); + return ret; +} + +void dlua_setmembers(struct dlua_script *script, + const struct dlua_table_values *values, int idx) +{ + i_assert(script != NULL); + i_assert(lua_istable(script->L, idx)); + while(values->name != NULL) { + switch(values->type) { + case DLUA_TABLE_VALUE_STRING: + lua_pushstring(script->L, values->v.s); + break; + case DLUA_TABLE_VALUE_INTEGER: + lua_pushnumber(script->L, values->v.i); + break; + case DLUA_TABLE_VALUE_DOUBLE: + lua_pushnumber(script->L, values->v.d); + break; + case DLUA_TABLE_VALUE_BOOLEAN: + lua_pushboolean(script->L, values->v.b ? 1 : 0); + break; + case DLUA_TABLE_VALUE_NULL: + lua_pushnil(script->L); + break; + default: + i_unreached(); + } + lua_setfield(script->L, idx-1, values->name); + values++; + } +} diff --git a/src/lib-lua/dlua-script.h b/src/lib-lua/dlua-script.h new file mode 100644 index 0000000000..8e53f2f5ca --- /dev/null +++ b/src/lib-lua/dlua-script.h @@ -0,0 +1,29 @@ +#ifndef LUA_SCRIPT_H +#define LUA_SCRIPT_H 1 + +struct dlua_script; + +/* Parse and load a lua script. Will reuse an existing script + if found. */ +int dlua_script_create_string(const char *str, struct dlua_script **script_r, + const char **error_r); +int dlua_script_create_file(const char *file, struct dlua_script **script_r, + const char **error_r); +/* Remember to set script name using i_stream_set_name */ +int dlua_script_create_stream(struct istream *is, struct dlua_script **script_r, + const char **error_r); + +/* run dlua_script_init function */ +int dlua_script_init(struct dlua_script *script, const char **error_r); + +/* Reference lua script */ +void dlua_script_ref(struct dlua_script *script); + +/* Unreference a script, calls deinit and frees when no more + references exist */ +void dlua_script_unref(struct dlua_script **_script); + +/* see if particular function is registered */ +bool dlua_script_has_function(struct dlua_script *script, const char *fn); + +#endif diff --git a/src/lib-lua/test-lua.c b/src/lib-lua/test-lua.c new file mode 100644 index 0000000000..c962d4bd22 --- /dev/null +++ b/src/lib-lua/test-lua.c @@ -0,0 +1,35 @@ +#include "test-lib.h" +#include "dlua-script-private.h" + +static void test_lua(void) +{ + static const char *luascript = +"function script_init(req)\n" +" dovecot.i_debug(\"lua script init called\")\n" +" return 0\n" +"end\n" +"function lua_function()\n" +"end\n"; + const char *error = NULL; + struct dlua_script *script = NULL; + + test_begin("lua script"); + + test_assert(dlua_script_create_string(luascript, &script, &error) == 0); + dlua_dovecot_register(script); + test_assert(dlua_script_init(script, &error) == 0); + test_assert(dlua_script_has_function(script, "lua_function")); + + dlua_script_unref(&script); + + test_end(); +} + +int main(void) { + void (*tests[])(void) = { + test_lua, + NULL + }; + + return test_run(tests); +}