Index: res/res_agi.c =================================================================== --- res/res_agi.c (revision 135479) +++ res/res_agi.c (working copy) @@ -99,6 +99,29 @@ #define AGI_PORT 4573 +enum agi_type { + AGI_NORMAL, + AGI_FAST, + AGI_ASYNC, +}; + +static void agi_channel_datastore_free(void *data); + +struct ast_datastore_info agi_channels_datastore_info = { + .type = "AGI_Channel", + .destroy = agi_channel_datastore_free, +}; + +struct agi_channel { + AST_LIST_ENTRY(agi_channel) list; + /*! Use this pointer with caution */ + struct ast_channel *chan; + enum agi_type type; + char last_command[256]; +}; + +AST_RWLIST_HEAD_STATIC(agi_channels, agi_channel); + enum agi_result { AGI_RESULT_FAILURE = -1, AGI_RESULT_SUCCESS, @@ -113,6 +136,20 @@ AST_THREADSTORAGE(agi_buf); #define AGI_BUF_INITSIZE 256 +static void agi_channel_datastore_free(void *vdata) +{ + struct agi_channel *entry, *data = vdata; + AST_RWLIST_WRLOCK(&agi_channels); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&agi_channels, entry, list) { + if (entry == data) { + AST_RWLIST_REMOVE_CURRENT(list); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END + AST_RWLIST_UNLOCK(&agi_channels); +} + int ast_agi_fdprintf(struct ast_channel *chan, int fd, char *fmt, ...) { int res = 0; @@ -194,7 +231,7 @@ store = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL); ast_channel_unlock(chan); if (!store) { - ast_log(LOG_ERROR, "Hu? datastore disappeared at Async AGI on Channel %s!\n", chan->name); + ast_log(LOG_ERROR, "Huh? datastore disappeared at Async AGI on Channel %s!\n", chan->name); return NULL; } agi_commands = store->data; @@ -238,6 +275,34 @@ return 0; } +static int add_agi_channel(struct ast_channel *chan, struct agi_channel *agi_channel) +{ + struct ast_datastore *datastore; + + datastore = ast_channel_datastore_alloc(&agi_channels_datastore_info, NULL); + if (!datastore) { + return -1; + } + datastore->data = agi_channel; + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + return 0; +} + +static int remove_agi_channel(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + if ((datastore = ast_channel_datastore_find(chan, &agi_channels_datastore_info, NULL))) { + ast_channel_datastore_remove(chan, datastore); + } + + AST_RWLIST_WRLOCK(&agi_channels); + AST_RWLIST_REMOVE(&agi_channels, (struct agi_channel *)datastore->data, list); + AST_RWLIST_UNLOCK(&agi_channels); + return 0; +} + static int add_to_agi(struct ast_channel *chan) { struct ast_datastore *datastore; @@ -355,6 +420,20 @@ static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf, int dead); static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced, int argc, char *argv[]); + +static void agi_set_channel_command(struct ast_channel *chan, const char *cmd) +{ + struct agi_channel *cur; + AST_RWLIST_RDLOCK(&agi_channels); + AST_RWLIST_TRAVERSE(&agi_channels, cur, list) { + if (cur->chan == chan) { + ast_copy_string(cur->last_command, cmd, sizeof(cur->last_command)); + break; + } + } + AST_RWLIST_UNLOCK(&agi_channels); +} + static enum agi_result launch_asyncagi(struct ast_channel *chan, char *argv[], int *efd) { /* This buffer sizes might cause truncation if the AGI command writes more data @@ -384,6 +463,7 @@ char ami_buffer[AMI_BUF_SIZE]; enum agi_result returnstatus = AGI_RESULT_SUCCESS_ASYNC; AGI async_agi; + struct agi_channel agi_channel = { .chan = chan, .type = AGI_ASYNC }; if (efd) { ast_log(LOG_WARNING, "Async AGI does not support Enhanced AGI yet\n"); @@ -396,11 +476,16 @@ return AGI_RESULT_FAILURE; } + if (add_agi_channel(chan, &agi_channel)) { + return AGI_RESULT_FAILURE; + } + /* this pipe allows us to create a "fake" AGI struct to use the AGI commands */ res = pipe(fds); if (res) { ast_log(LOG_ERROR, "failed to create Async AGI pipe\n"); + remove_agi_channel(chan); /* intentionally do not remove datastore, added with add_to_agi(), from channel. It will be removed when the channel is hung up anyways */ @@ -467,6 +552,7 @@ manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: Exec\r\nChannel: %s\r\nCommandID: %s\r\nResult: %s\r\n", chan->name, cmd->cmd_id, ami_buffer); free_agi_cmd(cmd); } else { + agi_set_channel_command(chan, ""); /* no command so far, wait a bit for a frame to read */ res = ast_waitfor(chan, timeout); if (res < 0) { @@ -495,6 +581,7 @@ /* notify manager users this channel cannot be controlled anymore by Async AGI */ manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: End\r\nChannel: %s\r\n", chan->name); + remove_agi_channel(chan); /* close the pipe */ close(fds[0]); @@ -606,10 +693,12 @@ int pid, toast[2], fromast[2], audio[2], res; struct stat st; - if (!strncasecmp(script, "agi://", 6)) + if (!strncasecmp(script, "agi://", 6)) { return launch_netscript(script, argv, fds, efd, opid); - if (!strncasecmp(script, "agi:async", sizeof("agi:async")-1)) + } + if (!strncasecmp(script, "agi:async", strlen("agi:async"))) { return launch_asyncagi(chan, argv, efd); + } if (script[0] != '/') { snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_AGI_DIR, script); @@ -2526,6 +2615,7 @@ char *ami_cmd = ast_strdupa(buf); int command_id = ast_random(), resultcode = 200; + agi_set_channel_command(chan, buf); manager_event(EVENT_FLAG_CALL, "AGIExec", "SubEvent: Start\r\n" "Channel: %s\r\n" @@ -2601,6 +2691,7 @@ /* how many times we'll retry if ast_waitfor_nandfs will return without either channel or file descriptor in case select is interrupted by a system call (EINTR) */ int retry = AGI_NANDFS_RETRY; + struct agi_channel agi_channel = { .chan = chan, .type = agi->fast ? AGI_FAST : AGI_NORMAL }; if (!(readf = fdopen(agi->ctrl, "r"))) { ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n"); @@ -2609,6 +2700,7 @@ close(agi->ctrl); return AGI_RESULT_FAILURE; } + add_agi_channel(chan, &agi_channel); setlinebuf(readf); setup_env(chan, request, agi->fd, (agi->audio > -1), argc, argv); for (;;) { @@ -2710,6 +2802,7 @@ waitpid(pid, status, WNOHANG); } fclose(readf); + remove_agi_channel(chan); return returnstatus; } @@ -2750,6 +2843,39 @@ return CLI_SUCCESS; } +static char *handle_cli_agi_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct agi_channel *cur; + struct ast_channel *chan; + struct ast_datastore *datastore; + switch (cmd) { + case CLI_INIT: + e->command = "agi list"; + e->usage = + "Usage: agi list\n" + " Display a list of channels currently operating with direction from\n" + " AGI and their current status.\n"; + case CLI_GENERATE: + return NULL; + } + + /* Prevent the agi_channels list from changing while we iterate through the channel list */ + AST_RWLIST_RDLOCK(&agi_channels); + for (chan = ast_channel_walk_locked(NULL); chan; chan = ast_channel_walk_locked(chan)) { + if ((datastore = ast_channel_datastore_find(chan, &agi_channels_datastore_info, NULL))) { + cur = datastore->data; + ast_cli(a->fd, "%20.20s %5s %50.50s\n", + cur->chan->name, + cur->type == AGI_ASYNC ? "Async" : cur->type == AGI_FAST ? "Fast" : ast_check_hangup(cur->chan) ? "Dead" : "", + cur->last_command); + break; + } + ast_channel_unlock(chan); + } + AST_RWLIST_UNLOCK(&agi_channels); + return CLI_SUCCESS; +} + /*! \brief Convert string to use HTML escaped characters \note Maybe this should be a generic function? */ @@ -2891,8 +3017,9 @@ ast_log(LOG_WARNING, "AGI requires an argument (script)\n"); return -1; } - if (dead) + if (dead) { ast_debug(3, "Hungup channel detected, running agi in dead mode.\n"); + } ast_copy_string(buf, data, sizeof(buf)); memset(&agi, 0, sizeof(agi)); AST_STANDARD_APP_ARGS(args, tmp); @@ -2986,7 +3113,8 @@ AST_CLI_DEFINE(handle_cli_agi_add_cmd, "Add AGI command to a channel in Async AGI"), AST_CLI_DEFINE(handle_cli_agi_debug, "Enable/Disable AGI debugging"), AST_CLI_DEFINE(handle_cli_agi_show, "List AGI commands or specific help"), - AST_CLI_DEFINE(handle_cli_agi_dump_html, "Dumps a list of AGI commands in HTML format", .deprecate_cmd = &cli_agi_dumphtml_deprecated) + AST_CLI_DEFINE(handle_cli_agi_dump_html, "Dumps a list of AGI commands in HTML format", .deprecate_cmd = &cli_agi_dumphtml_deprecated), + AST_CLI_DEFINE(handle_cli_agi_list, "List channels currently running an AGI"), }; static int unload_module(void)