Index: apps/app_dynapp.c =================================================================== --- apps/app_dynapp.c (revision 0) +++ apps/app_dynapp.c (revision 0) @@ -0,0 +1,369 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Moises Silva + * + * Moises Silva + * + * 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 + * + * \brief Dynamic dial plan application + * + * \author\verbatim Moises Silva \endverbatim + * + * This application allows to take control of a channel from the console or via AMI + * \ingroup applications + */ + +/*** MODULEINFO + no + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 76703 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/cli.h" +#include "asterisk/manager.h" +#include "asterisk/options.h" + +/* helper struct to represent a queued command (application) to be executed */ +struct pbx_cmd { + struct ast_channel *chan; + char *app; + char *app_data; + AST_LIST_ENTRY(pbx_cmd) entry; +}; + +/* helper struct to keep a list of channels executing DynamicApp and its commands */ +struct chan_entry { + struct ast_channel *chan; + AST_LIST_HEAD(,pbx_cmd) cmds; + AST_LIST_ENTRY(chan_entry) entry; +}; + + +static char *app = "DynamicApp"; +static char *synopsis = "Take control of a channel using the Asterisk Console or the Asterisk Manager Interface."; +static char *descrip = "This application will put the channel to wait for applications to execute.\n" + "You can either send the applications to execute using the Asterisk Manager Interface or the Asterisk Console.\n"; + +static const char mandescr_dynapp[] = +"Description: Add an application to the execute queue of the channel in a DynamicApp loop" +"Variables:\n" +" Channel: Channel that is currently in DynamicApp\n" +" App: Application to execute\n" +" Data: Arguments to application\n" +"\n"; + +static AST_LIST_HEAD(, chan_entry) chans = AST_LIST_HEAD_INIT_VALUE; + + +static struct pbx_cmd *get_cmd(struct chan_entry *centry) +{ + struct pbx_cmd *cmd = NULL; + AST_LIST_LOCK(¢ry->cmds); + cmd = AST_LIST_REMOVE_HEAD(¢ry->cmds, entry); + AST_LIST_UNLOCK(¢ry->cmds); + return cmd; + +} + +static struct chan_entry *get_chan_entry(struct ast_channel *chan) +{ + struct chan_entry *centry = NULL; + /* search in the channels list if this channel exists */ + AST_LIST_LOCK(&chans); + AST_LIST_TRAVERSE(&chans, centry, entry){ + if (centry->chan == chan) { + break; + } + } + AST_LIST_UNLOCK(&chans); + return centry; +} + +static int add_cmd(struct chan_entry *centry, struct ast_channel *chan, const char *app, const char *app_data) +{ + struct pbx_cmd *cmd; + cmd = ast_calloc(1, sizeof(*cmd)); + if (!cmd) { + return -1; + } + cmd->app = ast_strdup(app); + if (!cmd->app) { + ast_free(cmd); + return -1; + } + cmd->app_data = ast_strdup(app_data); + if (!cmd->app_data && app_data) { + ast_free(cmd->app); + ast_free(cmd); + return -1; + } + cmd->chan = chan; + AST_LIST_LOCK(¢ry->cmds); + AST_LIST_INSERT_HEAD(¢ry->cmds, cmd, entry); + AST_LIST_UNLOCK(¢ry->cmds); + return 0; +} + +static void free_cmd(struct pbx_cmd *cmd) +{ + ast_free(cmd->app); + ast_free(cmd->app_data); + ast_free(cmd); +} + +static void remove_fromapp(struct chan_entry *centry) +{ + struct pbx_cmd *cmd; + ast_log(LOG_DEBUG, "removing channel %s from DynamicApp chan list\n", centry->chan->name); + /* remove chan pointer of chans list */ + AST_LIST_LOCK(&chans); + AST_LIST_REMOVE(&chans, centry, entry); + AST_LIST_UNLOCK(&chans); + + /* remove all entries */ + AST_LIST_LOCK(¢ry->cmds); + AST_LIST_TRAVERSE_SAFE_BEGIN(¢ry->cmds, cmd, entry) { + AST_LIST_REMOVE_CURRENT(entry); + free_cmd(cmd); + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(¢ry->cmds); + + /* free the channel entry */ + ast_free(centry); +} + +static struct chan_entry *add_toapp(struct ast_channel *chan) +{ + struct chan_entry *centry; + centry = ast_calloc(1, sizeof(*centry)); + if (!centry) { + return NULL; + } + centry->chan = chan; + AST_LIST_LOCK(&chans); + AST_LIST_INSERT_HEAD(&chans, centry, entry); + AST_LIST_UNLOCK(&chans); + return centry; +} + +/*! + * \brief CLI command to add applications to execute in DynamicApp + * \param e + * \param cmd + * \param a + * + * \retval CLI_SUCCESS on success + * \retval NULL when init or tab completion is used +*/ +static char *handle_add_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel *chan; + struct chan_entry *centry; + switch (cmd) { + case CLI_INIT: + e->command = "dynapp"; + e->usage = "Usage: dynapp \n" + " Execute an application dynamically in the specified channel\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 1) + return ast_complete_channels(a->line, a->word, a->pos, a->n, 1); + return NULL; + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + chan = ast_get_channel_by_name_locked(a->argv[1]); + if (!chan) { + ast_log(LOG_WARNING, "channel %s does not exists or cannot lock it\n", a->argv[1]); + return CLI_FAILURE; + } + /* we should make sure we don't have a TOCTTOU bug here and centry disappears ( because of channel going out of DynamicApp ) + while we add the cmd entry, shouldn't we? */ + if (!(centry = get_chan_entry(chan))) { + ast_log(LOG_WARNING, "Channel %s is not in DynamicApp\n", chan->name); + ast_channel_unlock(chan); + return CLI_FAILURE; + } + if (add_cmd(centry, chan, a->argv[2], ((a->argc >= 4) ? a->argv[3] : NULL))) { + ast_log(LOG_WARNING, "failed to add to queue\n"); + ast_channel_unlock(chan); + return CLI_FAILURE; + } + ast_log(LOG_DEBUG, "Added command %s to channel %s queue\n", a->argv[2], chan->name); + ast_channel_unlock(chan); + return CLI_SUCCESS; +} + +/*! + * \brief Add a new command to execute by the DynamicApp application + * \param s + * \param m + * + * It will append the application to the specified channel's queue + * if the channel is not inside DynamicApp application it will return an error + * \retval 0 on success or incorrect use + * \retval 1 on failure to add the command ( most likely because the channel is not on DynamicApp loop ) +*/ +static int action_add_cmd(struct mansession *s, const struct message *m) +{ + const char *channel = astman_get_header(m, "Channel"); + const char *app = astman_get_header(m, "App"); + const char *data = astman_get_header(m, "Data"); + struct ast_channel *chan; + struct chan_entry *centry; + char buf[256]; + + if (!channel || !app) { + astman_send_error(s, m, "Both, Channel and App are *required*"); + return 0; + } + + chan = ast_get_channel_by_name_locked(channel); + if (!chan) { + snprintf(buf, sizeof(buf), "Channel %s does not exists or cannot get its lock", channel); + astman_send_error(s, m, buf); + return 1; + } + if (!(centry = get_chan_entry(chan))) { + snprintf(buf, sizeof(buf), "Channel %s is not running on DynamicApp", chan->name); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + return 1; + } + /* we should make sure we don't have a TOCTTOU bug here and centry disappears ( because of channel going out of DynamicApp ) + while we add the cmd entry, shouldn't we? */ + if (add_cmd(centry, chan, app, data)) { + snprintf(buf, sizeof(buf), "Failed to add command to channel %s queue", chan->name); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + return 1; + } + astman_send_ack(s, m, "Added command to channel queue"); + ast_channel_unlock(chan); + return 0; +} + +static int app_exec(struct ast_channel *chan, void *data) +{ + struct ast_frame *f; + struct pbx_cmd *cmd; + struct chan_entry *centry; + int res; int timeout = 100; struct ast_app *app; + + /* this keep track of channels in DynamicApp. add_toapp will return us a pointer to the new entry added in the list + that entry is used to retrieve commands from the queue + */ + if (!(centry = add_toapp(chan))) { + ast_log(LOG_ERROR, "failed to start DynamicApp\n"); + return -1; + } + + /* notify possible manager users of a new channel ready to receive commands */ + manager_event(EVENT_FLAG_CALL, "DynamicAppExec", "SubEvent: Start\r\nChannel: %s\r\n", chan->name); + + while (1) { + if (ast_check_hangup(chan)) { + ast_log(LOG_DEBUG, "ast_check_hangup returned true on chan %s\n", chan->name); + break; + } + cmd = get_cmd(centry); + if (cmd) { + ast_verb(3, "DynamicApp launching application (%s) Options: (%s) on %s\n", cmd->app, cmd->app_data, chan->name); + res = 0; + app = pbx_findapp(cmd->app); + if (!app) /* if the application is not found we just skip this command entry */ + ast_log(LOG_WARNING, "Could not find application (%s)\n", cmd->app); + else + res = pbx_exec(chan, app, cmd->app_data); + free_cmd(cmd); + if (res) /* if we get anything else but success, let the PBX handle this thing */ { + /* remove the channel from the application. Commands still in queue will be lost */ + remove_fromapp(centry); + /* notify manager users this channel cannot be controlled anymore in DynamicApp */ + manager_event(EVENT_FLAG_CALL, "DynamicAppExec", "SubEvent: End\r\nChannel: %s\r\n", chan->name); + return res; + } + } else { + res = ast_waitfor(chan, timeout); + if (res < 0) { + ast_log(LOG_DEBUG, "ast_waitfor returned <= 0 on chan %s\n", chan->name); + break; + } + if (res == 0) + continue; + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "No frame read on channel %s, going out ...\n", chan->name); + break; + } + if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) { + ast_log(LOG_DEBUG, "Got HANGUP frame on channel %s, going out ...\n", chan->name); + ast_frfree(f); + break; + } + ast_frfree(f); + } + } + + /* remove the channel from the application. Commands still in queue will be lost */ + remove_fromapp(centry); + + /* notify manager users this channel cannot be controlled anymore in DynamicApp */ + manager_event(EVENT_FLAG_CALL, "DynamicAppExec", "SubEvent: End\r\nChannel: %s\r\n", chan->name); + + return 0; +} + +static struct ast_cli_entry cli_cmds[] = { + AST_CLI_DEFINE(handle_add_cmd, "Add an application to a channel in DynamicApp") +}; + +static int unload_module(void) +{ + ast_unregister_application(app); + ast_cli_unregister_multiple(cli_cmds, sizeof(cli_cmds)/sizeof(struct ast_cli_entry)); + ast_manager_unregister("AddDynApp"); + return 0; +} + +static int load_module(void) +{ + if (ast_register_application(app, app_exec, synopsis, descrip)) + return AST_MODULE_LOAD_DECLINE; + ast_manager_register2("AddDynApp", EVENT_FLAG_CALL, action_add_cmd, "Add an application to execute by DynamicApp", mandescr_dynapp); + ast_cli_register_multiple(cli_cmds, sizeof(cli_cmds)/sizeof(struct ast_cli_entry)); + return AST_MODULE_LOAD_SUCCESS; +} + + + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DynamicApp");