Wed Oct 28 15:48:04 2009

Asterisk developer's documentation


app_externalivr.c File Reference

External IVR application interface. More...

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include "asterisk.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/options.h"

Include dependency graph for app_externalivr.c:

Go to the source code of this file.

Data Structures

struct  gen_state
struct  localuser
 We define a custom "local user" structure because we use it not only for keeping track of what is in use but also for keeping track of who we're dialing. More...
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)
char * description (void)
 Provides a description of the module.
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)
char * key ()
 Returns the ASTERISK_GPL_KEY.
int load_module (void)
 Initialize the module.
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)
int unload_module (void)
 Cleanup all module structures, sockets, etc.
int usecount (void)
 Provides a usecount.

Variables

static const char * app = "ExternalIVR"
static const char * descrip
static struct ast_generator gen
 LOCAL_USER_DECL
static const char * synopsis = "Interfaces with an external IVR application"
static const char * tdesc = "External IVR Interface Application"


Detailed Description

External IVR application interface.

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 67 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 245 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, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log(), ast_read(), ast_set_priority(), AST_STATE_UP, ast_strdupa, ast_strlen_zero(), ast_test_flag, ast_waitfor_nandfds(), localuser::chan, ast_frame::frametype, free, gen, input(), ast_channel::language, list, LOCAL_USER_ADD, LOCAL_USER_REMOVE, LOG_DEBUG, LOG_ERROR, LOG_NOTICE, LOG_WARNING, make_entry(), option_highpriority, send_child_event(), and ast_frame::subclass.

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

char* description ( void   ) 

Provides a description of the module.

Returns:
a short description of your module

Definition at line 580 of file app_externalivr.c.

00581 {
00582    return (char *) tdesc;
00583 }

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

Definition at line 108 of file app_externalivr.c.

References calloc, and gen_state::u.

00109 {
00110    struct localuser *u = params;
00111    struct gen_state *state;
00112 
00113    state = calloc(1, sizeof(*state));
00114 
00115    if (!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(), 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(), 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(), localuser::chan, gen_state::current, gen_closestream(), ast_channel::language, list, LOG_WARNING, gen_state::stream, and gen_state::u.

Referenced by gen_readframe().

00143 {
00144    struct 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, gen_closestream(), gen_nextfile(), list, gen_state::stream, and gen_state::u.

Referenced by gen_generate().

00174 {
00175    struct ast_frame *f = NULL;
00176    struct 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 }

char* key ( void   ) 

Returns the ASTERISK_GPL_KEY.

This returns the ASTERISK_GPL_KEY, signifiying that you agree to the terms of the GPL stated in the ASTERISK_GPL_KEY. Your module will not load if it does not return the EXACT message:

 char *key(void) {
         return ASTERISK_GPL_KEY;
 }

Returns:
ASTERISK_GPL_KEY

Definition at line 594 of file app_externalivr.c.

References ASTERISK_GPL_KEY.

00595 {
00596    return ASTERISK_GPL_KEY;
00597 }

int load_module ( void   ) 

Initialize the module.

This function is called at module load time. Put all code in here that needs to set up your module's hardware, software, registrations, etc.

Returns:
This function should return 0 on success and non-zero on failure. If the module is not loaded successfully, Asterisk will call its unload_module() function.
Initialize the Agents module. This function is being called by Asterisk when loading the module. Among other thing it registers applications, cli commands and reads the cofiguration file.

Returns:
int Always 0.
TE STUFF END

Definition at line 575 of file app_externalivr.c.

References app_exec, and ast_register_application().

00576 {
00577    return ast_register_application(app, app_exec, synopsis, descrip);
00578 }

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

Definition at line 231 of file app_externalivr.c.

References calloc.

Referenced by app_exec().

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

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

Definition at line 93 of file app_externalivr.c.

References ast_chan_log, and LOG_DEBUG.

Referenced by app_exec().

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

int unload_module ( void   ) 

Cleanup all module structures, sockets, etc.

This is called at exit. Any registrations and memory allocations need to be unregistered and free'd here. Nothing else will do these for you (until exit).

Returns:
Zero on success, or non-zero on error.

Definition at line 564 of file app_externalivr.c.

References ast_unregister_application(), and STANDARD_HANGUP_LOCALUSERS.

00565 {
00566    int res;
00567 
00568    res = ast_unregister_application(app);
00569 
00570    STANDARD_HANGUP_LOCALUSERS;
00571 
00572    return res;
00573 }

int usecount ( void   ) 

Provides a usecount.

This function will be called by various parts of asterisk. Basically, all it has to do is to return a usecount when called. You will need to maintain your usecount within the module somewhere. The usecount should be how many channels provided by this module are in use.

Returns:
The module's usecount.

Definition at line 585 of file app_externalivr.c.

References STANDARD_USECOUNT.

00586 {
00587    int res;
00588 
00589    STANDARD_USECOUNT(res);
00590 
00591    return res;
00592 }


Variable Documentation

const char* app = "ExternalIVR" [static]

Definition at line 52 of file app_externalivr.c.

const char* descrip [static]

Definition at line 56 of file app_externalivr.c.

struct ast_generator gen [static]

Definition at line 224 of file app_externalivr.c.

Referenced by app_exec().

Definition at line 84 of file app_externalivr.c.

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

Definition at line 54 of file app_externalivr.c.

const char* tdesc = "External IVR Interface Application" [static]

Definition at line 50 of file app_externalivr.c.


Generated on Wed Oct 28 15:48:04 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6