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

Generated on Thu May 24 14:21:05 2007 for Asterisk - the Open Source PBX by  doxygen 1.4.7