Wed Oct 28 11:45:25 2009

Asterisk developer's documentation


app_externalivr.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Kevin P. Fleming <kpfleming@digium.com>
00007  *
00008  * Portions taken from the file-based music-on-hold work
00009  * created by Anthony Minessale II in res_musiconhold.c
00010  *
00011  * See http://www.asterisk.org for more information about
00012  * the Asterisk project. Please do not directly contact
00013  * any of the maintainers of this project for assistance;
00014  * the project provides a web site, mailing lists and IRC
00015  * channels for your use.
00016  *
00017  * This program is free software, distributed under the terms of
00018  * the GNU General Public License Version 2. See the LICENSE file
00019  * at the top of the source tree.
00020  */
00021 
00022 /*! \file
00023  *
00024  * \brief External IVR application interface
00025  *
00026  * \author Kevin P. Fleming <kpfleming@digium.com>
00027  *
00028  * \note Portions taken from the file-based music-on-hold work
00029  * created by Anthony Minessale II in res_musiconhold.c
00030  *
00031  * \ingroup applications
00032  */
00033 
00034 /*** MODULEINFO
00035    <depend>working_fork</depend>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 195317 $")
00041 
00042 #include <signal.h>
00043 #ifdef HAVE_CAP
00044 #include <sys/capability.h>
00045 #endif /* HAVE_CAP */
00046 
00047 #include "asterisk/lock.h"
00048 #include "asterisk/file.h"
00049 #include "asterisk/channel.h"
00050 #include "asterisk/pbx.h"
00051 #include "asterisk/module.h"
00052 #include "asterisk/linkedlists.h"
00053 #include "asterisk/app.h"
00054 #include "asterisk/utils.h"
00055 
00056 static const char *app = "ExternalIVR";
00057 
00058 static const char *synopsis = "Interfaces with an external IVR application";
00059 
00060 static const char *descrip =
00061 "  ExternalIVR(command[,arg[,arg...]]): Forks a process to run the supplied command,\n"
00062 "and starts a generator on the channel. The generator's play list is\n"
00063 "controlled by the external application, which can add and clear entries\n"
00064 "via simple commands issued over its stdout. The external application\n"
00065 "will receive all DTMF events received on the channel, and notification\n"
00066 "if the channel is hung up. The application will not be forcibly terminated\n"
00067 "when the channel is hung up.\n"
00068 "See doc/externalivr.txt for a protocol specification.\n";
00069 
00070 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
00071 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
00072 
00073 struct playlist_entry {
00074    AST_LIST_ENTRY(playlist_entry) list;
00075    char filename[1];
00076 };
00077 
00078 struct ivr_localuser {
00079    struct ast_channel *chan;
00080    AST_LIST_HEAD(playlist, playlist_entry) playlist;
00081    AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
00082    int abort_current_sound;
00083    int playing_silence;
00084    int option_autoclear;
00085 };
00086 
00087 
00088 struct gen_state {
00089    struct ivr_localuser *u;
00090    struct ast_filestream *stream;
00091    struct playlist_entry *current;
00092    int sample_queue;
00093 };
00094 
00095 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
00096               int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
00097               const char *args);
00098 
00099 static void send_eivr_event(FILE *handle, const char event, const char *data,
00100    const struct ast_channel *chan)
00101 {
00102    char tmp[256];
00103 
00104    if (!data) {
00105       snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00106    } else {
00107       snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00108    }
00109 
00110    fprintf(handle, "%s\n", tmp);
00111    if (option_debug)
00112       ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00113 }
00114 
00115 static void *gen_alloc(struct ast_channel *chan, void *params)
00116 {
00117    struct ivr_localuser *u = params;
00118    struct gen_state *state;
00119 
00120    if (!(state = ast_calloc(1, sizeof(*state))))
00121       return NULL;
00122 
00123    state->u = u;
00124 
00125    return state;
00126 }
00127 
00128 static void gen_closestream(struct gen_state *state)
00129 {
00130    if (!state->stream)
00131       return;
00132 
00133    ast_closestream(state->stream);
00134    state->u->chan->stream = NULL;
00135    state->stream = NULL;
00136 }
00137 
00138 static void gen_release(struct ast_channel *chan, void *data)
00139 {
00140    struct gen_state *state = data;
00141 
00142    gen_closestream(state);
00143    ast_free(data);
00144 }
00145 
00146 /* caller has the playlist locked */
00147 static int gen_nextfile(struct gen_state *state)
00148 {
00149    struct ivr_localuser *u = state->u;
00150    char *file_to_stream;
00151 
00152    u->abort_current_sound = 0;
00153    u->playing_silence = 0;
00154    gen_closestream(state);
00155 
00156    while (!state->stream) {
00157       state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
00158       if (state->current) {
00159          file_to_stream = state->current->filename;
00160       } else {
00161          file_to_stream = "silence/10";
00162          u->playing_silence = 1;
00163       }
00164 
00165       if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
00166          ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
00167          if (!u->playing_silence) {
00168             continue;
00169          } else {
00170             break;
00171          }
00172       }
00173    }
00174 
00175    return (!state->stream);
00176 }
00177 
00178 static struct ast_frame *gen_readframe(struct gen_state *state)
00179 {
00180    struct ast_frame *f = NULL;
00181    struct ivr_localuser *u = state->u;
00182 
00183    if (u->abort_current_sound ||
00184       (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
00185       gen_closestream(state);
00186       AST_LIST_LOCK(&u->playlist);
00187       gen_nextfile(state);
00188       AST_LIST_UNLOCK(&u->playlist);
00189    }
00190 
00191    if (!(state->stream && (f = ast_readframe(state->stream)))) {
00192       if (state->current) {
00193          AST_LIST_LOCK(&u->finishlist);
00194          AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
00195          AST_LIST_UNLOCK(&u->finishlist);
00196          state->current = NULL;
00197       }
00198       if (!gen_nextfile(state))
00199          f = ast_readframe(state->stream);
00200    }
00201 
00202    return f;
00203 }
00204 
00205 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
00206 {
00207    struct gen_state *state = data;
00208    struct ast_frame *f = NULL;
00209    int res = 0;
00210 
00211    state->sample_queue += samples;
00212 
00213    while (state->sample_queue > 0) {
00214       if (!(f = gen_readframe(state)))
00215          return -1;
00216 
00217       res = ast_write(chan, f);
00218       ast_frfree(f);
00219       if (res < 0) {
00220          ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
00221          return -1;
00222       }
00223       state->sample_queue -= f->samples;
00224    }
00225 
00226    return res;
00227 }
00228 
00229 static struct ast_generator gen =
00230 {
00231    alloc: gen_alloc,
00232    release: gen_release,
00233    generate: gen_generate,
00234 };
00235 
00236 static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
00237 {
00238    /* original input data: "G,var1,var2," */
00239    /* data passed as "data":  "var1,var2" */
00240 
00241    char *inbuf, *variable;
00242    const char *value;
00243    int j;
00244    struct ast_str *newstring = ast_str_alloca(outbuflen); 
00245 
00246    outbuf[0] = '\0';
00247 
00248    for (j = 1, inbuf = data; ; j++) {
00249       variable = strsep(&inbuf, ",");
00250       if (variable == NULL) {
00251          int outstrlen = strlen(outbuf);
00252          if(outstrlen && outbuf[outstrlen - 1] == ',') {
00253             outbuf[outstrlen - 1] = 0;
00254          }
00255          break;
00256       }
00257       
00258       value = pbx_builtin_getvar_helper(chan, variable);
00259       if(!value)
00260          value = "";
00261       ast_str_append(&newstring, 0, "%s=%s,", variable, value);
00262       ast_copy_string(outbuf, newstring->str, outbuflen);
00263    }
00264 };
00265 
00266 static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
00267 {
00268    char *value;
00269 
00270    char *inbuf = ast_strdupa(data), *variable;
00271 
00272    for (variable = strsep(&inbuf, ","); variable; variable = strsep(&inbuf, ",")) {
00273       ast_debug(1, "Setting up a variable: %s\n", variable);
00274       /* variable contains "varname=value" */
00275       value = strchr(variable, '=');
00276       if (!value) {
00277          value = "";
00278       } else {
00279          *value++ = '\0';
00280       }
00281       pbx_builtin_setvar_helper(chan, variable, value);
00282    }
00283 };
00284 
00285 static struct playlist_entry *make_entry(const char *filename)
00286 {
00287    struct playlist_entry *entry;
00288 
00289    if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
00290       return NULL;
00291 
00292    strcpy(entry->filename, filename);
00293 
00294    return entry;
00295 }
00296 
00297 static int app_exec(struct ast_channel *chan, void *data)
00298 {
00299    struct playlist_entry *entry;
00300    int child_stdin[2] = { 0,0 };
00301    int child_stdout[2] = { 0,0 };
00302    int child_stderr[2] = { 0,0 };
00303    int res = -1;
00304    int gen_active = 0;
00305    int pid;
00306    char *buf, *comma_delim_argbuf;
00307    struct ivr_localuser foo = {
00308       .playlist = AST_LIST_HEAD_INIT_VALUE,
00309       .finishlist = AST_LIST_HEAD_INIT_VALUE,
00310    };
00311    struct ivr_localuser *u = &foo;
00312    sigset_t fullset, oldset;
00313    AST_DECLARE_APP_ARGS(args,
00314       AST_APP_ARG(cmd)[32];
00315    );
00316 
00317    sigfillset(&fullset);
00318    pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00319 
00320    u->abort_current_sound = 0;
00321    u->chan = chan;
00322 
00323    if (ast_strlen_zero(data)) {
00324       ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00325       return -1;
00326    }
00327 
00328    buf = ast_strdupa(data);
00329    AST_STANDARD_APP_ARGS(args, buf);
00330 
00331    /* copy args and replace commas with pipes */
00332    comma_delim_argbuf = ast_strdupa(data);
00333    
00334    if (pipe(child_stdin)) {
00335       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00336       goto exit;
00337    }
00338    if (pipe(child_stdout)) {
00339       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00340       goto exit;
00341    }
00342    if (pipe(child_stderr)) {
00343       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00344       goto exit;
00345    }
00346    if (chan->_state != AST_STATE_UP) {
00347       ast_answer(chan);
00348    }
00349    if (ast_activate_generator(chan, &gen, u) < 0) {
00350       ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00351       goto exit;
00352    } else
00353       gen_active = 1;
00354 
00355    pid = fork();
00356    if (pid < 0) {
00357       ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00358       goto exit;
00359    }
00360 
00361    if (!pid) {
00362       /* child process */
00363       int i;
00364 #ifdef HAVE_CAP
00365       cap_t cap = cap_from_text("cap_net_admin-eip");
00366 
00367       if (cap_set_proc(cap)) {
00368          /* Careful with order! Logging cannot happen after we close FDs */
00369          ast_log(LOG_WARNING, "Unable to remove capabilities.\n");
00370       }
00371       cap_free(cap);
00372 #endif
00373 
00374       signal(SIGPIPE, SIG_DFL);
00375       pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00376 
00377       if (ast_opt_high_priority)
00378          ast_set_priority(0);
00379 
00380       dup2(child_stdin[0], STDIN_FILENO);
00381       dup2(child_stdout[1], STDOUT_FILENO);
00382       dup2(child_stderr[1], STDERR_FILENO);
00383       for (i = STDERR_FILENO + 1; i < 1024; i++)
00384          close(i);
00385       execv(args.cmd[0], args.cmd);
00386       fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
00387       _exit(1);
00388    } else {
00389       /* parent process */
00390 
00391       close(child_stdin[0]);
00392       child_stdin[0] = 0;
00393       close(child_stdout[1]);
00394       child_stdout[1] = 0;
00395       close(child_stderr[1]);
00396       child_stderr[1] = 0;
00397       res = eivr_comm(chan, u, child_stdin[1], child_stdout[0], child_stderr[0], comma_delim_argbuf);
00398 
00399       exit:
00400       if (gen_active)
00401          ast_deactivate_generator(chan);
00402 
00403       if (child_stdin[0])
00404          close(child_stdin[0]);
00405 
00406       if (child_stdin[1])
00407          close(child_stdin[1]);
00408 
00409       if (child_stdout[0])
00410          close(child_stdout[0]);
00411 
00412       if (child_stdout[1])
00413          close(child_stdout[1]);
00414 
00415       if (child_stderr[0])
00416          close(child_stderr[0]);
00417 
00418       if (child_stderr[1])
00419          close(child_stderr[1]);
00420 
00421       while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00422          ast_free(entry);
00423 
00424       return res;
00425    }
00426 }
00427 
00428 static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
00429             int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
00430             const char *args)
00431 {
00432    struct playlist_entry *entry;
00433    struct ast_frame *f;
00434    int ms;
00435    int exception;
00436    int ready_fd;
00437    int waitfds[2] = { eivr_commands_fd, eivr_errors_fd };
00438    struct ast_channel *rchan;
00439    char *command;
00440    int res = -1;
00441    int test_available_fd = -1;
00442   
00443    FILE *eivr_commands = NULL;
00444    FILE *eivr_errors = NULL;
00445    FILE *eivr_events = NULL;
00446  
00447    if (!(eivr_events = fdopen(eivr_events_fd, "w"))) {
00448       ast_chan_log(LOG_WARNING, chan, "Could not open stream to send events\n");
00449       goto exit;
00450    }
00451    if (!(eivr_commands = fdopen(eivr_commands_fd, "r"))) {
00452       ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive commands\n");
00453       goto exit;
00454    }
00455    if(eivr_errors_fd) {  /*if opening a socket connection, error stream will not be used*/
00456       if (!(eivr_errors = fdopen(eivr_errors_fd, "r"))) {
00457          ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive errors\n");
00458          goto exit;
00459       }
00460    }
00461 
00462    test_available_fd = open("/dev/null", O_RDONLY);
00463  
00464    setvbuf(eivr_events, NULL, _IONBF, 0);
00465    setvbuf(eivr_commands, NULL, _IONBF, 0);
00466    if(eivr_errors)
00467       setvbuf(eivr_errors, NULL, _IONBF, 0);
00468 
00469    res = 0;
00470  
00471    while (1) {
00472       if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00473          ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00474          res = -1;
00475          break;
00476       }
00477       if (ast_check_hangup(chan)) {
00478          ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00479          send_eivr_event(eivr_events, 'H', NULL, chan);
00480          res = -1;
00481          break;
00482       }
00483  
00484       ready_fd = 0;
00485       ms = 100;
00486       errno = 0;
00487       exception = 0;
00488  
00489       rchan = ast_waitfor_nandfds(&chan, 1, waitfds, (eivr_errors_fd == 0) ? 1 : 2, &exception, &ready_fd, &ms);
00490  
00491       if (!AST_LIST_EMPTY(&u->finishlist)) {
00492          AST_LIST_LOCK(&u->finishlist);
00493          while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00494             send_eivr_event(eivr_events, 'F', entry->filename, chan);
00495             ast_free(entry);
00496          }
00497          AST_LIST_UNLOCK(&u->finishlist);
00498       }
00499  
00500       if (rchan) {
00501          /* the channel has something */
00502          f = ast_read(chan);
00503          if (!f) {
00504             ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00505             send_eivr_event(eivr_events, 'H', NULL, chan);
00506             res = -1;
00507             break;
00508          }
00509          if (f->frametype == AST_FRAME_DTMF) {
00510             send_eivr_event(eivr_events, f->subclass, NULL, chan);
00511             if (u->option_autoclear) {
00512                if (!u->abort_current_sound && !u->playing_silence)
00513                   send_eivr_event(eivr_events, 'T', NULL, chan);
00514                AST_LIST_LOCK(&u->playlist);
00515                while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00516                   send_eivr_event(eivr_events, 'D', entry->filename, chan);
00517                   ast_free(entry);
00518                }
00519                if (!u->playing_silence)
00520                   u->abort_current_sound = 1;
00521                AST_LIST_UNLOCK(&u->playlist);
00522             }
00523          } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00524             ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00525             send_eivr_event(eivr_events, 'H', NULL, chan);
00526             ast_frfree(f);
00527             res = -1;
00528             break;
00529          }
00530          ast_frfree(f);
00531       } else if (ready_fd == eivr_commands_fd) {
00532          char input[1024];
00533  
00534          if (exception || (dup2(eivr_commands_fd, test_available_fd) == -1) || feof(eivr_commands)) {
00535             ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00536             res = -1;
00537             break;
00538          }
00539   
00540          if (!fgets(input, sizeof(input), eivr_commands))
00541             continue;
00542  
00543          command = ast_strip(input);
00544   
00545          if (option_debug)
00546             ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00547   
00548          if (strlen(input) < 4)
00549             continue;
00550   
00551          if (input[0] == 'P') {
00552             send_eivr_event(eivr_events, 'P', args, chan);
00553  
00554          } else if (input[0] == 'S') {
00555             if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00556                ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00557                send_eivr_event(eivr_events, 'Z', NULL, chan);
00558                strcpy(&input[2], "exception");
00559             }
00560             if (!u->abort_current_sound && !u->playing_silence)
00561                send_eivr_event(eivr_events, 'T', NULL, chan);
00562             AST_LIST_LOCK(&u->playlist);
00563             while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00564                send_eivr_event(eivr_events, 'D', entry->filename, chan);
00565                ast_free(entry);
00566             }
00567             if (!u->playing_silence)
00568                u->abort_current_sound = 1;
00569             entry = make_entry(&input[2]);
00570             if (entry)
00571                AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00572             AST_LIST_UNLOCK(&u->playlist);
00573          } else if (input[0] == 'A') {
00574             if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00575                ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00576                send_eivr_event(eivr_events, 'Z', NULL, chan);
00577                strcpy(&input[2], "exception");
00578             }
00579             entry = make_entry(&input[2]);
00580             if (entry) {
00581                AST_LIST_LOCK(&u->playlist);
00582                AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00583                AST_LIST_UNLOCK(&u->playlist);
00584             }
00585          } else if (input[0] == 'G') {
00586             /* A get variable message:  "G,variable1,variable2,..." */
00587             char response[2048];
00588 
00589             ast_chan_log(LOG_NOTICE, chan, "Getting a Variable out of the channel: %s\n", &input[2]);
00590             ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
00591             send_eivr_event(eivr_events, 'G', response, chan);
00592          } else if (input[0] == 'V') {
00593             /* A set variable message:  "V,variablename=foo" */
00594             ast_chan_log(LOG_NOTICE, chan, "Setting a Variable up: %s\n", &input[2]);
00595             ast_eivr_setvariable(chan, &input[2]);
00596          } else if (input[0] == 'L') {
00597             ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
00598          } else if (input[0] == 'X') {
00599             ast_chan_log(LOG_NOTICE, chan, "Exiting ExternalIVR: %s\n", &input[2]);
00600             /*! \todo add deprecation debug message for X command here */
00601             res = 0;
00602             break;
00603          } else if (input[0] == 'E') {
00604             ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
00605             send_eivr_event(eivr_events, 'E', NULL, chan);
00606             res = 0;
00607             break;
00608          } else if (input[0] == 'H') {
00609             ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00610             send_eivr_event(eivr_events, 'H', NULL, chan);
00611             res = -1;
00612             break;
00613          } else if (input[0] == 'O') {
00614             if (!strcasecmp(&input[2], "autoclear"))
00615                u->option_autoclear = 1;
00616             else if (!strcasecmp(&input[2], "noautoclear"))
00617                u->option_autoclear = 0;
00618             else
00619                ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00620          }
00621       } else if (eivr_errors_fd && ready_fd == eivr_errors_fd) {
00622          char input[1024];
00623   
00624          if (exception || feof(eivr_errors)) {
00625             ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00626             res = -1;
00627             break;
00628          }
00629          if (fgets(input, sizeof(input), eivr_errors)) {
00630             command = ast_strip(input);
00631             ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00632          }
00633       } else if ((ready_fd < 0) && ms) { 
00634          if (errno == 0 || errno == EINTR)
00635             continue;
00636  
00637          ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00638          break;
00639       }
00640    }
00641   
00642  
00643 exit:
00644  
00645    if (test_available_fd > -1) {
00646       close(test_available_fd);
00647    }
00648 
00649    if (eivr_events)
00650       fclose(eivr_events);
00651  
00652    if (eivr_commands)
00653       fclose(eivr_commands);
00654 
00655    if (eivr_errors)
00656       fclose(eivr_errors);
00657   
00658    return res;
00659  
00660   }
00661 
00662 static int unload_module(void)
00663 {
00664    return ast_unregister_application(app);
00665 }
00666 
00667 static int load_module(void)
00668 {
00669    return ast_register_application(app, app_exec, synopsis, descrip);
00670 }
00671 
00672 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");

Generated on Wed Oct 28 11:45:25 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6