asterisk/pbx/pbx_lua.c

1707 lines
43 KiB
C
Raw Normal View History

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2007, Digium, Inc.
*
* Matthew Nicholson <mnicholson@digium.com>
*
* 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 <mnicholson@digium.com>
* \brief Lua PBX Switch
*
*/
/*** MODULEINFO
<depend>lua</depend>
<support_level>extended</support_level>
***/
#include "asterisk.h"
#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 "asterisk/paths.h"
#include "asterisk/hashtab.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
static const char *config = "extensions.lua";
static const char *registrar = "pbx_lua";
#ifdef LOW_MEMORY
#define LUA_EXT_DATA_SIZE 256
#else
#define LUA_EXT_DATA_SIZE 8192
#endif
#define LUA_BUF_SIZE 4096
/* This value is used by the lua engine to signal that a Goto or dialplan jump
* was detected. Ensure this value does not conflict with any values dialplan
* applications might return */
#define LUA_GOTO_DETECTED 5
static char *lua_read_extensions_file(lua_State *L, size_t *size, int *file_not_openable);
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_register_switches(lua_State *L);
static int lua_register_hints(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 int lua_check_hangup(lua_State *L);
static int lua_error_function(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);
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);
static void lua_create_hangup_function(lua_State *L);
static void lua_concat_args(lua_State *L, int start, int nargs);
static void lua_state_destroy(void *data);
static void lua_datastore_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
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);
static char *config_file_data = NULL;
static size_t config_file_size = 0;
static struct ast_context *local_contexts = NULL;
static struct ast_hashtab *local_table = NULL;
static const struct ast_datastore_info lua_datastore = {
.type = "lua",
.destroy = lua_state_destroy,
.chan_fixup = lua_datastore_fixup,
};
/*!
* \brief The destructor for lua_datastore
*/
static void lua_state_destroy(void *data)
{
if (data)
lua_close(data);
}
/*!
* \brief The fixup function for the lua_datastore.
* \param data the datastore data, in this case it will be a lua_State
* \param old_chan the channel we are moving from
* \param new_chan the channel we are moving to
*
* This function updates our internal channel pointer.
*/
static void lua_datastore_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
{
lua_State *L = data;
lua_pushlightuserdata(L, new_chan);
lua_setfield(L, LUA_REGISTRYINDEX, "channel");
}
/*!
* \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
*
* This funciton is executed as the '()' operator for apps accessed through the
* 'app' table.
*
* \return LUA error
*
* \code
* app.playback('demo-congrats')
* \endcode
*/
static int lua_pbx_exec(lua_State *L)
{
int res, nargs = lua_gettop(L);
const char *data = "";
char *app_name, *context, *exten;
char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE];
int priority, autoservice;
struct ast_app *app;
struct ast_channel *chan;
lua_getfield(L, 1, "name");
app_name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
if (!(app = pbx_findapp(app_name))) {
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");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
context = ast_strdupa(ast_channel_context(chan));
exten = ast_strdupa(ast_channel_exten(chan));
priority = ast_channel_priority(chan);
lua_concat_args(L, 2, nargs);
data = lua_tostring(L, -1);
ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n",
exten, context, priority,
term_color(tmp, app_name, COLOR_BRCYAN, 0, sizeof(tmp)),
Replace direct access to channel name with accessor functions There are many benefits to making the ast_channel an opaque handle, from increasing maintainability to presenting ways to kill masquerades. This patch kicks things off by taking things a field at a time, renaming the field to '__do_not_use_${fieldname}' and then writing setters/getters and converting the existing code to using them. When all fields are done, we can move ast_channel to a C file from channel.h and lop off the '__do_not_use_'. This patch sets up main/channel_interal_api.c to be the only file that actually accesses the ast_channel's fields directly. The intent would be for any API functions in channel.c to use the accessor functions. No more monkeying around with channel internals. We should use our own APIs. The interesting changes in this patch are the addition of channel_internal_api.c, the moving of the AST_DATA stuff from channel.c to channel_internal_api.c (note: the AST_DATA stuff will have to be reworked to use accessor functions when ast_channel is really opaque), and some re-working of the way channel iterators/callbacks are handled so as to avoid creating fake ast_channels on the stack to pass in matching data by directly accessing fields (since "name" is a stringfield and the fake channel doesn't init the stringfields, you can't use the ast_channel_name_set() function). I went with ast_channel_name(chan) for a getter, and ast_channel_name_set(chan, name) for a setter. The majority of the grunt-work for this change was done by writing a semantic patch using Coccinelle ( http://coccinelle.lip6.fr/ ). Review: https://reviewboard.asterisk.org/r/1655/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@350223 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2012-01-09 22:15:50 +00:00
term_color(tmp2, ast_channel_name(chan), COLOR_BRMAGENTA, 0, sizeof(tmp2)),
term_color(tmp3, data, COLOR_BRMAGENTA, 0, sizeof(tmp3)));
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
autoservice = lua_toboolean(L, -1);
lua_pop(L, 1);
if (autoservice)
ast_autoservice_stop(chan);
res = pbx_exec(chan, app, data);
lua_pop(L, 1); /* pop data */
data = "";
if (autoservice)
ast_autoservice_start(chan);
/* error executing an application, report it */
if (res) {
lua_pushinteger(L, res);
return lua_error(L);
}
if (strcmp(context, ast_channel_context(chan))) {
lua_pushstring(L, context);
lua_pushstring(L, ast_channel_context(chan));
lua_pushliteral(L, "context");
} else if (strcmp(exten, ast_channel_exten(chan))) {
lua_pushstring(L, exten);
lua_pushstring(L, ast_channel_exten(chan));
lua_pushliteral(L, "exten");
} else if (priority != ast_channel_priority(chan)) {
lua_pushinteger(L, priority);
lua_pushinteger(L, ast_channel_priority(chan));
lua_pushliteral(L, "priority");
} else {
/* no goto - restore the original position back
* to lua state, in case this was a recursive dialplan
* call (a dialplan application re-entering dialplan) */
lua_update_registry(L, context, exten, priority);
return 0;
}
/* goto detected - construct error message */
lua_insert(L, -3);
lua_pushliteral(L, " changed from ");
lua_insert(L, -3);
lua_pushliteral(L, " to ");
lua_insert(L, -2);
lua_concat(L, 5);
ast_debug(2, "Goto detected: %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
/* let the lua engine know it needs to return control to the pbx */
lua_pushinteger(L, LUA_GOTO_DETECTED);
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.
*
* \return LUA error
*
* \code
* channel.variable:get()
* \endcode
*/
static int lua_get_variable_value(lua_State *L)
{
struct ast_channel *chan;
char *value = NULL, *name;
char *workspace = ast_alloca(LUA_BUF_SIZE);
int autoservice;
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");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "name");
name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
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, ast_channel_varshead(chan));
}
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.
*
* \return LUA error
*
* \code
* channel.variable:set("value")
* \endcode
*/
static int lua_set_variable_value(lua_State *L)
{
const char *name, *value;
struct ast_channel *chan;
int autoservice;
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");
name = ast_strdupa(lua_tostring(L, -1));
lua_pop(L, 1);
value = luaL_checkstring(L, 2);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
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.
*
* The value on the top of the stack is popped and used as the name.
*
* \param L the lua_State to use
*/
static void lua_push_variable_table(lua_State *L)
{
lua_newtable(L);
luaL_getmetatable(L, "variable");
lua_setmetatable(L, -2);
lua_insert(L, -2); /* move the table after the 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 accessing 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, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
}
/*!
* \brief Create the hangup check function
*
* \param L the lua_State to use
*/
static void lua_create_hangup_function(lua_State *L)
{
lua_pushcfunction(L, &lua_check_hangup);
lua_setglobal(L, "check_hangup");
}
/*!
* \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)
{
struct ast_channel *chan;
const char *name = luaL_checkstring(L, 2);
char *value = NULL;
char *workspace = ast_alloca(LUA_BUF_SIZE);
workspace[0] = '\0';
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_pushvalue(L, 2);
lua_push_variable_table(L);
/* 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, ast_channel_varshead(chan));
}
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)
{
struct ast_channel *chan;
int autoservice;
const char *name = luaL_checkstring(L, 2);
const char *value = luaL_checkstring(L, 3);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
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 Concatenate a list of lua function arguments into a comma separated
* string.
* \param L the lua_State to use
* \param start the index of the first argument
* \param nargs the number of args
*
* The resulting string will be left on the top of the stack.
*/
static void lua_concat_args(lua_State *L, int start, int nargs) {
int concat = 0;
int i = start + 1;
if (start <= nargs && !lua_isnil(L, start)) {
lua_pushvalue(L, start);
concat += 1;
}
for (; i <= nargs; i++) {
if (lua_isnil(L, i)) {
lua_pushliteral(L, ",");
concat += 1;
} else {
lua_pushliteral(L, ",");
lua_pushvalue(L, i);
concat += 2;
}
}
lua_concat(L, concat);
}
/*!
* \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);
/* build a string in the form of "func_name(arg1,arg2,arg3)" */
lua_getfield(L, 1, "name");
lua_pushliteral(L, "(");
lua_concat_args(L, 2, nargs);
lua_pushliteral(L, ")");
lua_concat(L, 4);
lua_push_variable_table(L);
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.
*/
static int lua_autoservice_start(lua_State *L)
{
struct ast_channel *chan;
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
if (lua_toboolean(L, -1)) {
/* autservice already running */
lua_pop(L, 1);
return 0;
}
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
ast_autoservice_start(chan);
lua_pushboolean(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
return 0;
}
/*!
* \brief [lua_CFunction] Tell pbx_lua to stop maintaining 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.
*/
static int lua_autoservice_stop(lua_State *L)
{
struct ast_channel *chan;
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
if (!lua_toboolean(L, -1)) {
/* no autservice running */
lua_pop(L, 1);
return 0;
}
lua_pop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
ast_autoservice_stop(chan);
lua_pushboolean(L, 0);
lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
return 0;
}
/*!
* \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 [lua_CFunction] Check if this channel has been hungup or not (for
* access from lua, don't call directly)
*
* \param L the lua_State to use
*
* \return This function returns true if the channel was hungup
*/
static int lua_check_hangup(lua_State *L)
{
struct ast_channel *chan;
lua_getfield(L, LUA_REGISTRYINDEX, "channel");
chan = lua_touserdata(L, -1);
lua_pop(L, 1);
lua_pushboolean(L, ast_check_hangup(chan));
return 1;
}
/*!
* \brief [lua_CFunction] Handle lua errors (for access from lua, don't call
* directly)
*
* \param L the lua_State to use
*/
static int lua_error_function(lua_State *L)
{
int message_index;
/* pass number arguments right through back to asterisk*/
if (lua_isnumber(L, -1)) {
return 1;
}
/* if we are here then we have a string error message, let's attach a
* backtrace to it */
message_index = lua_gettop(L);
/* prepare to prepend a new line to the traceback */
lua_pushliteral(L, "\n");
lua_getglobal(L, "debug");
if (!lua_istable(L, -1)) {
/* Have no 'debug' table for whatever reason */
lua_pop(L, 2);
/* Original err message is on stack top now */
return 1;
}
lua_getfield(L, -1, "traceback");
if (!lua_isfunction(L, -1)) {
/* Same here for traceback function */
lua_pop(L, 3);
/* Original err message is on stack top now */
return 1;
}
lua_remove(L, -2); /* remove the 'debug' table */
lua_pushvalue(L, message_index);
lua_remove(L, message_index);
lua_pushnumber(L, 2);
lua_call(L, 2, 1);
/* prepend the new line we prepared above */
lua_concat(L, 2);
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)
{
int extensions, extensions_order;
/* create the extensions_order table */
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, "extensions_order");
lua_getfield(L, LUA_REGISTRYINDEX, "extensions_order");
extensions_order = lua_gettop(L);
/* sort each context in the extensions table */
/* load the 'extensions' table */
lua_getglobal(L, "extensions");
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;
int context_order;
/* copy the context_name to be used as the key for the
* context_order table in the extensions_order table later */
lua_pushvalue(L, context_name);
/* create the context_order table */
lua_newtable(L);
context_order = lua_gettop(L);
/* iterate through this context an populate 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;
#if LUA_VERSION_NUM < 502
lua_pushinteger(L, lua_objlen(L, context_order) + 1);
#else
lua_pushinteger(L, lua_rawlen(L, context_order) + 1);
#endif
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 Register dialplan switches for our pbx_lua contexts.
*
* 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_register_switches(lua_State *L)
{
int extensions;
struct ast_context *con = NULL;
/* create the hash table for our contexts */
/* XXX do we ever need to destroy this? pbx_config does not */
if (!local_table)
local_table = ast_hashtab_create(17, ast_hashtab_compare_contexts, ast_hashtab_resize_java, ast_hashtab_newsize_java, ast_hashtab_hash_contexts, 0);
/* load the 'extensions' table */
lua_getglobal(L, "extensions");
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 register a context and
* dialplan switch for each lua context
*/
for (lua_pushnil(L); lua_next(L, extensions); lua_pop(L, 1)) {
int context = lua_gettop(L);
int context_name = context - 1;
const char *context_str = lua_tostring(L, context_name);
/* find or create this context */
con = ast_context_find_or_create(&local_contexts, local_table, context_str, registrar);
if (!con) {
/* remove extensions table and context key and value */
lua_pop(L, 3);
lua_pushstring(L, "Failed to find or create context\n");
return 1;
}
/* register the switch */
if (ast_context_add_switch2(con, "Lua", "", 0, registrar)) {
/* remove extensions table and context key and value */
lua_pop(L, 3);
lua_pushstring(L, "Unable to create switch for context\n");
return 1;
}
}
/* remove the extensions table */
lua_pop(L, 1);
return 0;
}
/*!
* \brief Register dialplan hints for our pbx_lua contexts.
*
* 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_register_hints(lua_State *L)
{
int hints;
struct ast_context *con = NULL;
/* create the hash table for our contexts */
/* XXX do we ever need to destroy this? pbx_config does not */
if (!local_table)
local_table = ast_hashtab_create(17, ast_hashtab_compare_contexts, ast_hashtab_resize_java, ast_hashtab_newsize_java, ast_hashtab_hash_contexts, 0);
/* load the 'hints' table */
lua_getglobal(L, "hints");
hints = lua_gettop(L);
if (lua_isnil(L, -1)) {
/* hints table not found, move along */
lua_pop(L, 1);
return 0;
}
/* iterate through the hints table and register each context and
* the hints that go along with it
*/
for (lua_pushnil(L); lua_next(L, hints); lua_pop(L, 1)) {
int context = lua_gettop(L);
int context_name = context - 1;
const char *context_str = lua_tostring(L, context_name);
/* find or create this context */
con = ast_context_find_or_create(&local_contexts, local_table, context_str, registrar);
if (!con) {
/* remove hints table and context key and value */
lua_pop(L, 3);
lua_pushstring(L, "Failed to find or create context\n");
return 1;
}
/* register each hint */
for (lua_pushnil(L); lua_next(L, context); lua_pop(L, 1)) {
const char *hint_value = lua_tostring(L, -1);
const char *hint_name;
/* the hint value is not a string, ignore it */
if (!hint_value) {
continue;
}
/* copy the name then convert it to a string */
lua_pushvalue(L, -2);
if (!(hint_name = lua_tostring(L, -1))) {
/* ignore non-string value */
lua_pop(L, 1);
continue;
}
if (ast_add_extension2(con, 0, hint_name, PRIORITY_HINT, NULL, NULL, hint_value, NULL, NULL, registrar, NULL, 0)) {
/* remove hints table, hint name, hint value,
* key copy, context name, and contex table */
lua_pop(L, 6);
lua_pushstring(L, "Error creating hint\n");
return 1;
}
/* pop the name copy */
lua_pop(L, 1);
}
}
/* remove the hints table */
lua_pop(L, 1);
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
* \param file_not_openable a pointer to store if config file could be opened
*
* \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, size_t *size, int *file_not_openable)
{
FILE *f;
int error_func;
char *data;
char *path = ast_alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2);
sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config);
if (!(f = fopen(path, "r"))) {
lua_pushstring(L, "cannot open '");
lua_pushstring(L, path);
lua_pushstring(L, "' for reading: ");
lua_pushstring(L, strerror(errno));
lua_concat(L, 4);
*file_not_openable = 1;
return NULL;
}
if (fseek(f, 0l, SEEK_END)) {
fclose(f);
lua_pushliteral(L, "error determining the size of the config file");
return NULL;
}
*size = ftell(f);
if (fseek(f, 0l, SEEK_SET)) {
*size = 0;
fclose(f);
lua_pushliteral(L, "error reading config file");
return NULL;
}
if (!(data = ast_malloc(*size))) {
*size = 0;
fclose(f);
lua_pushstring(L, "not enough memory");
return NULL;
}
if (fread(data, sizeof(char), *size, f) != *size) {
*size = 0;
fclose(f);
lua_pushliteral(L, "problem reading configuration file");
return NULL;
}
fclose(f);
lua_pushcfunction(L, &lua_error_function);
error_func = lua_gettop(L);
if (luaL_loadbuffer(L, data, *size, "extensions.lua")
|| lua_pcall(L, 0, LUA_MULTRET, error_func)
|| lua_sort_extensions(L)
|| lua_register_switches(L)
|| lua_register_hints(L)) {
ast_free(data);
data = NULL;
*size = 0;
}
lua_remove(L, error_func);
return data;
}
/*!
* \brief Load the extensions.lua file from the internal buffer
*
* \param L the lua_State to use
* \param chan channel to work on
*
* 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);
lua_create_hangup_function(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)
{
size_t size = 0;
char *data = NULL;
int file_not_openable = 0;
luaL_openlibs(L);
if (!(data = lua_read_extensions_file(L, &size, &file_not_openable))) {
if (file_not_openable) {
return -1;
}
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;
/* merge our new contexts */
ast_merge_contexts_and_delete(&local_contexts, local_table, registrar);
/* merge_contexts_and_delete will actually, at the correct moment,
set the global dialplan pointers to your local_contexts and local_table.
It then will free up the old tables itself. Just be sure not to
hang onto the pointers. */
local_table = NULL;
local_contexts = NULL;
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;
lua_State *L;
if (!chan) {
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 {
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &lua_datastore, NULL);
ast_channel_unlock(chan);
if (!datastore) {
/* nothing found, allocate a new lua state */
datastore = ast_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_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);
L = datastore->data;
if (lua_load_extensions(L, chan)) {
const char *error = lua_tostring(L, -1);
Replace direct access to channel name with accessor functions There are many benefits to making the ast_channel an opaque handle, from increasing maintainability to presenting ways to kill masquerades. This patch kicks things off by taking things a field at a time, renaming the field to '__do_not_use_${fieldname}' and then writing setters/getters and converting the existing code to using them. When all fields are done, we can move ast_channel to a C file from channel.h and lop off the '__do_not_use_'. This patch sets up main/channel_interal_api.c to be the only file that actually accesses the ast_channel's fields directly. The intent would be for any API functions in channel.c to use the accessor functions. No more monkeying around with channel internals. We should use our own APIs. The interesting changes in this patch are the addition of channel_internal_api.c, the moving of the AST_DATA stuff from channel.c to channel_internal_api.c (note: the AST_DATA stuff will have to be reworked to use accessor functions when ast_channel is really opaque), and some re-working of the way channel iterators/callbacks are handled so as to avoid creating fake ast_channels on the stack to pass in matching data by directly accessing fields (since "name" is a stringfield and the fake channel doesn't init the stringfields, you can't use the ast_channel_name_set() function). I went with ast_channel_name(chan) for a getter, and ast_channel_name_set(chan, name) for a setter. The majority of the grunt-work for this change was done by writing a semantic patch using Coccinelle ( http://coccinelle.lip6.fr/ ). Review: https://reviewboard.asterisk.org/r/1655/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@350223 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2012-01-09 22:15:50 +00:00
ast_log(LOG_ERROR, "Error loading extensions.lua for %s: %s\n", ast_channel_name(chan), error);
ast_channel_lock(chan);
ast_channel_datastore_remove(chan, datastore);
ast_channel_unlock(chan);
ast_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)
{
int res;
lua_State *L;
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;
}
L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
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)
{
int res;
lua_State *L;
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;
}
L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
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)
{
int res;
lua_State *L;
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;
}
L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return 0;
}
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)
{
int res, error_func;
lua_State *L;
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;
}
L = lua_get_state(chan);
if (!L) {
ast_module_user_remove(u);
return -1;
}
lua_pushcfunction(L, &lua_error_function);
error_func = lua_gettop(L);
/* push the extension function onto the stack */
if (!lua_find_extension(L, context, exten, priority, &exists, 1)) {
lua_pop(L, 1); /* pop the debug function */
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_getfield(L, LUA_REGISTRYINDEX, "autoservice");
if (lua_toboolean(L, -1)) {
ast_autoservice_start(chan);
}
lua_pop(L, 1);
lua_update_registry(L, context, exten, priority);
lua_pushstring(L, context);
lua_pushstring(L, exten);
res = lua_pcall(L, 2, 0, error_func);
if (res) {
if (res == LUA_ERRRUN) {
res = -1;
if (lua_isnumber(L, -1)) {
res = lua_tointeger(L, -1);
if (res == LUA_GOTO_DETECTED) {
res = 0;
}
} else if (lua_isstring(L, -1)) {
const char *error = lua_tostring(L, -1);
ast_log(LOG_ERROR, "Error executing lua extension: %s\n", error);
}
} else if (res == LUA_ERRERR) {
res = -1;
ast_log(LOG_ERROR, "Error in the lua error handler (this is probably a bug in pbx_lua)\n");
} else if (res == LUA_ERRMEM) {
res = -1;
ast_log(LOG_ERROR, "Memory allocation error\n");
}
lua_pop(L, 1);
}
lua_remove(L, error_func);
lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
if (lua_toboolean(L, -1)) {
ast_autoservice_stop(chan);
}
lua_pop(L, 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)
{
int context_table, context_order_table;
size_t i;
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);
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 */
context_order_table = lua_gettop(L);
/* step through the extensions looking for a match */
#if LUA_VERSION_NUM < 502
for (i = 1; i < lua_objlen(L, context_order_table) + 1; i++) {
#else
for (i = 1; i < lua_rawlen(L, context_order_table) + 1; i++) {
#endif
int e_index_copy, match = 0;
const char *e;
lua_pushinteger(L, i);
lua_gettable(L, context_order_table);
lua_gettop(L);
/* copy the key at the top of the stack for use later */
lua_pushvalue(L, -1);
e_index_copy = lua_gettop(L);
if (!(e = lua_tostring(L, e_index_copy))) {
lua_pop(L, 2);
continue;
}
/* make sure this is not the 'include' extension */
if (!strcasecmp(e, "include")) {
lua_pop(L, 2);
continue;
}
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) {
/* pop the copy and the extension */
lua_pop(L, 2);
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, 4);
return 0;
}
/* remove the context table, the context order table, the
* extension, and the extension copy (or replace the extension
* with the corresponding function) */
if (push_func) {
lua_pop(L, 1); /* pop the copy */
lua_gettable(L, context_table);
lua_insert(L, -3);
lua_pop(L, 2);
} else {
lua_pop(L, 4);
}
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)
{
int res = AST_MODULE_LOAD_SUCCESS;
int loaded = 0;
lua_State *L = luaL_newstate();
if (!L) {
ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
return AST_MODULE_LOAD_FAILURE;
}
loaded = lua_reload_extensions(L);
if (loaded) {
const char *error = lua_tostring(L, -1);
ast_log(LOG_ERROR, "Error loading extensions.lua: %s\n", error);
if (loaded < 0) {
res = AST_MODULE_LOAD_DECLINE;
} else {
res = AST_MODULE_LOAD_FAILURE;
}
}
lua_close(L);
return res;
}
static int unload_module(void)
{
ast_context_destroy(NULL, registrar);
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)
{
int res;
if ((res = load_or_reload_lua_stuff()))
return res;
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;
}
pbx_lua: fix regression with global sym export and context clash by pbx_config. ASTERISK-23818 (lua contexts being overwritten by contexts of the same name in pbx_config) surfaced because pbx_lua, having the AST_MODFLAG_GLOBAL_SYMBOLS set, was always force loaded before pbx_config. Since I couldn't find any reason for pbx_lua to export it's symbols to the rest of Asterisk, I simply changed the flag to AST_MODFLAG_DEFAULT. Problem solved. What I didn't realize was that the symbols need to be exported not because Asterisk needs them but because any external Lua modules like luasql.mysql need the base Lua language APIs exported (ASTERISK-17279). Back to ASTERISK-23818... It looks like there's an issue in pbx.c where context_merge was only merging includes, switches and ignore patterns if the context was already existing AND has extensions, or if the context was brand new. If pbx_lua is loaded before pbx_config, the context will exist BUT pbx_lua, being implemented as a switch, will never place extensions in it, just the switch statement. The result is that when pbx_config loads, it never merges the switch statement created by pbx_lua into the final context. This patch sets pbx_lua's modflag back to AST_MODFLAG_GLOBAL_SYMBOLS and adds an "else if" in context_merge that catches the case where an existing context has includes, switchs or ingore patterns but no actual extensions. ASTERISK-23818 #close Reported by: Dennis Guse Reported by: Timo Teräs Tested by: George Joseph Review: https://reviewboard.asterisk.org/r/3891/ ........ Merged revisions 420146 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 420147 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 420148 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@420149 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-08-06 16:12:26 +00:00
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Lua PBX Switch",
.support_level = AST_MODULE_SUPPORT_EXTENDED,
.load = load_module,
.unload = unload_module,
.reload = reload,
);