From bad99f6a2a8b852b5702716a1fcbf1f1e57889f3 Mon Sep 17 00:00:00 2001 From: tilghman Date: Fri, 2 Nov 2007 15:36:34 +0000 Subject: Add pbx_lua as a method of doing extensions Reported by: mnicholson Patch by: mnicholson Closes issue #11140 git-svn-id: http://svn.digium.com/svn/asterisk/trunk@88250 f38db490-d61c-443f-a65b-d21fe96a405b --- pbx/pbx_lua.c | 1290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1290 insertions(+) create mode 100644 pbx/pbx_lua.c (limited to 'pbx/pbx_lua.c') diff --git a/pbx/pbx_lua.c b/pbx/pbx_lua.c new file mode 100644 index 000000000..8c566b11a --- /dev/null +++ b/pbx/pbx_lua.c @@ -0,0 +1,1290 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2007, Digium, Inc. + * + * Matthew Nicholson + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \author Matthew Nicholson + * \brief Lua PBX Switch + * + */ + +/*** MODULEINFO + lua + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include + +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/term.h" + +#include +#include +#include + +static char *config = "extensions.lua"; + +#define LUA_EXT_DATA_SIZE 256 +#define LUA_BUF_SIZE 4096 + +static char *lua_read_extensions_file(lua_State *L, long *size); +static int lua_load_extensions(lua_State *L, struct ast_channel *chan); +static int lua_reload_extensions(lua_State *L); +static void lua_free_extensions(void); +static int lua_sort_extensions(lua_State *L); +static int lua_extension_cmp(lua_State *L); +static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func); +static int lua_pbx_findapp(lua_State *L); +static int lua_pbx_exec(lua_State *L); + +static int lua_get_variable_value(lua_State *L); +static int lua_set_variable_value(lua_State *L); +static int lua_get_variable(lua_State *L); +static int lua_set_variable(lua_State *L); +static int lua_func_read(lua_State *L); + +static int lua_autoservice_start(lua_State *L); +static int lua_autoservice_stop(lua_State *L); +static int lua_autoservice_status(lua_State *L); + +static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority); +static void lua_push_variable_table(lua_State *L, const char *name); +static void lua_create_app_table(lua_State *L); +static void lua_create_channel_table(lua_State *L); +static void lua_create_variable_metatable(lua_State *L); +static void lua_create_application_metatable(lua_State *L); +static void lua_create_autoservice_functions(lua_State *L); + +void lua_state_destroy(void *data); +static lua_State *lua_get_state(struct ast_channel *chan); + +static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); +static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data); + +AST_MUTEX_DEFINE_STATIC(config_file_lock); +char *config_file_data = NULL; +long config_file_size = 0; + +static const struct ast_datastore_info lua_datastore = { + .type = "lua", + .destroy = lua_state_destroy, +}; + + +/*! + * \brief The destructor for lua_datastore + */ +void lua_state_destroy(void *data) +{ + if (data) + lua_close(data); +} + +/*! + * \brief [lua_CFunction] Find an app and return it in a lua table (for access from lua, don't + * call directly) + * + * This function would be called in the following example as it would be found + * in extensions.lua. + * + * \code + * app.dial + * \endcode + */ +static int lua_pbx_findapp(lua_State *L) +{ + const char *app_name = luaL_checkstring(L, 2); + + lua_newtable(L); + + lua_pushstring(L, "name"); + lua_pushstring(L, app_name); + lua_settable(L, -3); + + luaL_getmetatable(L, "application"); + lua_setmetatable(L, -2); + + return 1; +} + +/*! + * \brief [lua_CFunction] This function is part of the 'application' metatable + * and is used to execute applications similar to pbx_exec() (for access from + * lua, don't call directly) + * + * \param L the lua_State to use + * \return nothing + * + * This funciton is executed as the '()' operator for apps accessed through the + * 'app' table. + * + * \code + * app.playback('demo-congrats') + * \endcode + */ +static int lua_pbx_exec(lua_State *L) +{ + int nargs = lua_gettop(L); + char data[LUA_EXT_DATA_SIZE] = ""; + char *data_next = data; + size_t data_left = sizeof(data); + int res; + + lua_getfield(L, 1, "name"); + char *app_name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + struct ast_app *app = pbx_findapp(app_name); + if (!app) { + lua_pushstring(L, "application '"); + lua_pushstring(L, app_name); + lua_pushstring(L, "' not found"); + lua_concat(L, 3); + return lua_error(L); + } + + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + + lua_getfield(L, LUA_REGISTRYINDEX, "context"); + char *context = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "exten"); + char *exten = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "priority"); + int priority = lua_tointeger(L, -1); + lua_pop(L, 1); + + + if (nargs > 1) { + if (!lua_isnil(L, 2)) + ast_build_string(&data_next, &data_left, "%s", luaL_checkstring(L, 2)); + + int i; + for (i = 3; i <= nargs; i++) { + if (lua_isnil(L, i)) + ast_build_string(&data_next, &data_left, ","); + else + ast_build_string(&data_next, &data_left, ",%s", luaL_checkstring(L, i)); + } + } + + char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE]; + ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n", + exten, context, priority, + term_color(tmp, app_name, COLOR_BRCYAN, 0, sizeof(tmp)), + term_color(tmp2, chan->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)), + term_color(tmp3, data, COLOR_BRMAGENTA, 0, sizeof(tmp3))); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + res = pbx_exec(chan, app, data); + + if (autoservice) + ast_autoservice_start(chan); + + /* error executing an application, report it */ + if (res) { + lua_pushinteger(L, res); + return lua_error(L); + } + return 0; +} + +/*! + * \brief [lua_CFunction] Used to get the value of a variable or dialplan + * function (for access from lua, don't call directly) + * + * The value of the variable or function is returned. This function is the + * 'get()' function in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.variable:get() + * \endcode + */ +static int lua_get_variable_value(lua_State *L) +{ + char *value = NULL; + char *workspace = alloca(LUA_BUF_SIZE); + workspace[0] = '\0'; + + if (!lua_istable(L, 1)) { + lua_pushstring(L, "User probably used '.' instead of ':' for retrieving a channel variable value"); + return lua_error(L); + } + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "name"); + char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + /* if this is a dialplan function then use ast_func_read(), otherwise + * use pbx_retrieve_variable() */ + if (!ast_strlen_zero(name) && name[strlen(name) - 1] == ')') { + value = ast_func_read(chan, name, workspace, LUA_BUF_SIZE) ? NULL : workspace; + } else { + pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead); + } + + if (autoservice) + ast_autoservice_start(chan); + + if (value) { + lua_pushstring(L, value); + } else { + lua_pushnil(L); + } + + return 1; +} + +/*! + * \brief [lua_CFunction] Used to set the value of a variable or dialplan + * function (for access from lua, don't call directly) + * + * This function is the 'set()' function in the following example as would be + * seen in extensions.lua. + * + * \code + * channel.variable:set() + * \endcode + */ +static int lua_set_variable_value(lua_State *L) +{ + if (!lua_istable(L, 1)) { + lua_pushstring(L, "User probably used '.' instead of ':' for setting a channel variable"); + return lua_error(L); + } + + lua_getfield(L, 1, "name"); + const char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + const char *value = luaL_checkstring(L, 2); + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + pbx_builtin_setvar_helper(chan, name, value); + + if (autoservice) + ast_autoservice_start(chan); + + return 0; +} + +/*! + * \brief Update the lua registry with the given context, exten, and priority. + * + * \param L the lua_State to use + * \param context the new context + * \param exten the new exten + * \param priority the new priority + */ +static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority) +{ + lua_pushstring(L, context); + lua_setfield(L, LUA_REGISTRYINDEX, "context"); + + lua_pushstring(L, exten); + lua_setfield(L, LUA_REGISTRYINDEX, "exten"); + + lua_pushinteger(L, priority); + lua_setfield(L, LUA_REGISTRYINDEX, "priority"); +} + +/*! + * \brief Push a 'variable' table on the stack for access the channel variable + * with the given name. + * + * \param L the lua_State to use + * \param name the name of the variable + */ +static void lua_push_variable_table(lua_State *L, const char *name) +{ + lua_newtable(L); + luaL_getmetatable(L, "variable"); + lua_setmetatable(L, -2); + + lua_pushstring(L, name); + lua_setfield(L, -2, "name"); + + lua_pushcfunction(L, &lua_get_variable_value); + lua_setfield(L, -2, "get"); + + lua_pushcfunction(L, &lua_set_variable_value); + lua_setfield(L, -2, "set"); +} + +/*! + * \brief Create the global 'app' table for executing applications + * + * \param L the lua_State to use + */ +static void lua_create_app_table(lua_State *L) +{ + lua_newtable(L); + luaL_newmetatable(L, "app"); + + lua_pushstring(L, "__index"); + lua_pushcfunction(L, &lua_pbx_findapp); + lua_settable(L, -3); + + lua_setmetatable(L, -2); + lua_setglobal(L, "app"); +} + +/*! + * \brief Create the global 'channel' table for accesing channel variables + * + * \param L the lua_State to use + */ +static void lua_create_channel_table(lua_State *L) +{ + lua_newtable(L); + luaL_newmetatable(L, "channel_data"); + + lua_pushstring(L, "__index"); + lua_pushcfunction(L, &lua_get_variable); + lua_settable(L, -3); + + lua_pushstring(L, "__newindex"); + lua_pushcfunction(L, &lua_set_variable); + lua_settable(L, -3); + + lua_setmetatable(L, -2); + lua_setglobal(L, "channel"); +} + +/*! + * \brief Create the 'variable' metatable, used to retrieve channel variables + * + * \param L the lua_State to use + */ +static void lua_create_variable_metatable(lua_State *L) +{ + luaL_newmetatable(L, "variable"); + + lua_pushstring(L, "__call"); + lua_pushcfunction(L, &lua_func_read); + lua_settable(L, -3); + + lua_pop(L, 1); +} + +/*! + * \brief Create the 'application' metatable, used to execute asterisk + * applications from lua + * + * \param L the lua_State to use + */ +static void lua_create_application_metatable(lua_State *L) +{ + luaL_newmetatable(L, "application"); + + lua_pushstring(L, "__call"); + lua_pushcfunction(L, &lua_pbx_exec); + lua_settable(L, -3); + + lua_pop(L, 1); +} + +/*! + * \brief Create the autoservice functions + * + * \param L the lua_State to use + */ +static void lua_create_autoservice_functions(lua_State *L) +{ + lua_pushcfunction(L, &lua_autoservice_start); + lua_setglobal(L, "autoservice_start"); + + lua_pushcfunction(L, &lua_autoservice_stop); + lua_setglobal(L, "autoservice_stop"); + + lua_pushcfunction(L, &lua_autoservice_status); + lua_setglobal(L, "autoservice_status"); + + lua_pushboolean(L, 0); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); +} + +/*! + * \brief [lua_CFunction] Return a lua 'variable' object (for access from lua, don't call + * directly) + * + * This function is called to lookup a variable construct a 'variable' object. + * It would be called in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.variable + * \endcode + */ +static int lua_get_variable(lua_State *L) +{ + char *name = ast_strdupa(luaL_checkstring(L, 2)); + char *value = NULL; + char *workspace = alloca(LUA_BUF_SIZE); + workspace[0] = '\0'; + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_push_variable_table(L, name); + + /* if this is not a request for a dialplan funciton attempt to retrieve + * the value of the variable */ + if (!ast_strlen_zero(name) && name[strlen(name) - 1] != ')') { + pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead); + } + + if (value) { + lua_pushstring(L, value); + lua_setfield(L, -2, "value"); + } + + return 1; +} + +/*! + * \brief [lua_CFunction] Set the value of a channel variable or dialplan + * function (for access from lua, don't call directly) + * + * This function is called to set a variable or dialplan function. It would be + * called in the following example as would be seen in extensions.lua. + * + * \code + * channel.variable = "value" + * \endcode + */ +static int lua_set_variable(lua_State *L) +{ + const char *name = luaL_checkstring(L, 2); + const char *value = luaL_checkstring(L, 3); + + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + int autoservice = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (autoservice) + ast_autoservice_stop(chan); + + pbx_builtin_setvar_helper(chan, name, value); + + if (autoservice) + ast_autoservice_start(chan); + + return 0; +} + +/*! + * \brief [lua_CFunction] Create a 'variable' object for accessing a dialplan + * function (for access from lua, don't call directly) + * + * This function is called to create a 'variable' object to access a dialplan + * function. It would be called in the following example as would be seen in + * extensions.lua. + * + * \code + * channel.func("arg1", "arg2", "arg3") + * \endcode + * + * To actually do anything with the resulting value you must use the 'get()' + * and 'set()' methods (the reason is the resulting value is not a value, but + * an object in the form of a lua table). + */ +static int lua_func_read(lua_State *L) +{ + int nargs = lua_gettop(L); + char fullname[LUA_EXT_DATA_SIZE] = ""; + char *fullname_next = fullname; + size_t fullname_left = sizeof(fullname); + + lua_getfield(L, 1, "name"); + char *name = ast_strdupa(lua_tostring(L, -1)); + lua_pop(L, 1); + + ast_build_string(&fullname_next, &fullname_left, "%s(", name); + + if (nargs > 1) { + if (!lua_isnil(L, 2)) + ast_build_string(&fullname_next, &fullname_left, "%s", luaL_checkstring(L, 2)); + + int i; + for (i = 3; i <= nargs; i++) { + if (lua_isnil(L, i)) + ast_build_string(&fullname_next, &fullname_left, ","); + else + ast_build_string(&fullname_next, &fullname_left, ",%s", luaL_checkstring(L, i)); + } + } + + ast_build_string(&fullname_next, &fullname_left, ")"); + + lua_push_variable_table(L, fullname); + + return 1; +} + +/*! + * \brief [lua_CFunction] Tell pbx_lua to maintain an autoservice on this + * channel (for access from lua, don't call directly) + * + * \param L the lua_State to use + * + * This function will set a flag that will cause pbx_lua to maintain an + * autoservice on this channel. The autoservice will automatically be stopped + * and restarted before calling applications and functions. + * + * \return This function returns the result of the ast_autoservice_start() + * function as a boolean to its lua caller. + */ +static int lua_autoservice_start(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + int res = ast_autoservice_start(chan); + + lua_pushboolean(L, !res); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); + + lua_pushboolean(L, !res); + return 1; +} + +/*! + * \brief [lua_CFunction] Tell pbx_lua to stop maintaning an autoservice on + * this channel (for access from lua, don't call directly) + * + * \param L the lua_State to use + * + * This function will stop any autoservice running and turn off the autoservice + * flag. If this function returns false, it's probably because no autoservice + * was running to begin with. + * + * \return This function returns the result of the ast_autoservice_stop() + * function as a boolean to its lua caller. + */ +static int lua_autoservice_stop(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "channel"); + struct ast_channel *chan = lua_touserdata(L, -1); + lua_pop(L, 1); + + int res = ast_autoservice_stop(chan); + + lua_pushboolean(L, 0); + lua_setfield(L, LUA_REGISTRYINDEX, "autoservice"); + + lua_pushboolean(L, !res); + return 1; +} + +/*! + * \brief [lua_CFunction] Get the status of the autoservice flag (for access + * from lua, don't call directly) + * + * \param L the lua_State to use + * + * \return This function returns the status of the autoservice flag as a + * boolean to its lua caller. + */ +static int lua_autoservice_status(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "autoservice"); + return 1; +} + +/*! + * \brief Store the sort order of each context + + * In the event of an error, an error string will be pushed onto the lua stack. + * + * \retval 0 success + * \retval 1 failure + */ +static int lua_sort_extensions(lua_State *L) +{ + /* create the extensions_order table */ + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, "extensions_order"); + lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order"); + int extensions_order = lua_gettop(L); + + /* sort each context in the extensions table */ + /* load the 'extensions' table */ + lua_getglobal(L, "extensions"); + int extensions = lua_gettop(L); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_pushstring(L, "Unable to find 'extensions' table in extensions.lua\n"); + return 1; + } + + /* iterate through the extensions table and create a + * matching table (holding the sort order) in the + * extensions_order table for each context that is found + */ + for (lua_pushnil(L); lua_next(L, extensions); lua_pop(L, 1)) { + int context = lua_gettop(L); + int context_name = context - 1; + + lua_pushvalue(L, context_name); + lua_newtable(L); + int context_order = lua_gettop(L); + + /* iterate through this context an popluate the corrisponding + * table in the extensions_order table */ + for (lua_pushnil(L); lua_next(L, context); lua_pop(L, 1)) { + int exten = lua_gettop(L) - 1; + + lua_pushinteger(L, lua_objlen(L, context_order) + 1); + lua_pushvalue(L, exten); + lua_settable(L, context_order); + } + lua_settable(L, extensions_order); /* put the context_order table in the extensions_order table */ + + /* now sort the new table */ + + /* push the table.sort function */ + lua_getglobal(L, "table"); + lua_getfield(L, -1, "sort"); + lua_remove(L, -2); /* remove the 'table' table */ + + /* push the context_order table */ + lua_pushvalue(L, context_name); + lua_gettable(L, extensions_order); + + /* push the comp function */ + lua_pushcfunction(L, &lua_extension_cmp); + + if (lua_pcall(L, 2, 0, 0)) { + lua_insert(L, -5); + lua_pop(L, 4); + return 1; + } + } + + /* remove the extensions table and the extensions_order table */ + lua_pop(L, 2); + return 0; +} + +/*! + * \brief [lua_CFunction] Compare two extensions (for access from lua, don't + * call directly) + * + * This function returns true if the first extension passed should match after + * the second. It behaves like the '<' operator. + */ +static int lua_extension_cmp(lua_State *L) +{ + const char *a = luaL_checkstring(L, -2); + const char *b = luaL_checkstring(L, -1); + + if (ast_extension_cmp(a, b) == -1) + lua_pushboolean(L, 1); + else + lua_pushboolean(L, 0); + + return 1; +} + +/*! + * \brief Load the extensions.lua file in to a buffer and execute the file + * + * \param L the lua_State to use + * \param size a pointer to store the size of the buffer + * + * \note The caller is expected to free the buffer at some point. + * + * \return a pointer to the buffer + */ +static char *lua_read_extensions_file(lua_State *L, long *size) +{ + char *path = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2); + sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config); + + FILE *f = fopen(path, "r"); + if (!f) { + lua_pushstring(L, "cannot open '"); + lua_pushstring(L, path); + lua_pushstring(L, "' for reading: "); + lua_pushstring(L, strerror(errno)); + lua_concat(L, 4); + + return NULL; + } + + fseek(f, 0l, SEEK_END); + *size = ftell(f); + + fseek(f, 0l, SEEK_SET); + + char *data = ast_malloc(*size); + if (!data) { + *size = 0; + fclose(f); + lua_pushstring(L, "not enough memory"); + return NULL; + } + + fread(data, sizeof(char), *size, f); + fclose(f); + + if (luaL_loadbuffer(L, data, *size, "extensions.lua") + || lua_pcall(L, 0, LUA_MULTRET, 0) + || lua_sort_extensions(L)) { + ast_free(data); + data = NULL; + *size = 0; + } + return data; +} + +/*! + * \brief Load the extensions.lua file from the internal buffer + * + * \param L the lua_State to use + * + * This function also sets up some constructs used by the extensions.lua file. + * + * In the event of an error, an error string will be pushed onto the lua stack. + * + * \retval 0 success + * \retval 1 failure + */ +static int lua_load_extensions(lua_State *L, struct ast_channel *chan) +{ + + /* store a pointer to this channel */ + lua_pushlightuserdata(L, chan); + lua_setfield(L, LUA_REGISTRYINDEX, "channel"); + + luaL_openlibs(L); + + /* load and sort extensions */ + ast_mutex_lock(&config_file_lock); + if (luaL_loadbuffer(L, config_file_data, config_file_size, "extensions.lua") + || lua_pcall(L, 0, LUA_MULTRET, 0) + || lua_sort_extensions(L)) { + ast_mutex_unlock(&config_file_lock); + return 1; + } + ast_mutex_unlock(&config_file_lock); + + /* now we setup special tables and functions */ + + lua_create_app_table(L); + lua_create_channel_table(L); + + lua_create_variable_metatable(L); + lua_create_application_metatable(L); + + lua_create_autoservice_functions(L); + + return 0; +} + +/*! + * \brief Reload the extensions file and update the internal buffers if it + * loads correctly. + * + * \warning This function should not be called on a lua_State returned from + * lua_get_state(). + * + * \param L the lua_State to use (must be freshly allocated with + * luaL_newstate(), don't use lua_get_state()) + */ +static int lua_reload_extensions(lua_State *L) +{ + long size = 0; + char *data = NULL; + + luaL_openlibs(L); + + if (!(data = lua_read_extensions_file(L, &size))) { + return 1; + } + + ast_mutex_lock(&config_file_lock); + + if (config_file_data) + ast_free(config_file_data); + + config_file_data = data; + config_file_size = size; + + ast_mutex_unlock(&config_file_lock); + return 0; +} + +/*! + * \brief Free the internal extensions buffer. + */ +static void lua_free_extensions() +{ + ast_mutex_lock(&config_file_lock); + config_file_size = 0; + ast_free(config_file_data); + ast_mutex_unlock(&config_file_lock); +} + +/*! + * \brief Get the lua_State for this channel + * + * If no channel is passed then a new state is allocated. States with no + * channel assocatied with them should only be used for matching extensions. + * If the channel does not yet have a lua state associated with it, one will be + * created. + * + * \note If no channel was passed then the caller is expected to free the state + * using lua_close(). + * + * \return a lua_State + */ +static lua_State *lua_get_state(struct ast_channel *chan) +{ + struct ast_datastore *datastore = NULL; + if (!chan) { + lua_State *L = luaL_newstate(); + if (!L) { + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return NULL; + } + + if (lua_load_extensions(L, NULL)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error); + lua_close(L); + return NULL; + } + return L; + } else { + datastore = ast_channel_datastore_find(chan, &lua_datastore, NULL); + + if (!datastore) { + /* nothing found, allocate a new lua state */ + datastore = ast_channel_datastore_alloc(&lua_datastore, NULL); + if (!datastore) { + ast_log(LOG_ERROR, "Error allocation channel datastore for lua_State\n"); + return NULL; + } + + datastore->data = luaL_newstate(); + if (!datastore->data) { + ast_channel_datastore_free(datastore); + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return NULL; + } + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + + lua_State *L = datastore->data; + + if (lua_load_extensions(L, chan)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua for %s: %s\n", chan->name, error); + + ast_channel_lock(chan); + ast_channel_datastore_remove(chan, datastore); + ast_channel_unlock(chan); + + ast_channel_datastore_free(datastore); + return NULL; + } + } + + return datastore->data; + } +} + +static int exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &exists, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +static int canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &canmatch, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +static int matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return 0; + } + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return 0; + } + + int res = lua_find_extension(L, context, exten, priority, &matchmore, 0); + + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + + +static int exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data) +{ + struct ast_module_user *u = ast_module_user_add(chan); + if (!u) { + ast_log(LOG_ERROR, "Error adjusting use count, probably could not allocate memory\n"); + return -1; + } + + int res; + + lua_State *L = lua_get_state(chan); + if (!L) { + ast_module_user_remove(u); + return -1; + } + + /* push the extension function onto the stack */ + if (!lua_find_extension(L, context, exten, priority, &exists, 1)) { + ast_log(LOG_ERROR, "Could not find extension %s in context %s\n", exten, context); + if (!chan) lua_close(L); + ast_module_user_remove(u); + return -1; + } + + lua_update_registry(L, context, exten, priority); + + lua_pushstring(L, context); + lua_pushstring(L, exten); + + res = lua_pcall(L, 2, 0, 0); + if (res) { + if (res == LUA_ERRRUN) { + if (lua_isnumber(L, -1)) { + res = lua_tointeger(L, -1); + } else if (lua_isstring(L, -1)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error executing lua extension: %s\n", error); + res = -1; + } + } else { + res = -1; + } + } + if (!chan) lua_close(L); + ast_module_user_remove(u); + return res; +} + +/*! + * \brief Locate an extensions and optionally push the matching function on the + * stack + * + * \param L the lua_State to use + * \param context the context to look in + * \param exten the extension to look up + * \param priority the priority to check, '1' is the only valid priority + * \param func the calling func, used to adjust matching behavior between, + * match, canmatch, and matchmore + * \param push_func whether or not to push the lua function for the given + * extension onto the stack + */ +static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func) +{ + ast_debug(2, "Looking up %s@%s:%i\n", exten, context, priority); + if (priority != 1) + return 0; + + /* load the 'extensions' table */ + lua_getglobal(L, "extensions"); + if (lua_isnil(L, -1)) { + ast_log(LOG_ERROR, "Unable to find 'extensions' table in extensions.lua\n"); + lua_pop(L, 1); + return 0; + } + + /* load the given context */ + lua_getfield(L, -1, context); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + return 0; + } + + /* remove the extensions table */ + lua_remove(L, -2); + + int context_table = lua_gettop(L); + + /* load the extensions order table for this context */ + lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order"); + lua_getfield(L, -1, context); + + lua_remove(L, -2); /* remove the extensions order table */ + + int context_order_table = lua_gettop(L); + + /* step through the extensions looking for a match */ + int i; + for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) { + lua_pushinteger(L, i); + lua_gettable(L, context_order_table); + int e_index = lua_gettop(L); + int isnumber = lua_isnumber(L, e_index); + + const char *e = lua_tostring(L, e_index); + if (!e) { + lua_pop(L, 1); + continue; + } + + /* make sure this is not the 'include' extension */ + if(!strcasecmp(e, "include")) { + lua_pop(L, 1); + continue; + } + + int match = 0; + if (func == &matchmore) + match = ast_extension_close(e, exten, E_MATCHMORE); + else if (func == &canmatch) + match = ast_extension_close(e, exten, E_CANMATCH); + else + match = ast_extension_match(e, exten); + + /* the extension matching functions return 0 on fail, 1 on + * match, 2 on earlymatch */ + + if (!match) { + lua_pop(L, 1); + continue; /* keep trying */ + } + + if (func == &matchmore && match == 2) { + /* We match an extension ending in '!'. The decision in + * this case is final and counts as no match. */ + lua_pop(L, 3); + return 0; + } + + /* remove the context table, the context order table, and the + * extension (or replace the extension with the corisponding + * function) */ + if (push_func) { + /* here we must convert the exten back to an integer + * because lua_tostring will change the value on the + * stack to a string */ + if (isnumber) { + int e_int = lua_tointeger(L, e_index); + lua_pop(L, 1); /* the exten should be the top of the stack */ + lua_pushinteger(L, e_int); + } + lua_gettable(L, context_table); + lua_insert(L, -3); + lua_pop(L, 2); + } else { + lua_pop(L, 3); + } + + return 1; + } + + /* load the includes for this context */ + lua_getfield(L, context_table, "include"); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return 0; + } + + /* remove the context and the order table*/ + lua_remove(L, context_order_table); + lua_remove(L, context_table); + + /* Now try any includes we have in this context */ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *c = lua_tostring(L, -1); + if (!c) + continue; + + if (lua_find_extension(L, c, exten, priority, func, push_func)) { + /* remove the value, the key, and the includes table + * from the stack. Leave the function behind if + * necessary */ + + if (push_func) + lua_insert(L, -4); + + lua_pop(L, 3); + return 1; + } + } + + /* pop the includes table */ + lua_pop(L, 1); + return 0; +} + +static struct ast_switch lua_switch = { + .name = "Lua", + .description = "Lua PBX Switch", + .exists = exists, + .canmatch = canmatch, + .exec = exec, + .matchmore = matchmore, +}; + + +static int load_or_reload_lua_stuff(void) +{ + lua_State *L = luaL_newstate(); + if (!L) { + ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n"); + return AST_MODULE_LOAD_FAILURE; + } + + int res = AST_MODULE_LOAD_SUCCESS; + + if (lua_reload_extensions(L)) { + const char *error = lua_tostring(L, -1); + ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error); + res = AST_MODULE_LOAD_FAILURE; + } + + lua_close(L); + return res; +} + +static int unload_module(void) +{ + ast_unregister_switch(&lua_switch); + lua_free_extensions(); + return 0; +} + +static int reload(void) +{ + return load_or_reload_lua_stuff(); +} + +static int load_module(void) +{ + if (load_or_reload_lua_stuff()) + return AST_MODULE_LOAD_FAILURE; + + if (ast_register_switch(&lua_switch)) { + ast_log(LOG_ERROR, "Unable to register LUA PBX switch\n"); + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Lua PBX Switch", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); + -- cgit v1.2.3