Asterisk.org: Developer Documentation (28 Oct 2009)

Wed Oct 28 15:47:47 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  * \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 }