Thu Oct 8 21:57:31 2009

Asterisk developer's documentation


app_externalivr.c File Reference

External IVR application interface. More...

#include "asterisk.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
#include "asterisk/app.h"
#include "asterisk/utils.h"
#include "asterisk/options.h"

Include dependency graph for app_externalivr.c:

Go to the source code of this file.

Data Structures

struct  gen_state
struct  ivr_localuser
struct  playlist_entry

Defines

#define ast_chan_log(level, channel, format,...)   ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)

Functions

static int app_exec (struct ast_channel *chan, void *data)
 AST_MODULE_INFO_STANDARD (ASTERISK_GPL_KEY,"External IVR Interface Application")
static void * gen_alloc (struct ast_channel *chan, void *params)
static void gen_closestream (struct gen_state *state)
static int gen_generate (struct ast_channel *chan, void *data, int len, int samples)
static int gen_nextfile (struct gen_state *state)
static struct ast_framegen_readframe (struct gen_state *state)
static void gen_release (struct ast_channel *chan, void *data)
static int load_module (void)
static struct playlist_entrymake_entry (const char *filename)
static void send_child_event (FILE *handle, const char event, const char *data, const struct ast_channel *chan)
static int unload_module (void)

Variables

static const char * app = "ExternalIVR"
static const char * descrip
static struct ast_generator gen
static const char * synopsis = "Interfaces with an external IVR application"


Detailed Description

External IVR application interface.

Author:
Kevin P. Fleming <kpfleming@digium.com>
Note:
Portions taken from the file-based music-on-hold work created by Anthony Minessale II in res_musiconhold.c

Definition in file app_externalivr.c.


Define Documentation

#define ast_chan_log ( level,
channel,
format,
...   )     ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)

Definition at line 71 of file app_externalivr.c.

Referenced by app_exec(), gen_generate(), gen_nextfile(), and send_child_event().


Function Documentation

static int app_exec ( struct ast_channel chan,
void *  data 
) [static]

Definition at line 243 of file app_externalivr.c.

References ast_channel::_state, ast_activate_generator(), ast_answer(), ast_app_separate_args(), ast_chan_log, ast_check_hangup(), AST_CONTROL_HANGUP, ast_deactivate_generator(), ast_fileexists(), AST_FLAG_ZOMBIE, AST_FRAME_CONTROL, AST_FRAME_DTMF, ast_frfree, AST_LIST_EMPTY, AST_LIST_HEAD_INIT_VALUE, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log(), ast_module_user_add, ast_module_user_remove, ast_opt_high_priority, ast_read(), ast_set_priority(), AST_STATE_UP, ast_strdupa, ast_strlen_zero(), ast_test_flag, ast_waitfor_nandfds(), ivr_localuser::chan, errno, f, ast_frame::frametype, free, gen, input(), LOG_DEBUG, LOG_NOTICE, LOG_WARNING, make_entry(), send_child_event(), and ast_frame::subclass.

00244 {
00245    struct ast_module_user *lu;
00246    struct playlist_entry *entry;
00247    const char *args = data;
00248    int child_stdin[2] = { 0,0 };
00249    int child_stdout[2] = { 0,0 };
00250    int child_stderr[2] = { 0,0 };
00251    int res = -1;
00252    int test_available_fd = -1;
00253    int gen_active = 0;
00254    int pid;
00255    char *argv[32];
00256    int argc = 1;
00257    char *buf, *command;
00258    FILE *child_commands = NULL;
00259    FILE *child_errors = NULL;
00260    FILE *child_events = NULL;
00261    struct ivr_localuser foo = {
00262       .playlist = AST_LIST_HEAD_INIT_VALUE,
00263       .finishlist = AST_LIST_HEAD_INIT_VALUE,
00264    };
00265    struct ivr_localuser *u = &foo;
00266    sigset_t fullset, oldset;
00267 
00268    lu = ast_module_user_add(chan);
00269 
00270    sigfillset(&fullset);
00271    pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00272 
00273    u->abort_current_sound = 0;
00274    u->chan = chan;
00275    
00276    if (ast_strlen_zero(args)) {
00277       ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00278       ast_module_user_remove(lu);
00279       return -1;  
00280    }
00281 
00282    buf = ast_strdupa(data);
00283 
00284    argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
00285 
00286    if (pipe(child_stdin)) {
00287       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00288       goto exit;
00289    }
00290 
00291    if (pipe(child_stdout)) {
00292       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00293       goto exit;
00294    }
00295 
00296    if (pipe(child_stderr)) {
00297       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00298       goto exit;
00299    }
00300 
00301    if (chan->_state != AST_STATE_UP) {
00302       ast_answer(chan);
00303    }
00304 
00305    if (ast_activate_generator(chan, &gen, u) < 0) {
00306       ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00307       goto exit;
00308    } else
00309       gen_active = 1;
00310 
00311    pid = fork();
00312    if (pid < 0) {
00313       ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00314       goto exit;
00315    }
00316 
00317    if (!pid) {
00318       /* child process */
00319       int i;
00320 
00321       signal(SIGPIPE, SIG_DFL);
00322       pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00323 
00324       if (ast_opt_high_priority)
00325          ast_set_priority(0);
00326 
00327       dup2(child_stdin[0], STDIN_FILENO);
00328       dup2(child_stdout[1], STDOUT_FILENO);
00329       dup2(child_stderr[1], STDERR_FILENO);
00330       for (i = STDERR_FILENO + 1; i < 1024; i++)
00331          close(i);
00332       execv(argv[0], argv);
00333       fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
00334       _exit(1);
00335    } else {
00336       /* parent process */
00337       int child_events_fd = child_stdin[1];
00338       int child_commands_fd = child_stdout[0];
00339       int child_errors_fd = child_stderr[0];
00340       struct ast_frame *f;
00341       int ms;
00342       int exception;
00343       int ready_fd;
00344       int waitfds[2] = { child_errors_fd, child_commands_fd };
00345       struct ast_channel *rchan;
00346 
00347       pthread_sigmask(SIG_SETMASK, &oldset, NULL);
00348 
00349       close(child_stdin[0]);
00350       child_stdin[0] = 0;
00351       close(child_stdout[1]);
00352       child_stdout[1] = 0;
00353       close(child_stderr[1]);
00354       child_stderr[1] = 0;
00355 
00356       if (!(child_events = fdopen(child_events_fd, "w"))) {
00357          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
00358          goto exit;
00359       }
00360 
00361       if (!(child_commands = fdopen(child_commands_fd, "r"))) {
00362          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
00363          goto exit;
00364       }
00365 
00366       if (!(child_errors = fdopen(child_errors_fd, "r"))) {
00367          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
00368          goto exit;
00369       }
00370 
00371       test_available_fd = open("/dev/null", O_RDONLY);
00372 
00373       setvbuf(child_events, NULL, _IONBF, 0);
00374       setvbuf(child_commands, NULL, _IONBF, 0);
00375       setvbuf(child_errors, NULL, _IONBF, 0);
00376 
00377       res = 0;
00378 
00379       while (1) {
00380          if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00381             ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00382             res = -1;
00383             break;
00384          }
00385 
00386          if (ast_check_hangup(chan)) {
00387             ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00388             send_child_event(child_events, 'H', NULL, chan);
00389             res = -1;
00390             break;
00391          }
00392 
00393          ready_fd = 0;
00394          ms = 100;
00395          errno = 0;
00396          exception = 0;
00397 
00398          rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
00399 
00400          if (!AST_LIST_EMPTY(&u->finishlist)) {
00401             AST_LIST_LOCK(&u->finishlist);
00402             while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00403                send_child_event(child_events, 'F', entry->filename, chan);
00404                free(entry);
00405             }
00406             AST_LIST_UNLOCK(&u->finishlist);
00407          }
00408 
00409          if (rchan) {
00410             /* the channel has something */
00411             f = ast_read(chan);
00412             if (!f) {
00413                ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00414                send_child_event(child_events, 'H', NULL, chan);
00415                res = -1;
00416                break;
00417             }
00418 
00419             if (f->frametype == AST_FRAME_DTMF) {
00420                send_child_event(child_events, f->subclass, NULL, chan);
00421                if (u->option_autoclear) {
00422                   if (!u->abort_current_sound && !u->playing_silence)
00423                      send_child_event(child_events, 'T', NULL, chan);
00424                   AST_LIST_LOCK(&u->playlist);
00425                   while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00426                      send_child_event(child_events, 'D', entry->filename, chan);
00427                      free(entry);
00428                   }
00429                   if (!u->playing_silence)
00430                      u->abort_current_sound = 1;
00431                   AST_LIST_UNLOCK(&u->playlist);
00432                }
00433             } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00434                ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00435                send_child_event(child_events, 'H', NULL, chan);
00436                ast_frfree(f);
00437                res = -1;
00438                break;
00439             }
00440             ast_frfree(f);
00441          } else if (ready_fd == child_commands_fd) {
00442             char input[1024];
00443 
00444             if (exception || feof(child_commands)) {
00445                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00446                res = -1;
00447                break;
00448             }
00449 
00450             if (!fgets(input, sizeof(input), child_commands))
00451                continue;
00452 
00453             command = ast_strip(input);
00454 
00455             ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00456 
00457             if (strlen(input) < 4)
00458                continue;
00459 
00460             if (input[0] == 'S') {
00461                if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00462                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00463                   send_child_event(child_events, 'Z', NULL, chan);
00464                   strcpy(&input[2], "exception");
00465                }
00466                if (!u->abort_current_sound && !u->playing_silence)
00467                   send_child_event(child_events, 'T', NULL, chan);
00468                AST_LIST_LOCK(&u->playlist);
00469                while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00470                   send_child_event(child_events, 'D', entry->filename, chan);
00471                   free(entry);
00472                }
00473                if (!u->playing_silence)
00474                   u->abort_current_sound = 1;
00475                entry = make_entry(&input[2]);
00476                if (entry)
00477                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00478                AST_LIST_UNLOCK(&u->playlist);
00479             } else if (input[0] == 'A') {
00480                if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00481                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00482                   send_child_event(child_events, 'Z', NULL, chan);
00483                   strcpy(&input[2], "exception");
00484                }
00485                entry = make_entry(&input[2]);
00486                if (entry) {
00487                   AST_LIST_LOCK(&u->playlist);
00488                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00489                   AST_LIST_UNLOCK(&u->playlist);
00490                }
00491             } else if (input[0] == 'H') {
00492                ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00493                send_child_event(child_events, 'H', NULL, chan);
00494                break;
00495             } else if (input[0] == 'O') {
00496                if (!strcasecmp(&input[2], "autoclear"))
00497                   u->option_autoclear = 1;
00498                else if (!strcasecmp(&input[2], "noautoclear"))
00499                   u->option_autoclear = 0;
00500                else
00501                   ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00502             }
00503          } else if (ready_fd == child_errors_fd) {
00504             char input[1024];
00505 
00506             if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) {
00507                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00508                res = -1;
00509                break;
00510             }
00511 
00512             if (fgets(input, sizeof(input), child_errors)) {
00513                command = ast_strip(input);
00514                ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00515             }
00516          } else if ((ready_fd < 0) && ms) { 
00517             if (errno == 0 || errno == EINTR)
00518                continue;
00519 
00520             ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00521             break;
00522          }
00523       }
00524    }
00525 
00526  exit:
00527    if (gen_active)
00528       ast_deactivate_generator(chan);
00529 
00530    if (child_events)
00531       fclose(child_events);
00532 
00533    if (child_commands)
00534       fclose(child_commands);
00535 
00536    if (child_errors)
00537       fclose(child_errors);
00538 
00539    if (test_available_fd > -1) {
00540       close(test_available_fd);
00541    }
00542 
00543    if (child_stdin[0])
00544       close(child_stdin[0]);
00545 
00546    if (child_stdin[1])
00547       close(child_stdin[1]);
00548 
00549    if (child_stdout[0])
00550       close(child_stdout[0]);
00551 
00552    if (child_stdout[1])
00553       close(child_stdout[1]);
00554 
00555    if (child_stderr[0])
00556       close(child_stderr[0]);
00557 
00558    if (child_stderr[1])
00559       close(child_stderr[1]);
00560 
00561    while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00562       free(entry);
00563 
00564    ast_module_user_remove(lu);
00565 
00566    return res;
00567 }

AST_MODULE_INFO_STANDARD ( ASTERISK_GPL_KEY  ,
"External IVR Interface Application"   
)

static void* gen_alloc ( struct ast_channel chan,
void *  params 
) [static]

Definition at line 110 of file app_externalivr.c.

References ast_calloc, and gen_state::u.

00111 {
00112    struct ivr_localuser *u = params;
00113    struct gen_state *state;
00114    
00115    if (!(state = ast_calloc(1, sizeof(*state))))
00116       return NULL;
00117 
00118    state->u = u;
00119 
00120    return state;
00121 }

static void gen_closestream ( struct gen_state state  )  [static]

Definition at line 123 of file app_externalivr.c.

References ast_closestream(), ivr_localuser::chan, ast_channel::stream, gen_state::stream, and gen_state::u.

Referenced by gen_nextfile(), gen_readframe(), and gen_release().

00124 {
00125    if (!state->stream)
00126       return;
00127 
00128    ast_closestream(state->stream);
00129    state->u->chan->stream = NULL;
00130    state->stream = NULL;
00131 }

static int gen_generate ( struct ast_channel chan,
void *  data,
int  len,
int  samples 
) [static]

Definition at line 200 of file app_externalivr.c.

References ast_chan_log, ast_frfree, ast_write(), errno, f, gen_readframe(), LOG_WARNING, gen_state::sample_queue, and ast_frame::samples.

00201 {
00202    struct gen_state *state = data;
00203    struct ast_frame *f = NULL;
00204    int res = 0;
00205 
00206    state->sample_queue += samples;
00207 
00208    while (state->sample_queue > 0) {
00209       if (!(f = gen_readframe(state)))
00210          return -1;
00211 
00212       res = ast_write(chan, f);
00213       ast_frfree(f);
00214       if (res < 0) {
00215          ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
00216          return -1;
00217       }
00218       state->sample_queue -= f->samples;
00219    }
00220 
00221    return res;
00222 }

static int gen_nextfile ( struct gen_state state  )  [static]

Definition at line 142 of file app_externalivr.c.

References ast_chan_log, AST_LIST_REMOVE_HEAD, ast_openstream_full(), ivr_localuser::chan, gen_state::current, errno, gen_closestream(), LOG_WARNING, gen_state::stream, and gen_state::u.

Referenced by gen_readframe().

00143 {
00144    struct ivr_localuser *u = state->u;
00145    char *file_to_stream;
00146    
00147    u->abort_current_sound = 0;
00148    u->playing_silence = 0;
00149    gen_closestream(state);
00150 
00151    while (!state->stream) {
00152       state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
00153       if (state->current) {
00154          file_to_stream = state->current->filename;
00155       } else {
00156          file_to_stream = "silence/10";
00157          u->playing_silence = 1;
00158       }
00159 
00160       if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
00161          ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
00162          if (!u->playing_silence) {
00163             continue;
00164          } else { 
00165             break;
00166          }
00167       }
00168    }
00169 
00170    return (!state->stream);
00171 }

static struct ast_frame* gen_readframe ( struct gen_state state  )  [static, read]

Definition at line 173 of file app_externalivr.c.

References AST_LIST_FIRST, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_readframe(), gen_state::current, f, gen_closestream(), gen_nextfile(), gen_state::stream, and gen_state::u.

Referenced by gen_generate().

00174 {
00175    struct ast_frame *f = NULL;
00176    struct ivr_localuser *u = state->u;
00177    
00178    if (u->abort_current_sound ||
00179        (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
00180       gen_closestream(state);
00181       AST_LIST_LOCK(&u->playlist);
00182       gen_nextfile(state);
00183       AST_LIST_UNLOCK(&u->playlist);
00184    }
00185 
00186    if (!(state->stream && (f = ast_readframe(state->stream)))) {
00187       if (state->current) {
00188          AST_LIST_LOCK(&u->finishlist);
00189          AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
00190          AST_LIST_UNLOCK(&u->finishlist);
00191          state->current = NULL;
00192       }
00193       if (!gen_nextfile(state))
00194          f = ast_readframe(state->stream);
00195    }
00196 
00197    return f;
00198 }

static void gen_release ( struct ast_channel chan,
void *  data 
) [static]

Definition at line 133 of file app_externalivr.c.

References free, and gen_closestream().

00134 {
00135    struct gen_state *state = data;
00136 
00137    gen_closestream(state);
00138    free(data);
00139 }

static int load_module ( void   )  [static]

Definition at line 580 of file app_externalivr.c.

References app_exec, and ast_register_application().

00581 {
00582    return ast_register_application(app, app_exec, synopsis, descrip);
00583 }

static struct playlist_entry* make_entry ( const char *  filename  )  [static, read]

Definition at line 231 of file app_externalivr.c.

References ast_calloc.

Referenced by app_exec().

00232 {
00233    struct playlist_entry *entry;
00234    
00235    if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
00236       return NULL;
00237 
00238    strcpy(entry->filename, filename);
00239 
00240    return entry;
00241 }

static void send_child_event ( FILE *  handle,
const char  event,
const char *  data,
const struct ast_channel chan 
) [static]

Definition at line 95 of file app_externalivr.c.

References ast_chan_log, and LOG_DEBUG.

Referenced by app_exec().

00097 {
00098    char tmp[256];
00099 
00100    if (!data) {
00101       snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00102    } else {
00103       snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00104    }
00105 
00106    fprintf(handle, "%s\n", tmp);
00107    ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00108 }

static int unload_module ( void   )  [static]

Definition at line 569 of file app_externalivr.c.

References ast_module_user_hangup_all, and ast_unregister_application().

00570 {
00571    int res;
00572 
00573    res = ast_unregister_application(app);
00574 
00575    ast_module_user_hangup_all();
00576 
00577    return res;
00578 }


Variable Documentation

const char* app = "ExternalIVR" [static]

Definition at line 56 of file app_externalivr.c.

const char* descrip [static]

Definition at line 60 of file app_externalivr.c.

struct ast_generator gen [static]

Definition at line 224 of file app_externalivr.c.

Referenced by app_exec(), reload_config(), and set_config().

const char* synopsis = "Interfaces with an external IVR application" [static]

Definition at line 58 of file app_externalivr.c.


Generated on Thu Oct 8 21:57:31 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.8