update comment to match the state of the code
[asterisk-bristuff.git] / apps / app_externalivr.c
blob7859a3e166a06842100190ad987c9f5151cfcb85
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Kevin P. Fleming <kpfleming@digium.com>
8 * Portions taken from the file-based music-on-hold work
9 * created by Anthony Minessale II in res_musiconhold.c
11 * See http://www.asterisk.org for more information about
12 * the Asterisk project. Please do not directly contact
13 * any of the maintainers of this project for assistance;
14 * the project provides a web site, mailing lists and IRC
15 * channels for your use.
17 * This program is free software, distributed under the terms of
18 * the GNU General Public License Version 2. See the LICENSE file
19 * at the top of the source tree.
22 /*! \file
24 * \brief External IVR application interface
26 * \author Kevin P. Fleming <kpfleming@digium.com>
28 * \note Portions taken from the file-based music-on-hold work
29 * created by Anthony Minessale II in res_musiconhold.c
31 * \ingroup applications
34 #include "asterisk.h"
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <signal.h>
45 #include "asterisk/lock.h"
46 #include "asterisk/file.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/app.h"
53 #include "asterisk/utils.h"
54 #include "asterisk/options.h"
56 static const char *app = "ExternalIVR";
58 static const char *synopsis = "Interfaces with an external IVR application";
60 static const char *descrip =
61 " ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
62 "and starts a generator on the channel. The generator's play list is\n"
63 "controlled by the external application, which can add and clear entries\n"
64 "via simple commands issued over its stdout. The external application\n"
65 "will receive all DTMF events received on the channel, and notification\n"
66 "if the channel is hung up. The application will not be forcibly terminated\n"
67 "when the channel is hung up.\n"
68 "See doc/externalivr.txt for a protocol specification.\n";
70 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
71 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
73 struct playlist_entry {
74 AST_LIST_ENTRY(playlist_entry) list;
75 char filename[1];
78 struct ivr_localuser {
79 struct ast_channel *chan;
80 AST_LIST_HEAD(playlist, playlist_entry) playlist;
81 AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
82 int abort_current_sound;
83 int playing_silence;
84 int option_autoclear;
88 struct gen_state {
89 struct ivr_localuser *u;
90 struct ast_filestream *stream;
91 struct playlist_entry *current;
92 int sample_queue;
95 static void send_child_event(FILE *handle, const char event, const char *data,
96 const struct ast_channel *chan)
98 char tmp[256];
100 if (!data) {
101 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
102 } else {
103 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
106 fprintf(handle, "%s\n", tmp);
107 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
110 static void *gen_alloc(struct ast_channel *chan, void *params)
112 struct ivr_localuser *u = params;
113 struct gen_state *state;
115 if (!(state = ast_calloc(1, sizeof(*state))))
116 return NULL;
118 state->u = u;
120 return state;
123 static void gen_closestream(struct gen_state *state)
125 if (!state->stream)
126 return;
128 ast_closestream(state->stream);
129 state->u->chan->stream = NULL;
130 state->stream = NULL;
133 static void gen_release(struct ast_channel *chan, void *data)
135 struct gen_state *state = data;
137 gen_closestream(state);
138 free(data);
141 /* caller has the playlist locked */
142 static int gen_nextfile(struct gen_state *state)
144 struct ivr_localuser *u = state->u;
145 char *file_to_stream;
147 u->abort_current_sound = 0;
148 u->playing_silence = 0;
149 gen_closestream(state);
151 while (!state->stream) {
152 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
153 if (state->current) {
154 file_to_stream = state->current->filename;
155 } else {
156 file_to_stream = "silence/10";
157 u->playing_silence = 1;
160 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
161 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
162 if (!u->playing_silence) {
163 continue;
164 } else {
165 break;
170 return (!state->stream);
173 static struct ast_frame *gen_readframe(struct gen_state *state)
175 struct ast_frame *f = NULL;
176 struct ivr_localuser *u = state->u;
178 if (u->abort_current_sound ||
179 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
180 gen_closestream(state);
181 AST_LIST_LOCK(&u->playlist);
182 gen_nextfile(state);
183 AST_LIST_UNLOCK(&u->playlist);
186 if (!(state->stream && (f = ast_readframe(state->stream)))) {
187 if (state->current) {
188 AST_LIST_LOCK(&u->finishlist);
189 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
190 AST_LIST_UNLOCK(&u->finishlist);
191 state->current = NULL;
193 if (!gen_nextfile(state))
194 f = ast_readframe(state->stream);
197 return f;
200 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
202 struct gen_state *state = data;
203 struct ast_frame *f = NULL;
204 int res = 0;
206 state->sample_queue += samples;
208 while (state->sample_queue > 0) {
209 if (!(f = gen_readframe(state)))
210 return -1;
212 res = ast_write(chan, f);
213 ast_frfree(f);
214 if (res < 0) {
215 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
216 return -1;
218 state->sample_queue -= f->samples;
221 return res;
224 static struct ast_generator gen =
226 alloc: gen_alloc,
227 release: gen_release,
228 generate: gen_generate,
231 static struct playlist_entry *make_entry(const char *filename)
233 struct playlist_entry *entry;
235 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
236 return NULL;
238 strcpy(entry->filename, filename);
240 return entry;
243 static int app_exec(struct ast_channel *chan, void *data)
245 struct ast_module_user *lu;
246 struct playlist_entry *entry;
247 const char *args = data;
248 int child_stdin[2] = { 0,0 };
249 int child_stdout[2] = { 0,0 };
250 int child_stderr[2] = { 0,0 };
251 int res = -1;
252 int gen_active = 0;
253 int pid;
254 char *argv[32];
255 int argc = 1;
256 char *buf, *command;
257 FILE *child_commands = NULL;
258 FILE *child_errors = NULL;
259 FILE *child_events = NULL;
260 struct ivr_localuser foo = {
261 .playlist = AST_LIST_HEAD_INIT_VALUE,
262 .finishlist = AST_LIST_HEAD_INIT_VALUE,
264 struct ivr_localuser *u = &foo;
265 sigset_t fullset, oldset;
267 lu = ast_module_user_add(chan);
269 sigfillset(&fullset);
270 pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
272 u->abort_current_sound = 0;
273 u->chan = chan;
275 if (ast_strlen_zero(args)) {
276 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
277 ast_module_user_remove(lu);
278 return -1;
281 buf = ast_strdupa(data);
283 argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
285 if (pipe(child_stdin)) {
286 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
287 goto exit;
290 if (pipe(child_stdout)) {
291 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
292 goto exit;
295 if (pipe(child_stderr)) {
296 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
297 goto exit;
300 if (chan->_state != AST_STATE_UP) {
301 ast_answer(chan);
304 if (ast_activate_generator(chan, &gen, u) < 0) {
305 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
306 goto exit;
307 } else
308 gen_active = 1;
310 pid = fork();
311 if (pid < 0) {
312 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
313 goto exit;
316 if (!pid) {
317 /* child process */
318 int i;
320 signal(SIGPIPE, SIG_DFL);
321 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
323 if (ast_opt_high_priority)
324 ast_set_priority(0);
326 dup2(child_stdin[0], STDIN_FILENO);
327 dup2(child_stdout[1], STDOUT_FILENO);
328 dup2(child_stderr[1], STDERR_FILENO);
329 for (i = STDERR_FILENO + 1; i < 1024; i++)
330 close(i);
331 execv(argv[0], argv);
332 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
333 _exit(1);
334 } else {
335 /* parent process */
336 int child_events_fd = child_stdin[1];
337 int child_commands_fd = child_stdout[0];
338 int child_errors_fd = child_stderr[0];
339 struct ast_frame *f;
340 int ms;
341 int exception;
342 int ready_fd;
343 int waitfds[2] = { child_errors_fd, child_commands_fd };
344 struct ast_channel *rchan;
346 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
348 close(child_stdin[0]);
349 child_stdin[0] = 0;
350 close(child_stdout[1]);
351 child_stdout[1] = 0;
352 close(child_stderr[1]);
353 child_stderr[1] = 0;
355 if (!(child_events = fdopen(child_events_fd, "w"))) {
356 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
357 goto exit;
360 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
361 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
362 goto exit;
365 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
366 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
367 goto exit;
370 setvbuf(child_events, NULL, _IONBF, 0);
371 setvbuf(child_commands, NULL, _IONBF, 0);
372 setvbuf(child_errors, NULL, _IONBF, 0);
374 res = 0;
376 while (1) {
377 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
378 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
379 res = -1;
380 break;
383 if (ast_check_hangup(chan)) {
384 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
385 send_child_event(child_events, 'H', NULL, chan);
386 res = -1;
387 break;
390 ready_fd = 0;
391 ms = 100;
392 errno = 0;
393 exception = 0;
395 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
397 if (!AST_LIST_EMPTY(&u->finishlist)) {
398 AST_LIST_LOCK(&u->finishlist);
399 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
400 send_child_event(child_events, 'F', entry->filename, chan);
401 free(entry);
403 AST_LIST_UNLOCK(&u->finishlist);
406 if (rchan) {
407 /* the channel has something */
408 f = ast_read(chan);
409 if (!f) {
410 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
411 send_child_event(child_events, 'H', NULL, chan);
412 res = -1;
413 break;
416 if (f->frametype == AST_FRAME_DTMF) {
417 send_child_event(child_events, f->subclass, NULL, chan);
418 if (u->option_autoclear) {
419 if (!u->abort_current_sound && !u->playing_silence)
420 send_child_event(child_events, 'T', NULL, chan);
421 AST_LIST_LOCK(&u->playlist);
422 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
423 send_child_event(child_events, 'D', entry->filename, chan);
424 free(entry);
426 if (!u->playing_silence)
427 u->abort_current_sound = 1;
428 AST_LIST_UNLOCK(&u->playlist);
430 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
431 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
432 send_child_event(child_events, 'H', NULL, chan);
433 ast_frfree(f);
434 res = -1;
435 break;
437 ast_frfree(f);
438 } else if (ready_fd == child_commands_fd) {
439 char input[1024];
441 if (exception || feof(child_commands)) {
442 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
443 res = -1;
444 break;
447 if (!fgets(input, sizeof(input), child_commands))
448 continue;
450 command = ast_strip(input);
452 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
454 if (strlen(input) < 4)
455 continue;
457 if (input[0] == 'S') {
458 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
459 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
460 send_child_event(child_events, 'Z', NULL, chan);
461 strcpy(&input[2], "exception");
463 if (!u->abort_current_sound && !u->playing_silence)
464 send_child_event(child_events, 'T', NULL, chan);
465 AST_LIST_LOCK(&u->playlist);
466 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
467 send_child_event(child_events, 'D', entry->filename, chan);
468 free(entry);
470 if (!u->playing_silence)
471 u->abort_current_sound = 1;
472 entry = make_entry(&input[2]);
473 if (entry)
474 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
475 AST_LIST_UNLOCK(&u->playlist);
476 } else if (input[0] == 'A') {
477 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
478 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
479 send_child_event(child_events, 'Z', NULL, chan);
480 strcpy(&input[2], "exception");
482 entry = make_entry(&input[2]);
483 if (entry) {
484 AST_LIST_LOCK(&u->playlist);
485 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
486 AST_LIST_UNLOCK(&u->playlist);
488 } else if (input[0] == 'H') {
489 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
490 send_child_event(child_events, 'H', NULL, chan);
491 break;
492 } else if (input[0] == 'O') {
493 if (!strcasecmp(&input[2], "autoclear"))
494 u->option_autoclear = 1;
495 else if (!strcasecmp(&input[2], "noautoclear"))
496 u->option_autoclear = 0;
497 else
498 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
500 } else if (ready_fd == child_errors_fd) {
501 char input[1024];
503 if (exception || feof(child_errors)) {
504 ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
505 res = -1;
506 break;
509 if (fgets(input, sizeof(input), child_errors)) {
510 command = ast_strip(input);
511 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
513 } else if ((ready_fd < 0) && ms) {
514 if (errno == 0 || errno == EINTR)
515 continue;
517 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
518 break;
523 exit:
524 if (gen_active)
525 ast_deactivate_generator(chan);
527 if (child_events)
528 fclose(child_events);
530 if (child_commands)
531 fclose(child_commands);
533 if (child_errors)
534 fclose(child_errors);
536 if (child_stdin[0])
537 close(child_stdin[0]);
539 if (child_stdin[1])
540 close(child_stdin[1]);
542 if (child_stdout[0])
543 close(child_stdout[0]);
545 if (child_stdout[1])
546 close(child_stdout[1]);
548 if (child_stderr[0])
549 close(child_stderr[0]);
551 if (child_stderr[1])
552 close(child_stderr[1]);
554 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
555 free(entry);
557 ast_module_user_remove(lu);
559 return res;
562 static int unload_module(void)
564 int res;
566 res = ast_unregister_application(app);
568 ast_module_user_hangup_all();
570 return res;
573 static int load_module(void)
575 return ast_register_application(app, app_exec, synopsis, descrip);
578 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");