Sat Mar 24 23:25:59 2007

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  * \ingroup applications
00027  */
00028 
00029 #include <stdlib.h>
00030 #include <stdio.h>
00031 #include <string.h>
00032 #include <unistd.h>
00033 #include <errno.h>
00034 
00035 #include "asterisk.h"
00036 
00037 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 7634 $")
00038 
00039 #include "asterisk/lock.h"
00040 #include "asterisk/file.h"
00041 #include "asterisk/logger.h"
00042 #include "asterisk/channel.h"
00043 #include "asterisk/pbx.h"
00044 #include "asterisk/module.h"
00045 #include "asterisk/linkedlists.h"
00046 #include "asterisk/app.h"
00047 
00048 static const char *tdesc = "External IVR Interface Application";
00049 
00050 static const char *app = "ExternalIVR";
00051 
00052 static const char *synopsis = "Interfaces with an external IVR application";
00053 
00054 static const char *descrip = 
00055 "  ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
00056 "and starts a generator on the channel. The generator's play list is\n"
00057 "controlled by the external application, which can add and clear entries\n"
00058 "via simple commands issued over its stdout. The external application\n"
00059 "will receive all DTMF events received on the channel, and notification\n"
00060 "if the channel is hung up. The application will not be forcibly terminated\n"
00061 "when the channel is hung up.\n"
00062 "See doc/README.externalivr for a protocol specification.\n";
00063 
00064 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
00065 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
00066 
00067 struct playlist_entry {
00068    AST_LIST_ENTRY(playlist_entry) list;
00069    char filename[1];
00070 };
00071 
00072 struct localuser {
00073    struct ast_channel *chan;
00074    struct localuser *next;
00075    AST_LIST_HEAD(playlist, playlist_entry) playlist;
00076    AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
00077    int abort_current_sound;
00078    int playing_silence;
00079    int option_autoclear;
00080 };
00081 
00082 LOCAL_USER_DECL;
00083 
00084 struct gen_state {
00085    struct localuser *u;
00086    struct ast_filestream *stream;
00087    struct playlist_entry *current;
00088    int sample_queue;
00089 };
00090 
00091 static void send_child_event(FILE *handle, const char event, const char *data,
00092               const struct ast_channel *chan)
00093 {
00094    char tmp[256];
00095 
00096    if (!data) {
00097       snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00098    } else {
00099       snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00100    }
00101 
00102    fprintf(handle, "%s\n", tmp);
00103    ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00104 }
00105 
00106 static void *gen_alloc(struct ast_channel *chan, void *params)
00107 {
00108    struct localuser *u = params;
00109    struct gen_state *state;
00110 
00111    state = calloc(1, sizeof(*state));
00112 
00113    if (!state)
00114       return NULL;
00115 
00116    state->u = u;
00117 
00118    return state;
00119 }
00120 
00121 static void gen_closestream(struct gen_state *state)
00122 {
00123    if (!state->stream)
00124       return;
00125 
00126    ast_closestream(state->stream);
00127    state->u->chan->stream = NULL;
00128    state->stream = NULL;
00129 }
00130 
00131 static void gen_release(struct ast_channel *chan, void *data)
00132 {
00133    struct gen_state *state = data;
00134 
00135    gen_closestream(state);
00136    free(data);
00137 }
00138 
00139 /* caller has the playlist locked */
00140 static int gen_nextfile(struct gen_state *state)
00141 {
00142    struct localuser *u = state->u;
00143    char *file_to_stream;
00144    
00145    u->abort_current_sound = 0;
00146    u->playing_silence = 0;
00147    gen_closestream(state);
00148 
00149    while (!state->stream) {
00150       state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
00151       if (state->current) {
00152          file_to_stream = state->current->filename;
00153       } else {
00154          file_to_stream = "silence-10";
00155          u->playing_silence = 1;
00156       }
00157 
00158       if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
00159          ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
00160          if (!u->playing_silence) {
00161             continue;
00162          } else { 
00163             break;
00164          }
00165       }
00166    }
00167 
00168    return (!state->stream);
00169 }
00170 
00171 static struct ast_frame *gen_readframe(struct gen_state *state)
00172 {
00173    struct ast_frame *f = NULL;
00174    struct localuser *u = state->u;
00175    
00176    if (u->abort_current_sound ||
00177        (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
00178       gen_closestream(state);
00179       AST_LIST_LOCK(&u->playlist);
00180       gen_nextfile(state);
00181       AST_LIST_UNLOCK(&u->playlist);
00182    }
00183 
00184    if (!(state->stream && (f = ast_readframe(state->stream)))) {
00185       if (state->current) {
00186          AST_LIST_LOCK(&u->finishlist);
00187          AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
00188          AST_LIST_UNLOCK(&u->finishlist);
00189          state->current = NULL;
00190       }
00191       if (!gen_nextfile(state))
00192          f = ast_readframe(state->stream);
00193    }
00194 
00195    return f;
00196 }
00197 
00198 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
00199 {
00200    struct gen_state *state = data;
00201    struct ast_frame *f = NULL;
00202    int res = 0;
00203 
00204    state->sample_queue += samples;
00205 
00206    while (state->sample_queue > 0) {
00207       if (!(f = gen_readframe(state)))
00208          return -1;
00209 
00210       res = ast_write(chan, f);
00211       ast_frfree(f);
00212       if (res < 0) {
00213          ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
00214          return -1;
00215       }
00216       state->sample_queue -= f->samples;
00217    }
00218 
00219    return res;
00220 }
00221 
00222 static struct ast_generator gen =
00223 {
00224    alloc: gen_alloc,
00225    release: gen_release,
00226    generate: gen_generate,
00227 };
00228 
00229 static struct playlist_entry *make_entry(const char *filename)
00230 {
00231    struct playlist_entry *entry;
00232 
00233    entry = calloc(1, sizeof(*entry) + strlen(filename) + 10);
00234 
00235    if (!entry)
00236       return NULL;
00237 
00238    strcpy(entry->filename, filename);
00239 
00240    return entry;
00241 }
00242 
00243 static int app_exec(struct ast_channel *chan, void *data)
00244 {
00245    struct localuser *u = NULL;
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 
00261    LOCAL_USER_ADD(u);
00262    
00263    AST_LIST_HEAD_INIT(&u->playlist);
00264    AST_LIST_HEAD_INIT(&u->finishlist);
00265    u->abort_current_sound = 0;
00266    
00267    if (ast_strlen_zero(args)) {
00268       ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00269       goto exit;
00270    }
00271 
00272    buf = ast_strdupa(data);
00273    if (!buf) {
00274       ast_log(LOG_ERROR, "Out of memory!\n");
00275       LOCAL_USER_REMOVE(u);
00276       return -1;
00277    }
00278 
00279    argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
00280 
00281    if (pipe(child_stdin)) {
00282       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00283       goto exit;
00284    }
00285 
00286    if (pipe(child_stdout)) {
00287       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00288       goto exit;
00289    }
00290 
00291    if (pipe(child_stderr)) {
00292       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00293       goto exit;
00294    }
00295 
00296    if (chan->_state != AST_STATE_UP) {
00297       ast_answer(chan);
00298    }
00299 
00300    if (ast_activate_generator(chan, &gen, u) < 0) {
00301       ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00302       goto exit;
00303    } else
00304       gen_active = 1;
00305 
00306    pid = fork();
00307    if (pid < 0) {
00308       ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00309       goto exit;
00310    }
00311 
00312    if (!pid) {
00313       /* child process */
00314       int i;
00315 
00316       dup2(child_stdin[0], STDIN_FILENO);
00317       dup2(child_stdout[1], STDOUT_FILENO);
00318       dup2(child_stderr[1], STDERR_FILENO);
00319       for (i = STDERR_FILENO + 1; i < 1024; i++)
00320          close(i);
00321       execv(argv[0], argv);
00322       fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
00323       exit(1);
00324    } else {
00325       /* parent process */
00326       int child_events_fd = child_stdin[1];
00327       int child_commands_fd = child_stdout[0];
00328       int child_errors_fd = child_stderr[0];
00329       struct ast_frame *f;
00330       int ms;
00331       int exception;
00332       int ready_fd;
00333       int waitfds[2] = { child_errors_fd, child_commands_fd };
00334       struct ast_channel *rchan;
00335 
00336       close(child_stdin[0]);
00337       child_stdin[0] = 0;
00338       close(child_stdout[1]);
00339       child_stdout[1] = 0;
00340       close(child_stderr[1]);
00341       child_stderr[1] = 0;
00342 
00343       if (!(child_events = fdopen(child_events_fd, "w"))) {
00344          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
00345          goto exit;
00346       }
00347 
00348       if (!(child_commands = fdopen(child_commands_fd, "r"))) {
00349          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
00350          goto exit;
00351       }
00352 
00353       if (!(child_errors = fdopen(child_errors_fd, "r"))) {
00354          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
00355          goto exit;
00356       }
00357 
00358       setvbuf(child_events, NULL, _IONBF, 0);
00359       setvbuf(child_commands, NULL, _IONBF, 0);
00360       setvbuf(child_errors, NULL, _IONBF, 0);
00361 
00362       res = 0;
00363 
00364       while (1) {
00365          if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00366             ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00367             res = -1;
00368             break;
00369          }
00370 
00371          if (ast_check_hangup(chan)) {
00372             ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00373             send_child_event(child_events, 'H', NULL, chan);
00374             res = -1;
00375             break;
00376          }
00377 
00378          ready_fd = 0;
00379          ms = 100;
00380          errno = 0;
00381          exception = 0;
00382 
00383          rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
00384 
00385          if (!AST_LIST_EMPTY(&u->finishlist)) {
00386             AST_LIST_LOCK(&u->finishlist);
00387             while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00388                send_child_event(child_events, 'F', entry->filename, chan);
00389                free(entry);
00390             }
00391             AST_LIST_UNLOCK(&u->finishlist);
00392          }
00393 
00394          if (rchan) {
00395             /* the channel has something */
00396             f = ast_read(chan);
00397             if (!f) {
00398                ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00399                send_child_event(child_events, 'H', NULL, chan);
00400                res = -1;
00401                break;
00402             }
00403 
00404             if (f->frametype == AST_FRAME_DTMF) {
00405                send_child_event(child_events, f->subclass, NULL, chan);
00406                if (u->option_autoclear) {
00407                   if (!u->abort_current_sound && !u->playing_silence)
00408                      send_child_event(child_events, 'T', NULL, chan);
00409                   AST_LIST_LOCK(&u->playlist);
00410                   while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00411                      send_child_event(child_events, 'D', entry->filename, chan);
00412                      free(entry);
00413                   }
00414                   if (!u->playing_silence)
00415                      u->abort_current_sound = 1;
00416                   AST_LIST_UNLOCK(&u->playlist);
00417                }
00418             } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00419                ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00420                send_child_event(child_events, 'H', NULL, chan);
00421                ast_frfree(f);
00422                res = -1;
00423                break;
00424             }
00425             ast_frfree(f);
00426          } else if (ready_fd == child_commands_fd) {
00427             char input[1024];
00428 
00429             if (exception || feof(child_commands)) {
00430                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00431                res = -1;
00432                break;
00433             }
00434 
00435             if (!fgets(input, sizeof(input), child_commands))
00436                continue;
00437 
00438             command = ast_strip(input);
00439 
00440             ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00441 
00442             if (strlen(input) < 4)
00443                continue;
00444 
00445             if (input[0] == 'S') {
00446                if (ast_fileexists(&input[2], NULL, NULL) == -1) {
00447                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00448                   send_child_event(child_events, 'Z', NULL, chan);
00449                   strcpy(&input[2], "exception");
00450                }
00451                if (!u->abort_current_sound && !u->playing_silence)
00452                   send_child_event(child_events, 'T', NULL, chan);
00453                AST_LIST_LOCK(&u->playlist);
00454                while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00455                   send_child_event(child_events, 'D', entry->filename, chan);
00456                   free(entry);
00457                }
00458                if (!u->playing_silence)
00459                   u->abort_current_sound = 1;
00460                entry = make_entry(&input[2]);
00461                if (entry)
00462                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00463                AST_LIST_UNLOCK(&u->playlist);
00464             } else if (input[0] == 'A') {
00465                if (ast_fileexists(&input[2], NULL, NULL) == -1) {
00466                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00467                   send_child_event(child_events, 'Z', NULL, chan);
00468                   strcpy(&input[2], "exception");
00469                }
00470                entry = make_entry(&input[2]);
00471                if (entry) {
00472                   AST_LIST_LOCK(&u->playlist);
00473                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00474                   AST_LIST_UNLOCK(&u->playlist);
00475                }
00476             } else if (input[0] == 'H') {
00477                ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00478                send_child_event(child_events, 'H', NULL, chan);
00479                break;
00480             } else if (input[0] == 'O') {
00481                if (!strcasecmp(&input[2], "autoclear"))
00482                   u->option_autoclear = 1;
00483                else if (!strcasecmp(&input[2], "noautoclear"))
00484                   u->option_autoclear = 0;
00485                else
00486                   ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00487             }
00488          } else if (ready_fd == child_errors_fd) {
00489             char input[1024];
00490 
00491             if (exception || feof(child_errors)) {
00492                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00493                res = -1;
00494                break;
00495             }
00496 
00497             if (fgets(input, sizeof(input), child_errors)) {
00498                command = ast_strip(input);
00499                ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00500             }
00501          } else if ((ready_fd < 0) && ms) { 
00502             if (errno == 0 || errno == EINTR)
00503                continue;
00504 
00505             ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00506             break;
00507          }
00508       }
00509    }
00510 
00511  exit:
00512    if (gen_active)
00513       ast_deactivate_generator(chan);
00514 
00515    if (child_events)
00516       fclose(child_events);
00517 
00518    if (child_commands)
00519       fclose(child_commands);
00520 
00521    if (child_errors)
00522       fclose(child_errors);
00523 
00524    if (child_stdin[0])
00525       close(child_stdin[0]);
00526 
00527    if (child_stdin[1])
00528       close(child_stdin[1]);
00529 
00530    if (child_stdout[0])
00531       close(child_stdout[0]);
00532 
00533    if (child_stdout[1])
00534       close(child_stdout[1]);
00535 
00536    if (child_stderr[0])
00537       close(child_stderr[0]);
00538 
00539    if (child_stderr[1])
00540       close(child_stderr[1]);
00541 
00542    while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00543       free(entry);
00544 
00545    LOCAL_USER_REMOVE(u);
00546 
00547    return res;
00548 }
00549 
00550 int unload_module(void)
00551 {
00552    int res;
00553 
00554    res = ast_unregister_application(app);
00555 
00556    STANDARD_HANGUP_LOCALUSERS;
00557 
00558    return res;
00559 }
00560 
00561 int load_module(void)
00562 {
00563    return ast_register_application(app, app_exec, synopsis, descrip);
00564 }
00565 
00566 char *description(void)
00567 {
00568    return (char *) tdesc;
00569 }
00570 
00571 int usecount(void)
00572 {
00573    int res;
00574 
00575    STANDARD_USECOUNT(res);
00576 
00577    return res;
00578 }
00579 
00580 char *key()
00581 {
00582    return ASTERISK_GPL_KEY;
00583 }

Generated on Sat Mar 24 23:25:59 2007 for Asterisk - the Open Source PBX by  doxygen 1.4.6