#include "asterisk.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
#include "asterisk/app.h"
#include "asterisk/utils.h"
#include "asterisk/options.h"
Go to the source code of this file.
Data Structures | |
struct | gen_state |
struct | ivr_localuser |
struct | ivr_localuser::finishlist |
struct | ivr_localuser::playlist |
struct | playlist_entry |
Defines | |
#define | ast_chan_log(level, channel, format,) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__) |
Functions | |
static void | __reg_module (void) |
static void | __unreg_module (void) |
static int | app_exec (struct ast_channel *chan, void *data) |
static void * | gen_alloc (struct ast_channel *chan, void *params) |
static void | gen_closestream (struct gen_state *state) |
static int | gen_generate (struct ast_channel *chan, void *data, int len, int samples) |
static int | gen_nextfile (struct gen_state *state) |
static struct ast_frame * | gen_readframe (struct gen_state *state) |
static void | gen_release (struct ast_channel *chan, void *data) |
static int | load_module (void) |
static struct playlist_entry * | make_entry (const char *filename) |
static void | send_child_event (FILE *handle, const char event, const char *data, const struct ast_channel *chan) |
static int | unload_module (void) |
Variables | |
static struct ast_module_info | __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "External IVR Interface Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "f450f61f60e761b3aa089ebed76ca8a5" , .load = load_module, .unload = unload_module, } |
static const char * | app = "ExternalIVR" |
static const struct ast_module_info * | ast_module_info = &__mod_info |
static const char * | descrip |
static struct ast_generator | gen |
static const char * | synopsis = "Interfaces with an external IVR application" |
Definition in file app_externalivr.c.
#define ast_chan_log | ( | level, | |||
channel, | |||||
format | ) | ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__) |
Definition at line 71 of file app_externalivr.c.
Referenced by app_exec(), gen_generate(), gen_nextfile(), and send_child_event().
static void __reg_module | ( | void | ) | [static] |
Definition at line 585 of file app_externalivr.c.
static void __unreg_module | ( | void | ) | [static] |
Definition at line 585 of file app_externalivr.c.
static int app_exec | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 243 of file app_externalivr.c.
References ast_channel::_state, ivr_localuser::abort_current_sound, ast_activate_generator(), ast_answer(), ast_app_separate_args(), ast_chan_log, ast_check_hangup(), AST_CONTROL_HANGUP, ast_deactivate_generator(), ast_fileexists(), AST_FLAG_ZOMBIE, AST_FRAME_CONTROL, AST_FRAME_DTMF, ast_frfree, AST_LIST_EMPTY, AST_LIST_HEAD_INIT_VALUE, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log(), ast_module_user_add, ast_module_user_remove, ast_opt_high_priority, ast_read(), ast_set_priority(), AST_STATE_UP, ast_strdupa, ast_strip(), ast_strlen_zero(), ast_test_flag, ast_waitfor_nandfds(), ivr_localuser::chan, errno, f, playlist_entry::filename, ivr_localuser::finishlist, free, gen, input(), ast_channel::language, playlist_entry::list, LOG_DEBUG, LOG_NOTICE, LOG_WARNING, make_entry(), ivr_localuser::option_autoclear, ivr_localuser::playing_silence, ivr_localuser::playlist, and send_child_event().
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 }
static void* gen_alloc | ( | struct ast_channel * | chan, | |
void * | params | |||
) | [static] |
Definition at line 110 of file app_externalivr.c.
References ast_calloc, and gen_state::u.
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 }
static void gen_closestream | ( | struct gen_state * | state | ) | [static] |
Definition at line 123 of file app_externalivr.c.
References ast_closestream(), ivr_localuser::chan, ast_channel::stream, gen_state::stream, and gen_state::u.
Referenced by gen_nextfile(), gen_readframe(), and gen_release().
00124 { 00125 if (!state->stream) 00126 return; 00127 00128 ast_closestream(state->stream); 00129 state->u->chan->stream = NULL; 00130 state->stream = NULL; 00131 }
static int gen_generate | ( | struct ast_channel * | chan, | |
void * | data, | |||
int | len, | |||
int | samples | |||
) | [static] |
Definition at line 200 of file app_externalivr.c.
References ast_chan_log, ast_frfree, ast_write(), errno, f, gen_readframe(), LOG_WARNING, and gen_state::sample_queue.
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 }
static int gen_nextfile | ( | struct gen_state * | state | ) | [static] |
Definition at line 142 of file app_externalivr.c.
References ivr_localuser::abort_current_sound, ast_chan_log, AST_LIST_REMOVE_HEAD, ast_openstream_full(), ivr_localuser::chan, gen_state::current, errno, playlist_entry::filename, gen_closestream(), ast_channel::language, playlist_entry::list, LOG_WARNING, ivr_localuser::playing_silence, ivr_localuser::playlist, gen_state::stream, and gen_state::u.
Referenced by gen_readframe().
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 }
Definition at line 173 of file app_externalivr.c.
References ivr_localuser::abort_current_sound, AST_LIST_FIRST, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_readframe(), gen_state::current, f, ivr_localuser::finishlist, gen_closestream(), gen_nextfile(), playlist_entry::list, ivr_localuser::playing_silence, ivr_localuser::playlist, gen_state::stream, and gen_state::u.
Referenced by gen_generate().
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 }
static void gen_release | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 133 of file app_externalivr.c.
References free, and gen_closestream().
00134 { 00135 struct gen_state *state = data; 00136 00137 gen_closestream(state); 00138 free(data); 00139 }
static int load_module | ( | void | ) | [static] |
Definition at line 580 of file app_externalivr.c.
References app_exec, and ast_register_application().
00581 { 00582 return ast_register_application(app, app_exec, synopsis, descrip); 00583 }
static struct playlist_entry* make_entry | ( | const char * | filename | ) | [static] |
Definition at line 231 of file app_externalivr.c.
References ast_calloc.
Referenced by app_exec().
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 }
static void send_child_event | ( | FILE * | handle, | |
const char | event, | |||
const char * | data, | |||
const struct ast_channel * | chan | |||
) | [static] |
Definition at line 95 of file app_externalivr.c.
References ast_chan_log, and LOG_DEBUG.
Referenced by app_exec().
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 }
static int unload_module | ( | void | ) | [static] |
Definition at line 569 of file app_externalivr.c.
References ast_module_user_hangup_all, and ast_unregister_application().
00570 { 00571 int res; 00572 00573 res = ast_unregister_application(app); 00574 00575 ast_module_user_hangup_all(); 00576 00577 return res; 00578 }
struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "External IVR Interface Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "f450f61f60e761b3aa089ebed76ca8a5" , .load = load_module, .unload = unload_module, } [static] |
Definition at line 585 of file app_externalivr.c.
const char* app = "ExternalIVR" [static] |
Definition at line 56 of file app_externalivr.c.
const struct ast_module_info* ast_module_info = &__mod_info [static] |
Definition at line 585 of file app_externalivr.c.
const char* descrip [static] |
Definition at line 60 of file app_externalivr.c.
struct ast_generator gen [static] |
Definition at line 224 of file app_externalivr.c.
Referenced by app_exec(), ast_activate_generator(), reload_config(), and set_config().
const char* synopsis = "Interfaces with an external IVR application" [static] |
Definition at line 58 of file app_externalivr.c.