Thu Oct 9 06:44:45 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  * \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 #include <signal.h>
00035 
00036 #include "asterisk.h"
00037 
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 48394 $")
00039 
00040 #include "asterisk/lock.h"
00041 #include "asterisk/file.h"
00042 #include "asterisk/logger.h"
00043 #include "asterisk/channel.h"
00044 #include "asterisk/pbx.h"
00045 #include "asterisk/module.h"
00046 #include "asterisk/linkedlists.h"
00047 #include "asterisk/app.h"
00048 #include "asterisk/options.h"
00049 
00050 static const char *tdesc = "External IVR Interface Application";
00051 
00052 static const char *app = "ExternalIVR";
00053 
00054 static const char *synopsis = "Interfaces with an external IVR application";
00055 
00056 static const char *descrip = 
00057 "  ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
00058 "and starts a generator on the channel. The generator's play list is\n"
00059 "controlled by the external application, which can add and clear entries\n"
00060 "via simple commands issued over its stdout. The external application\n"
00061 "will receive all DTMF events received on the channel, and notification\n"
00062 "if the channel is hung up. The application will not be forcibly terminated\n"
00063 "when the channel is hung up.\n"
00064 "See doc/README.externalivr for a protocol specification.\n";
00065 
00066 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
00067 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
00068 
00069 struct playlist_entry {
00070    AST_LIST_ENTRY(playlist_entry) list;
00071    char filename[1];
00072 };
00073 
00074 struct localuser {
00075    struct ast_channel *chan;
00076    struct localuser *next;
00077    AST_LIST_HEAD(playlist, playlist_entry) playlist;
00078    AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
00079    int abort_current_sound;
00080    int playing_silence;
00081    int option_autoclear;
00082 };
00083 
00084 LOCAL_USER_DECL;
00085 
00086 struct gen_state {
00087    struct localuser *u;
00088    struct ast_filestream *stream;
00089    struct playlist_entry *current;
00090    int sample_queue;
00091 };
00092 
00093 static void send_child_event(FILE *handle, const char event, const char *data,
00094               const struct ast_channel *chan)
00095 {
00096    char tmp[256];
00097 
00098    if (!data) {
00099       snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
00100    } else {
00101       snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
00102    }
00103 
00104    fprintf(handle, "%s\n", tmp);
00105    ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
00106 }
00107 
00108 static void *gen_alloc(struct ast_channel *chan, void *params)
00109 {
00110    struct localuser *u = params;
00111    struct gen_state *state;
00112 
00113    state = calloc(1, sizeof(*state));
00114 
00115    if (!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 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 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    entry = calloc(1, sizeof(*entry) + strlen(filename) + 10);
00236 
00237    if (!entry)
00238       return NULL;
00239 
00240    strcpy(entry->filename, filename);
00241 
00242    return entry;
00243 }
00244 
00245 static int app_exec(struct ast_channel *chan, void *data)
00246 {
00247    struct localuser *u = NULL;
00248    struct playlist_entry *entry;
00249    const char *args = data;
00250    int child_stdin[2] = { 0,0 };
00251    int child_stdout[2] = { 0,0 };
00252    int child_stderr[2] = { 0,0 };
00253    int res = -1;
00254    int gen_active = 0;
00255    int pid;
00256    char *argv[32];
00257    int argc = 1;
00258    char *buf, *command;
00259    FILE *child_commands = NULL;
00260    FILE *child_errors = NULL;
00261    FILE *child_events = NULL;
00262    sigset_t fullset, oldset;
00263 
00264    LOCAL_USER_ADD(u);
00265 
00266    sigfillset(&fullset);
00267    pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00268 
00269    AST_LIST_HEAD_INIT(&u->playlist);
00270    AST_LIST_HEAD_INIT(&u->finishlist);
00271    u->abort_current_sound = 0;
00272    
00273    if (ast_strlen_zero(args)) {
00274       ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
00275       goto exit;
00276    }
00277 
00278    buf = ast_strdupa(data);
00279    if (!buf) {
00280       ast_log(LOG_ERROR, "Out of memory!\n");
00281       LOCAL_USER_REMOVE(u);
00282       return -1;
00283    }
00284 
00285    argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
00286 
00287    if (pipe(child_stdin)) {
00288       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
00289       goto exit;
00290    }
00291 
00292    if (pipe(child_stdout)) {
00293       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
00294       goto exit;
00295    }
00296 
00297    if (pipe(child_stderr)) {
00298       ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
00299       goto exit;
00300    }
00301 
00302    if (chan->_state != AST_STATE_UP) {
00303       ast_answer(chan);
00304    }
00305 
00306    if (ast_activate_generator(chan, &gen, u) < 0) {
00307       ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
00308       goto exit;
00309    } else
00310       gen_active = 1;
00311 
00312    pid = fork();
00313    if (pid < 0) {
00314       ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
00315       goto exit;
00316    }
00317 
00318    if (!pid) {
00319       /* child process */
00320       int i;
00321 
00322       signal(SIGPIPE, SIG_DFL);
00323       pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00324 
00325       if (option_highpriority)
00326          ast_set_priority(0);
00327 
00328       dup2(child_stdin[0], STDIN_FILENO);
00329       dup2(child_stdout[1], STDOUT_FILENO);
00330       dup2(child_stderr[1], STDERR_FILENO);
00331       for (i = STDERR_FILENO + 1; i < 1024; i++)
00332          close(i);
00333       execv(argv[0], argv);
00334       fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
00335       _exit(1);
00336    } else {
00337       /* parent process */
00338       int child_events_fd = child_stdin[1];
00339       int child_commands_fd = child_stdout[0];
00340       int child_errors_fd = child_stderr[0];
00341       struct ast_frame *f;
00342       int ms;
00343       int exception;
00344       int ready_fd;
00345       int waitfds[2] = { child_errors_fd, child_commands_fd };
00346       struct ast_channel *rchan;
00347 
00348       pthread_sigmask(SIG_SETMASK, &oldset, NULL);
00349 
00350       close(child_stdin[0]);
00351       child_stdin[0] = 0;
00352       close(child_stdout[1]);
00353       child_stdout[1] = 0;
00354       close(child_stderr[1]);
00355       child_stderr[1] = 0;
00356 
00357       if (!(child_events = fdopen(child_events_fd, "w"))) {
00358          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
00359          goto exit;
00360       }
00361 
00362       if (!(child_commands = fdopen(child_commands_fd, "r"))) {
00363          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
00364          goto exit;
00365       }
00366 
00367       if (!(child_errors = fdopen(child_errors_fd, "r"))) {
00368          ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
00369          goto exit;
00370       }
00371 
00372       setvbuf(child_events, NULL, _IONBF, 0);
00373       setvbuf(child_commands, NULL, _IONBF, 0);
00374       setvbuf(child_errors, NULL, _IONBF, 0);
00375 
00376       res = 0;
00377 
00378       while (1) {
00379          if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
00380             ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
00381             res = -1;
00382             break;
00383          }
00384 
00385          if (ast_check_hangup(chan)) {
00386             ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
00387             send_child_event(child_events, 'H', NULL, chan);
00388             res = -1;
00389             break;
00390          }
00391 
00392          ready_fd = 0;
00393          ms = 100;
00394          errno = 0;
00395          exception = 0;
00396 
00397          rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
00398 
00399          if (!AST_LIST_EMPTY(&u->finishlist)) {
00400             AST_LIST_LOCK(&u->finishlist);
00401             while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
00402                send_child_event(child_events, 'F', entry->filename, chan);
00403                free(entry);
00404             }
00405             AST_LIST_UNLOCK(&u->finishlist);
00406          }
00407 
00408          if (rchan) {
00409             /* the channel has something */
00410             f = ast_read(chan);
00411             if (!f) {
00412                ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
00413                send_child_event(child_events, 'H', NULL, chan);
00414                res = -1;
00415                break;
00416             }
00417 
00418             if (f->frametype == AST_FRAME_DTMF) {
00419                send_child_event(child_events, f->subclass, NULL, chan);
00420                if (u->option_autoclear) {
00421                   if (!u->abort_current_sound && !u->playing_silence)
00422                      send_child_event(child_events, 'T', NULL, chan);
00423                   AST_LIST_LOCK(&u->playlist);
00424                   while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00425                      send_child_event(child_events, 'D', entry->filename, chan);
00426                      free(entry);
00427                   }
00428                   if (!u->playing_silence)
00429                      u->abort_current_sound = 1;
00430                   AST_LIST_UNLOCK(&u->playlist);
00431                }
00432             } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00433                ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
00434                send_child_event(child_events, 'H', NULL, chan);
00435                ast_frfree(f);
00436                res = -1;
00437                break;
00438             }
00439             ast_frfree(f);
00440          } else if (ready_fd == child_commands_fd) {
00441             char input[1024];
00442 
00443             if (exception || feof(child_commands)) {
00444                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00445                res = -1;
00446                break;
00447             }
00448 
00449             if (!fgets(input, sizeof(input), child_commands))
00450                continue;
00451 
00452             command = ast_strip(input);
00453 
00454             ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
00455 
00456             if (strlen(input) < 4)
00457                continue;
00458 
00459             if (input[0] == 'S') {
00460                if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00461                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00462                   send_child_event(child_events, 'Z', NULL, chan);
00463                   strcpy(&input[2], "exception");
00464                }
00465                if (!u->abort_current_sound && !u->playing_silence)
00466                   send_child_event(child_events, 'T', NULL, chan);
00467                AST_LIST_LOCK(&u->playlist);
00468                while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
00469                   send_child_event(child_events, 'D', entry->filename, chan);
00470                   free(entry);
00471                }
00472                if (!u->playing_silence)
00473                   u->abort_current_sound = 1;
00474                entry = make_entry(&input[2]);
00475                if (entry)
00476                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00477                AST_LIST_UNLOCK(&u->playlist);
00478             } else if (input[0] == 'A') {
00479                if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
00480                   ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
00481                   send_child_event(child_events, 'Z', NULL, chan);
00482                   strcpy(&input[2], "exception");
00483                }
00484                entry = make_entry(&input[2]);
00485                if (entry) {
00486                   AST_LIST_LOCK(&u->playlist);
00487                   AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
00488                   AST_LIST_UNLOCK(&u->playlist);
00489                }
00490             } else if (input[0] == 'H') {
00491                ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
00492                send_child_event(child_events, 'H', NULL, chan);
00493                break;
00494             } else if (input[0] == 'O') {
00495                if (!strcasecmp(&input[2], "autoclear"))
00496                   u->option_autoclear = 1;
00497                else if (!strcasecmp(&input[2], "noautoclear"))
00498                   u->option_autoclear = 0;
00499                else
00500                   ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
00501             }
00502          } else if (ready_fd == child_errors_fd) {
00503             char input[1024];
00504 
00505             if (exception || feof(child_errors)) {
00506                ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
00507                res = -1;
00508                break;
00509             }
00510 
00511             if (fgets(input, sizeof(input), child_errors)) {
00512                command = ast_strip(input);
00513                ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
00514             }
00515          } else if ((ready_fd < 0) && ms) { 
00516             if (errno == 0 || errno == EINTR)
00517                continue;
00518 
00519             ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
00520             break;
00521          }
00522       }
00523    }
00524 
00525  exit:
00526    if (gen_active)
00527       ast_deactivate_generator(chan);
00528 
00529    if (child_events)
00530       fclose(child_events);
00531 
00532    if (child_commands)
00533       fclose(child_commands);
00534 
00535    if (child_errors)
00536       fclose(child_errors);
00537 
00538    if (child_stdin[0])
00539       close(child_stdin[0]);
00540 
00541    if (child_stdin[1])
00542       close(child_stdin[1]);
00543 
00544    if (child_stdout[0])
00545       close(child_stdout[0]);
00546 
00547    if (child_stdout[1])
00548       close(child_stdout[1]);
00549 
00550    if (child_stderr[0])
00551       close(child_stderr[0]);
00552 
00553    if (child_stderr[1])
00554       close(child_stderr[1]);
00555 
00556    while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
00557       free(entry);
00558 
00559    LOCAL_USER_REMOVE(u);
00560 
00561    return res;
00562 }
00563 
00564 int unload_module(void)
00565 {
00566    int res;
00567 
00568    res = ast_unregister_application(app);
00569 
00570    STANDARD_HANGUP_LOCALUSERS;
00571 
00572    return res;
00573 }
00574 
00575 int load_module(void)
00576 {
00577    return ast_register_application(app, app_exec, synopsis, descrip);
00578 }
00579 
00580 char *description(void)
00581 {
00582    return (char *) tdesc;
00583 }
00584 
00585 int usecount(void)
00586 {
00587    int res;
00588 
00589    STANDARD_USECOUNT(res);
00590 
00591    return res;
00592 }
00593 
00594 char *key()
00595 {
00596    return ASTERISK_GPL_KEY;
00597 }

Generated on Thu Oct 9 06:44:45 2008 for Asterisk - the Open Source PBX by  doxygen 1.5.1