Mon May 14 04:43:30 2007

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, f, free, gen, input(), LOG_DEBUG, LOG_NOTICE, LOG_WARNING, make_entry(), and send_child_event().

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

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(), ivr_localuser::chan, f, gen_readframe(), LOG_WARNING, and gen_state::sample_queue.

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, 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]

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 573 of file app_externalivr.c.

References app_exec, and ast_register_application().

00574 {
00575    return ast_register_application(app, app_exec, synopsis, descrip);
00576 }

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

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, ivr_localuser::chan, 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 562 of file app_externalivr.c.

References ast_module_user_hangup_all, and ast_unregister_application().

00563 {
00564    int res;
00565 
00566    res = ast_unregister_application(app);
00567 
00568    ast_module_user_hangup_all();
00569 
00570    return res;
00571 }


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(), ast_activate_generator(), 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 Mon May 14 04:43:31 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1