merge new_loader_completion branch, including (at least):
[asterisk/asterisk.git] / apps / app_externalivr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Kevin P. Fleming <kpfleming@digium.com>
7  *
8  * Portions taken from the file-based music-on-hold work
9  * created by Anthony Minessale II in res_musiconhold.c
10  *
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.
16  *
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.
20  */
21
22 /*! \file
23  *
24  * \brief External IVR application interface
25  *
26  * \author Kevin P. Fleming <kpfleming@digium.com>
27  *
28  * \note Portions taken from the file-based music-on-hold work
29  * created by Anthony Minessale II in res_musiconhold.c
30  *
31  * \ingroup applications
32  */
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/module.h"
50 #include "asterisk/linkedlists.h"
51 #include "asterisk/app.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/options.h"
54
55 static const char *app = "ExternalIVR";
56
57 static const char *synopsis = "Interfaces with an external IVR application";
58
59 static const char *descrip = 
60 "  ExternalIVR(command[|arg[|arg...]]): Forks an process to run the supplied command,\n"
61 "and starts a generator on the channel. The generator's play list is\n"
62 "controlled by the external application, which can add and clear entries\n"
63 "via simple commands issued over its stdout. The external application\n"
64 "will receive all DTMF events received on the channel, and notification\n"
65 "if the channel is hung up. The application will not be forcibly terminated\n"
66 "when the channel is hung up.\n"
67 "See doc/externalivr.txt for a protocol specification.\n";
68
69 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
70 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
71
72 struct playlist_entry {
73         AST_LIST_ENTRY(playlist_entry) list;
74         char filename[1];
75 };
76
77 struct ivr_localuser {
78         struct ast_channel *chan;
79         AST_LIST_HEAD(playlist, playlist_entry) playlist;
80         AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
81         int abort_current_sound;
82         int playing_silence;
83         int option_autoclear;
84 };
85
86
87 struct gen_state {
88         struct ivr_localuser *u;
89         struct ast_filestream *stream;
90         struct playlist_entry *current;
91         int sample_queue;
92 };
93
94 static void send_child_event(FILE *handle, const char event, const char *data,
95                              const struct ast_channel *chan)
96 {
97         char tmp[256];
98
99         if (!data) {
100                 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
101         } else {
102                 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
103         }
104
105         fprintf(handle, "%s\n", tmp);
106         ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
107 }
108
109 static void *gen_alloc(struct ast_channel *chan, void *params)
110 {
111         struct ivr_localuser *u = params;
112         struct gen_state *state;
113         
114         if (!(state = ast_calloc(1, sizeof(*state))))
115                 return NULL;
116
117         state->u = u;
118
119         return state;
120 }
121
122 static void gen_closestream(struct gen_state *state)
123 {
124         if (!state->stream)
125                 return;
126
127         ast_closestream(state->stream);
128         state->u->chan->stream = NULL;
129         state->stream = NULL;
130 }
131
132 static void gen_release(struct ast_channel *chan, void *data)
133 {
134         struct gen_state *state = data;
135
136         gen_closestream(state);
137         free(data);
138 }
139
140 /* caller has the playlist locked */
141 static int gen_nextfile(struct gen_state *state)
142 {
143         struct ivr_localuser *u = state->u;
144         char *file_to_stream;
145         
146         u->abort_current_sound = 0;
147         u->playing_silence = 0;
148         gen_closestream(state);
149
150         while (!state->stream) {
151                 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
152                 if (state->current) {
153                         file_to_stream = state->current->filename;
154                 } else {
155                         file_to_stream = "silence-10";
156                         u->playing_silence = 1;
157                 }
158
159                 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
160                         ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
161                         if (!u->playing_silence) {
162                                 continue;
163                         } else { 
164                                 break;
165                         }
166                 }
167         }
168
169         return (!state->stream);
170 }
171
172 static struct ast_frame *gen_readframe(struct gen_state *state)
173 {
174         struct ast_frame *f = NULL;
175         struct ivr_localuser *u = state->u;
176         
177         if (u->abort_current_sound ||
178             (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
179                 gen_closestream(state);
180                 AST_LIST_LOCK(&u->playlist);
181                 gen_nextfile(state);
182                 AST_LIST_UNLOCK(&u->playlist);
183         }
184
185         if (!(state->stream && (f = ast_readframe(state->stream)))) {
186                 if (state->current) {
187                         AST_LIST_LOCK(&u->finishlist);
188                         AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
189                         AST_LIST_UNLOCK(&u->finishlist);
190                         state->current = NULL;
191                 }
192                 if (!gen_nextfile(state))
193                         f = ast_readframe(state->stream);
194         }
195
196         return f;
197 }
198
199 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
200 {
201         struct gen_state *state = data;
202         struct ast_frame *f = NULL;
203         int res = 0;
204
205         state->sample_queue += samples;
206
207         while (state->sample_queue > 0) {
208                 if (!(f = gen_readframe(state)))
209                         return -1;
210
211                 res = ast_write(chan, f);
212                 ast_frfree(f);
213                 if (res < 0) {
214                         ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
215                         return -1;
216                 }
217                 state->sample_queue -= f->samples;
218         }
219
220         return res;
221 }
222
223 static struct ast_generator gen =
224 {
225         alloc: gen_alloc,
226         release: gen_release,
227         generate: gen_generate,
228 };
229
230 static struct playlist_entry *make_entry(const char *filename)
231 {
232         struct playlist_entry *entry;
233         
234         if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
235                 return NULL;
236
237         strcpy(entry->filename, filename);
238
239         return entry;
240 }
241
242 static int app_exec(struct ast_channel *chan, void *data)
243 {
244         struct ast_module_user *lu;
245         struct playlist_entry *entry;
246         const char *args = data;
247         int child_stdin[2] = { 0,0 };
248         int child_stdout[2] = { 0,0 };
249         int child_stderr[2] = { 0,0 };
250         int res = -1;
251         int gen_active = 0;
252         int pid;
253         char *argv[32];
254         int argc = 1;
255         char *buf, *command;
256         FILE *child_commands = NULL;
257         FILE *child_errors = NULL;
258         FILE *child_events = NULL;
259         struct ivr_localuser foo = {
260                 .playlist = AST_LIST_HEAD_INIT_VALUE,
261                 .finishlist = AST_LIST_HEAD_INIT_VALUE,
262         };
263         struct ivr_localuser *u = &foo;
264
265         lu = ast_module_user_add(chan);
266         
267         u->abort_current_sound = 0;
268         u->chan = chan;
269         
270         if (ast_strlen_zero(args)) {
271                 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
272                 ast_module_user_remove(lu);
273                 return -1;      
274         }
275
276         buf = ast_strdupa(data);
277
278         argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
279
280         if (pipe(child_stdin)) {
281                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
282                 goto exit;
283         }
284
285         if (pipe(child_stdout)) {
286                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
287                 goto exit;
288         }
289
290         if (pipe(child_stderr)) {
291                 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
292                 goto exit;
293         }
294
295         if (chan->_state != AST_STATE_UP) {
296                 ast_answer(chan);
297         }
298
299         if (ast_activate_generator(chan, &gen, u) < 0) {
300                 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
301                 goto exit;
302         } else
303                 gen_active = 1;
304
305         pid = fork();
306         if (pid < 0) {
307                 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
308                 goto exit;
309         }
310
311         if (!pid) {
312                 /* child process */
313                 int i;
314
315                 if (ast_opt_high_priority)
316                         ast_set_priority(0);
317
318                 dup2(child_stdin[0], STDIN_FILENO);
319                 dup2(child_stdout[1], STDOUT_FILENO);
320                 dup2(child_stderr[1], STDERR_FILENO);
321                 for (i = STDERR_FILENO + 1; i < 1024; i++)
322                         close(i);
323                 execv(argv[0], argv);
324                 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
325                 exit(1);
326         } else {
327                 /* parent process */
328                 int child_events_fd = child_stdin[1];
329                 int child_commands_fd = child_stdout[0];
330                 int child_errors_fd = child_stderr[0];
331                 struct ast_frame *f;
332                 int ms;
333                 int exception;
334                 int ready_fd;
335                 int waitfds[2] = { child_errors_fd, child_commands_fd };
336                 struct ast_channel *rchan;
337
338                 close(child_stdin[0]);
339                 child_stdin[0] = 0;
340                 close(child_stdout[1]);
341                 child_stdout[1] = 0;
342                 close(child_stderr[1]);
343                 child_stderr[1] = 0;
344
345                 if (!(child_events = fdopen(child_events_fd, "w"))) {
346                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
347                         goto exit;
348                 }
349
350                 if (!(child_commands = fdopen(child_commands_fd, "r"))) {
351                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
352                         goto exit;
353                 }
354
355                 if (!(child_errors = fdopen(child_errors_fd, "r"))) {
356                         ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
357                         goto exit;
358                 }
359
360                 setvbuf(child_events, NULL, _IONBF, 0);
361                 setvbuf(child_commands, NULL, _IONBF, 0);
362                 setvbuf(child_errors, NULL, _IONBF, 0);
363
364                 res = 0;
365
366                 while (1) {
367                         if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
368                                 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
369                                 res = -1;
370                                 break;
371                         }
372
373                         if (ast_check_hangup(chan)) {
374                                 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
375                                 send_child_event(child_events, 'H', NULL, chan);
376                                 res = -1;
377                                 break;
378                         }
379
380                         ready_fd = 0;
381                         ms = 100;
382                         errno = 0;
383                         exception = 0;
384
385                         rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
386
387                         if (!AST_LIST_EMPTY(&u->finishlist)) {
388                                 AST_LIST_LOCK(&u->finishlist);
389                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
390                                         send_child_event(child_events, 'F', entry->filename, chan);
391                                         free(entry);
392                                 }
393                                 AST_LIST_UNLOCK(&u->finishlist);
394                         }
395
396                         if (rchan) {
397                                 /* the channel has something */
398                                 f = ast_read(chan);
399                                 if (!f) {
400                                         ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
401                                         send_child_event(child_events, 'H', NULL, chan);
402                                         res = -1;
403                                         break;
404                                 }
405
406                                 if (f->frametype == AST_FRAME_DTMF) {
407                                         send_child_event(child_events, f->subclass, NULL, chan);
408                                         if (u->option_autoclear) {
409                                                 if (!u->abort_current_sound && !u->playing_silence)
410                                                         send_child_event(child_events, 'T', NULL, chan);
411                                                 AST_LIST_LOCK(&u->playlist);
412                                                 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
413                                                         send_child_event(child_events, 'D', entry->filename, chan);
414                                                         free(entry);
415                                                 }
416                                                 if (!u->playing_silence)
417                                                         u->abort_current_sound = 1;
418                                                 AST_LIST_UNLOCK(&u->playlist);
419                                         }
420                                 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
421                                         ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
422                                         send_child_event(child_events, 'H', NULL, chan);
423                                         ast_frfree(f);
424                                         res = -1;
425                                         break;
426                                 }
427                                 ast_frfree(f);
428                         } else if (ready_fd == child_commands_fd) {
429                                 char input[1024];
430
431                                 if (exception || feof(child_commands)) {
432                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
433                                         res = -1;
434                                         break;
435                                 }
436
437                                 if (!fgets(input, sizeof(input), child_commands))
438                                         continue;
439
440                                 command = ast_strip(input);
441
442                                 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
443
444                                 if (strlen(input) < 4)
445                                         continue;
446
447                                 if (input[0] == 'S') {
448                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
449                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
450                                                 send_child_event(child_events, 'Z', NULL, chan);
451                                                 strcpy(&input[2], "exception");
452                                         }
453                                         if (!u->abort_current_sound && !u->playing_silence)
454                                                 send_child_event(child_events, 'T', NULL, chan);
455                                         AST_LIST_LOCK(&u->playlist);
456                                         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
457                                                 send_child_event(child_events, 'D', entry->filename, chan);
458                                                 free(entry);
459                                         }
460                                         if (!u->playing_silence)
461                                                 u->abort_current_sound = 1;
462                                         entry = make_entry(&input[2]);
463                                         if (entry)
464                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
465                                         AST_LIST_UNLOCK(&u->playlist);
466                                 } else if (input[0] == 'A') {
467                                         if (ast_fileexists(&input[2], NULL, NULL) == -1) {
468                                                 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
469                                                 send_child_event(child_events, 'Z', NULL, chan);
470                                                 strcpy(&input[2], "exception");
471                                         }
472                                         entry = make_entry(&input[2]);
473                                         if (entry) {
474                                                 AST_LIST_LOCK(&u->playlist);
475                                                 AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
476                                                 AST_LIST_UNLOCK(&u->playlist);
477                                         }
478                                 } else if (input[0] == 'H') {
479                                         ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
480                                         send_child_event(child_events, 'H', NULL, chan);
481                                         break;
482                                 } else if (input[0] == 'O') {
483                                         if (!strcasecmp(&input[2], "autoclear"))
484                                                 u->option_autoclear = 1;
485                                         else if (!strcasecmp(&input[2], "noautoclear"))
486                                                 u->option_autoclear = 0;
487                                         else
488                                                 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
489                                 }
490                         } else if (ready_fd == child_errors_fd) {
491                                 char input[1024];
492
493                                 if (exception || feof(child_errors)) {
494                                         ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
495                                         res = -1;
496                                         break;
497                                 }
498
499                                 if (fgets(input, sizeof(input), child_errors)) {
500                                         command = ast_strip(input);
501                                         ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
502                                 }
503                         } else if ((ready_fd < 0) && ms) { 
504                                 if (errno == 0 || errno == EINTR)
505                                         continue;
506
507                                 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
508                                 break;
509                         }
510                 }
511         }
512
513  exit:
514         if (gen_active)
515                 ast_deactivate_generator(chan);
516
517         if (child_events)
518                 fclose(child_events);
519
520         if (child_commands)
521                 fclose(child_commands);
522
523         if (child_errors)
524                 fclose(child_errors);
525
526         if (child_stdin[0])
527                 close(child_stdin[0]);
528
529         if (child_stdin[1])
530                 close(child_stdin[1]);
531
532         if (child_stdout[0])
533                 close(child_stdout[0]);
534
535         if (child_stdout[1])
536                 close(child_stdout[1]);
537
538         if (child_stderr[0])
539                 close(child_stderr[0]);
540
541         if (child_stderr[1])
542                 close(child_stderr[1]);
543
544         while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
545                 free(entry);
546
547         ast_module_user_remove(lu);
548
549         return res;
550 }
551
552 static int unload_module(void)
553 {
554         int res;
555
556         res = ast_unregister_application(app);
557
558         ast_module_user_hangup_all();
559
560         return res;
561 }
562
563 static int load_module(void)
564 {
565         return ast_register_application(app, app_exec, synopsis, descrip);
566 }
567
568 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");