/* spoa-server: processing Lua * * Copyright 2018 OZON / Thierry Fournier * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include "spoa.h" static lua_State *L = NULL; static struct worker *worker; static int ps_lua_start_worker(struct worker *w); static int ps_lua_load_file(struct worker *w, const char *file); static int ps_lua_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args); static struct ps ps_lua_bindings_1 = { .init_worker = ps_lua_start_worker, .load_file = ps_lua_load_file, .exec_message = ps_lua_exec_message, .ext = ".lua", }; static struct ps ps_lua_bindings_2 = { .init_worker = ps_lua_start_worker, .load_file = ps_lua_load_file, .exec_message = ps_lua_exec_message, .ext = ".luac", }; /* Imported from Lua-5.3.4 */ static int typeerror (lua_State *L, int arg, const char *tname) { const char *msg; const char *typearg; /* name for the type of the actual argument */ if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) typearg = lua_tostring(L, -1); /* use the given type name */ else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA) typearg = "light userdata"; /* special name for messages */ else typearg = luaL_typename(L, arg); /* standard name */ msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg); return luaL_argerror(L, arg, msg); } /* Imported from Lua-5.3.4 */ static void tag_error (lua_State *L, int arg, int tag) { typeerror(L, arg, lua_typename(L, tag)); } #ifndef luaL_checkboolean static int luaL_checkboolean(lua_State *L, int index) { if (!lua_isboolean(L, index)) { tag_error(L, index, LUA_TBOOLEAN); } return lua_toboolean(L, index); } #endif static int ps_lua_register_message(lua_State *L) { const char *name; long ref; /* First argument is a message name */ name = luaL_checkstring(L, 1); /* Second argument is a function */ if (!lua_isfunction(L, 2)) { const char *msg = lua_pushfstring(L, "function expected, got %s", luaL_typename(L, 2)); luaL_argerror(L, 2, msg); } lua_pushvalue(L, 2); ref = luaL_ref(L, LUA_REGISTRYINDEX); /* Register the message processor */ ps_register_message(&ps_lua_bindings_1, name, (void *)ref); return 1; } static int ps_lua_set_var_null(lua_State *L) { const char *name; size_t name_len; unsigned char scope; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); if (!set_var_null(worker, name, name_len, scope)) { luaL_error(L, "No space left available"); } return 0; } static int ps_lua_set_var_boolean(lua_State *L) { const char *name; size_t name_len; unsigned char scope; int64_t value; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkboolean(L, 3); if (!set_var_bool(worker, name, name_len, scope, value)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_uint32(lua_State *L) { const char *name; size_t name_len; unsigned char scope; int64_t value; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkinteger(L, 3); if (value < 0 || value > UINT_MAX) luaL_error(L, "Integer '%lld' out of range for 'uint32' type", value); if (!set_var_uint32(worker, name, name_len, scope, value)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_int32(lua_State *L) { const char *name; size_t name_len; unsigned char scope; int64_t value; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkinteger(L, 3); if (value < INT_MIN || value > INT_MAX) luaL_error(L, "Integer '%lld' out of range for 'int32' type", value); if (!set_var_int32(worker, name, name_len, scope, value)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_uint64(lua_State *L) { const char *name; size_t name_len; unsigned char scope; int64_t value; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkinteger(L, 3); if (value < 0) luaL_error(L, "Integer '%lld' out of range for 'uint64' type", value); if (!set_var_uint64(worker, name, name_len, scope, value)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_int64(lua_State *L) { const char *name; size_t name_len; unsigned char scope; int64_t value; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkinteger(L, 3); if (!set_var_int64(worker, name, name_len, scope, value)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_ipv4(lua_State *L) { const char *name; size_t name_len; unsigned char scope; const char *value; struct in_addr ipv4; int ret; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkstring(L, 3); ret = inet_pton(AF_INET, value, &ipv4); if (ret == 0) luaL_error(L, "IPv4 '%s': invalid format", value); if (ret == -1) luaL_error(L, "IPv4 '%s': %s", value, strerror(errno)); if (!set_var_ipv4(worker, name, name_len, scope, &ipv4)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_ipv6(lua_State *L) { const char *name; size_t name_len; unsigned char scope; const char *value; struct in6_addr ipv6; int ret; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checkstring(L, 3); ret = inet_pton(AF_INET6, value, &ipv6); if (ret == 0) luaL_error(L, "IPv6 '%s': invalid format", value); if (ret == -1) luaL_error(L, "IPv6 '%s': %s", value, strerror(errno)); if (!set_var_ipv6(worker, name, name_len, scope, &ipv6)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_str(lua_State *L) { const char *name; size_t name_len; unsigned char scope; const char *value; size_t value_len; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checklstring(L, 3, &value_len); if (!set_var_string(worker, name, name_len, scope, value, value_len)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_set_var_bin(lua_State *L) { const char *name; size_t name_len; unsigned char scope; const char *value; size_t value_len; name = luaL_checklstring(L, 1, &name_len); scope = (unsigned char)luaL_checkinteger(L, 2); value = luaL_checklstring(L, 3, &value_len); if (!set_var_bin(worker, name, name_len, scope, value, value_len)) luaL_error(L, "No space left available"); return 0; } static int ps_lua_start_worker(struct worker *w) { if (L != NULL) return 1; worker = w; L = luaL_newstate(); luaL_openlibs(L); lua_newtable(L); lua_pushstring(L, "register_message"); lua_pushcclosure(L, ps_lua_register_message, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_null"); lua_pushcclosure(L, ps_lua_set_var_null, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_boolean"); lua_pushcclosure(L, ps_lua_set_var_boolean, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_uint32"); lua_pushcclosure(L, ps_lua_set_var_uint32, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_int32"); lua_pushcclosure(L, ps_lua_set_var_int32, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_uint64"); lua_pushcclosure(L, ps_lua_set_var_uint64, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_int64"); lua_pushcclosure(L, ps_lua_set_var_int64, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_ipv4"); lua_pushcclosure(L, ps_lua_set_var_ipv4, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_ipv6"); lua_pushcclosure(L, ps_lua_set_var_ipv6, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_str"); lua_pushcclosure(L, ps_lua_set_var_str, 0); lua_rawset(L, -3); lua_pushstring(L, "set_var_bin"); lua_pushcclosure(L, ps_lua_set_var_bin, 0); lua_rawset(L, -3); lua_pushstring(L, "scope"); lua_newtable(L); lua_pushstring(L, "proc"); lua_pushinteger(L, SPOE_SCOPE_PROC); lua_rawset(L, -3); lua_pushstring(L, "sess"); lua_pushinteger(L, SPOE_SCOPE_SESS); lua_rawset(L, -3); lua_pushstring(L, "txn"); lua_pushinteger(L, SPOE_SCOPE_TXN); lua_rawset(L, -3); lua_pushstring(L, "req"); lua_pushinteger(L, SPOE_SCOPE_REQ); lua_rawset(L, -3); lua_pushstring(L, "res"); lua_pushinteger(L, SPOE_SCOPE_RES); lua_rawset(L, -3); lua_rawset(L, -3); /* scope */ lua_setglobal(L, "spoa"); return 1; } static int ps_lua_load_file(struct worker *w, const char *file) { int error; /* Load the file and check syntax */ error = luaL_loadfile(L, file); if (error) { fprintf(stderr, "lua syntax error: %s\n", lua_tostring(L, -1)); return 0; } /* If no syntax error where detected, execute the code. */ error = lua_pcall(L, 0, LUA_MULTRET, 0); switch (error) { case LUA_OK: break; case LUA_ERRRUN: fprintf(stderr, "lua runtime error: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); return 0; case LUA_ERRMEM: fprintf(stderr, "lua out of memory error\n"); return 0; case LUA_ERRERR: fprintf(stderr, "lua message handler error: %s\n", lua_tostring(L, 0)); lua_pop(L, 1); return 0; case LUA_ERRGCMM: fprintf(stderr, "lua garbage collector error: %s\n", lua_tostring(L, 0)); lua_pop(L, 1); return 0; default: fprintf(stderr, "lua unknown error: %s\n", lua_tostring(L, 0)); lua_pop(L, 1); return 0; } return 1; } static int ps_lua_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args) { long lua_ref = (long)ref; int ret; char *msg_fmt = NULL; const char *msg; int i; char ipbuf[64]; /* Restore function in the stack */ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_ref); /* convert args in lua mode */ lua_newtable(L); for (i = 0; i < nargs; i++) { lua_newtable(L); lua_pushstring(L, "name"); lua_pushlstring(L, args[i].name.str, args[i].name.len); lua_rawset(L, -3); /* Push name */ lua_pushstring(L, "value"); switch (args[i].value.type) { case SPOE_DATA_T_NULL: lua_pushnil(L); break; case SPOE_DATA_T_BOOL: lua_pushboolean(L, args[i].value.u.boolean); break; case SPOE_DATA_T_INT32: lua_pushinteger(L, args[i].value.u.sint32); break; case SPOE_DATA_T_UINT32: lua_pushinteger(L, args[i].value.u.uint32); break; case SPOE_DATA_T_INT64: lua_pushinteger(L, args[i].value.u.sint64); break; case SPOE_DATA_T_UINT64: if (args[i].value.u.uint64 > LLONG_MAX) lua_pushnil(L); else lua_pushinteger(L, args[i].value.u.uint64); break; case SPOE_DATA_T_IPV4: if (inet_ntop(AF_INET, &args[i].value.u.ipv4, ipbuf, 64) == NULL) lua_pushnil(L); else lua_pushstring(L, ipbuf); break; case SPOE_DATA_T_IPV6: if (inet_ntop(AF_INET6, &args[i].value.u.ipv4, ipbuf, 64) == NULL) lua_pushnil(L); else lua_pushstring(L, ipbuf); break; case SPOE_DATA_T_STR: case SPOE_DATA_T_BIN: lua_pushlstring(L, args[i].value.u.buffer.str, args[i].value.u.buffer.len); break; default: lua_pushnil(L); break; } lua_rawset(L, -3); /* Push name */ lua_rawseti(L, -2, i + 1); /* Pusg table in globale table */ } /* execute lua function */ while (1) { ret = lua_resume(L, L, 1); switch (ret) { case LUA_OK: return 1; case LUA_YIELD: DEBUG("Lua yield"); continue; case LUA_ERRMEM: LOG("Lua: Out of memory error"); return 0; case LUA_ERRRUN: msg_fmt = "Lua runtime error"; case LUA_ERRGCMM: msg_fmt = msg_fmt ? msg_fmt : "Lua garbage collector error"; case LUA_ERRERR: msg_fmt = msg_fmt ? msg_fmt : "Lua message handler error"; default: msg_fmt = msg_fmt ? msg_fmt : "Lua unknown error"; msg = lua_tostring(L, -1); if (msg == NULL) msg = "Unknown error"; LOG("%s: %s", msg_fmt, msg); lua_settop(L, 0); return 0; } } return 1; } __attribute__((constructor)) static void __ps_lua_init(void) { ps_register(&ps_lua_bindings_1); ps_register(&ps_lua_bindings_2); }