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

Generated on Wed Mar 4 19:57:58 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7