Mon Mar 31 07:37:54 2008

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 #include "asterisk.h"
00035 
00036 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00037 
00038 #include <stdlib.h>
00039 #include <stdio.h>
00040 #include <string.h>
00041 #include <unistd.h>
00042 #include <errno.h>
00043 #include <signal.h>
00044 
00045 #include "asterisk/lock.h"
00046 #include "asterisk/file.h"
00047 #include "asterisk/logger.h"
00048 #include "asterisk/channel.h"
00049 #include "asterisk/pbx.h"
00050 #include "asterisk/module.h"
00051 #include "asterisk/linkedlists.h"
00052 #include "asterisk/app.h"
00053 #include "asterisk/utils.h"
00054 #include "asterisk/options.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 void send_child_event(FILE *handle, const char event, const char *data,
00096               const struct ast_channel *chan)
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 }
00109 
00110 static void *gen_alloc(struct ast_channel *chan, void *params)
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 }
00122 
00123 static void gen_closestream(struct gen_state *state)
00124 {
00125    if (!state->stream)
00126       return;
00127 
00128    ast_closestream(state->stream);
00129    state->u->chan->stream = NULL;
00130    state->stream = NULL;
00131 }
00132 
00133 static void gen_release(struct ast_channel *chan, void *data)
00134 {
00135    struct gen_state *state = data;
00136 
00137    gen_closestream(state);
00138    free(data);
00139 }
00140 
00141 /* caller has the playlist locked */
00142 static int gen_nextfile(struct gen_state *state)
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 }
00172 
00173 static struct ast_frame *gen_readframe(struct gen_state *state)
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 }
00199 
00200 static int gen_generate(struct ast_channel *chan, void *data, int len, int 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 }
00223 
00224 static struct ast_generator gen =
00225 {
00226    alloc: gen_alloc,
00227    release: gen_release,
00228    generate: gen_generate,
00229 };
00230 
00231 static struct playlist_entry *make_entry(const char *filename)
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 }
00242 
00243 static int app_exec(struct ast_channel *chan, void *data)
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 }
00561 
00562 static int unload_module(void)
00563 {
00564    int res;
00565 
00566    res = ast_unregister_application(app);
00567 
00568    ast_module_user_hangup_all();
00569 
00570    return res;
00571 }
00572 
00573 static int load_module(void)
00574 {
00575    return ast_register_application(app, app_exec, synopsis, descrip);
00576 }
00577 
00578 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");

Generated on Mon Mar 31 07:37:54 2008 for Asterisk - the Open Source PBX by  doxygen 1.5.1