From 0e998fdff62b4a399a514d948c00698caaf53807 Mon Sep 17 00:00:00 2001 From: Tzafrir Cohen Date: Thu, 31 Jul 2008 18:14:18 +0300 Subject: [PATCH] The XAGI feature. --- agi/Makefile | 4 +- agi/xagi-test.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ include/asterisk/agi.h | 1 + res/res_agi.c | 124 +++++++++++++++++++++++++++++++++-- 4 files changed, 295 insertions(+), 9 deletions(-) create mode 100644 agi/xagi-test.c diff --git a/agi/Makefile b/agi/Makefile index c24c7f100..8f2394e4c 100644 --- a/agi/Makefile +++ b/agi/Makefile @@ -13,7 +13,7 @@ .PHONY: clean all uninstall -AGIS=agi-test.agi eagi-test eagi-sphinx-test jukebox.agi +AGIS=agi-test.agi eagi-test eagi-sphinx-test jukebox.agi xagi-test ifeq ($(OSARCH),SunOS) LIBS+=-lsocket -lnsl @@ -38,7 +38,7 @@ uninstall: for x in $(AGIS); do rm -f $(DESTDIR)$(AGI_DIR)/$$x ; done clean: - rm -f *.so *.o look eagi-test eagi-sphinx-test + rm -f *.so *.o look eagi-test eagi-sphinx-test xagi-test rm -f .*.o.d .*.oo.d *.s *.i rm -f strcompat.c diff --git a/agi/xagi-test.c b/agi/xagi-test.c new file mode 100644 index 000000000..39e28cba7 --- /dev/null +++ b/agi/xagi-test.c @@ -0,0 +1,175 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * XAGI sample script + * + * Copyright (C) 2005 Junghanns.NET GmbH + * Klaus-Peter Junghanns + * + * based on eagi-test.c + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include +#include +#include +#include +#include +#include +#ifdef SOLARIS +#include +#endif + +#define AUDIO_FILENO_IN (STDERR_FILENO + 1) +#define AUDIO_FILENO_OUT (STDERR_FILENO + 2) + +static int read_environment(void) +{ + char buf[256]; + char *val; + /* Read environment */ + for(;;) { + fgets(buf, sizeof(buf), stdin); + if (feof(stdin)) + return -1; + buf[strlen(buf) - 1] = '\0'; + /* Check for end of environment */ + if (!strlen(buf)) + return 0; + val = strchr(buf, ':'); + if (!val) { + fprintf(stderr, "Invalid environment: '%s'\n", buf); + return -1; + } + *val = '\0'; + val++; + val++; + /* Skip space */ + // fprintf(stderr, "Environment: '%s' is '%s'\n", buf, val); + + /* Load into normal environment */ + setenv(buf, val, 1); + + } + /* Never reached */ + return 0; +} + +static void app_echo(void) +{ + fd_set fds; + int res; + int bytes = 0; + static char astresp[256]; + char audiobuf[16000]; /* 1 second of audio */ + for (;;) { + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + FD_SET(AUDIO_FILENO_IN, &fds); + /* Wait for *some* sort of I/O */ + res = select(AUDIO_FILENO_IN + 1, &fds, NULL, NULL, NULL); + if (res < 0) { + fprintf(stderr, "Error in select: %s\n", strerror(errno)); + return; + } + if (FD_ISSET(STDIN_FILENO, &fds)) { + fgets(astresp, sizeof(astresp), stdin); + if (feof(stdin)) { + return; + } + astresp[strlen(astresp) - 1] = '\0'; + fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp); + return; + } + if (FD_ISSET(AUDIO_FILENO_IN, &fds)) { + /* what goes in.... */ + res = read(AUDIO_FILENO_IN, audiobuf, sizeof(audiobuf)); + if (res > 0) { + bytes = res; + /* must come out */ + write(AUDIO_FILENO_OUT, audiobuf, bytes); + } + } + } +} + +static char *wait_result(void) +{ + fd_set fds; + int res; + static char astresp[256]; + char audiobuf[4096]; + for (;;) { + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + FD_SET(AUDIO_FILENO_IN, &fds); + /* Wait for *some* sort of I/O */ + res = select(AUDIO_FILENO_IN + 1, &fds, NULL, NULL, NULL); + if (res < 0) { + fprintf(stderr, "Error in select: %s\n", strerror(errno)); + return NULL; + } + if (FD_ISSET(STDIN_FILENO, &fds)) { + fgets(astresp, sizeof(astresp), stdin); + if (feof(stdin)) { + fprintf(stderr, "Got hungup on apparently\n"); + return NULL; + } + astresp[strlen(astresp) - 1] = '\0'; + fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp); + return astresp; + } + if (FD_ISSET(AUDIO_FILENO_IN, &fds)) { + res = read(AUDIO_FILENO_IN, audiobuf, sizeof(audiobuf)); + /* drop it, like it's hot */ + } + } + +} + +static char *run_command(char *command) +{ + fprintf(stdout, "%s\n", command); + return wait_result(); +} + + +static int run_script(void) +{ + char *res; + res = run_command("STREAM FILE demo-echotest \"\""); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + app_echo(); + return 0; +} + +int main(int argc, char *argv[]) +{ + char *tmp; + int ver = 0; + int subver = 0; + /* Setup stdin/stdout for line buffering */ + setlinebuf(stdin); + setlinebuf(stdout); + if (read_environment()) { + fprintf(stderr, "Failed to read environment: %s\n", strerror(errno)); + exit(1); + } + tmp = getenv("agi_enhanced"); + if (tmp) { + if (sscanf(tmp, "%d.%d", &ver, &subver) != 2) + ver = 0; + } + if (ver < 2) { + fprintf(stderr, "No XAGI services available. Use XAGI, not AGI or EAGI\n"); + exit(1); + } + if (run_script()) + return -1; + exit(0); +} diff --git a/include/asterisk/agi.h b/include/asterisk/agi.h index 5797176fe..c0849aff6 100644 --- a/include/asterisk/agi.h +++ b/include/asterisk/agi.h @@ -30,6 +30,7 @@ extern "C" { typedef struct agi_state { int fd; /* FD for general output */ int audio; /* FD for audio output */ + int audio_in; /* FD for audio output */ int ctrl; /* FD for input control */ unsigned int fast:1; /* flag for fast agi or not */ } AGI; diff --git a/res/res_agi.c b/res/res_agi.c index a60155087..f01c884fd 100644 --- a/res/res_agi.c +++ b/res/res_agi.c @@ -11,6 +11,9 @@ * the project provides a web site, mailing lists and IRC * channels for your use. * + * Copyright (C) 2005 Junghanns.NET GmbH + * Klaus-Peter Junghanns + * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. @@ -76,16 +79,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static char *app = "AGI"; +static char *xapp = "XAGI"; + static char *eapp = "EAGI"; static char *deadapp = "DeadAGI"; static char *synopsis = "Executes an AGI compliant application"; +static char *xsynopsis = "Executes an XAGI compliant application"; static char *esynopsis = "Executes an EAGI compliant application"; static char *deadsynopsis = "Executes AGI on a hungup channel"; static char *descrip = -" [E|Dead]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n" +" [E|Dead|X]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n" "program on a channel. AGI allows Asterisk to launch external programs\n" "written in any language to control a telephony channel, play audio,\n" "read DTMF digits, etc. by communicating with the AGI protocol on stdin\n" @@ -98,6 +104,8 @@ static char *descrip = "variable to \"no\" before executing the AGI application.\n" " Using 'EAGI' provides enhanced AGI, with incoming audio available out of band\n" "on file descriptor 3\n\n" +"Using 'XAGI' provides enhanced AGI, with incoming audio available out of band" +" on file descriptor 3 and outgoing audio available out of band on file descriptor 4\n\n" " Use the CLI command 'agi show' to list available agi commands\n" " This application sets the following channel variable upon completion:\n" " AGISTATUS The status of the attempt to the run the AGI script\n" @@ -237,13 +245,14 @@ static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, in return AGI_RESULT_SUCCESS_FAST; } -static enum agi_result launch_script(char *script, char *argv[], int *fds, int *efd, int *opid) +static enum agi_result launch_script(char *script, char *argv[], int *fds, int *efd, int *efd2, int *opid) { char tmp[256]; int pid; int toast[2]; int fromast[2]; int audio[2]; + int audio2[2]; int x; int res; sigset_t signal_set, old_set; @@ -288,6 +297,33 @@ static enum agi_result launch_script(char *script, char *argv[], int *fds, int * return AGI_RESULT_FAILURE; } } + if (efd2) { + if (pipe(audio2)) { + ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno)); + close(fromast[0]); + close(fromast[1]); + close(toast[0]); + close(toast[1]); + close(audio[0]); + close(audio[1]); + return AGI_RESULT_FAILURE; + } + res = fcntl(audio2[0], F_GETFL); + if (res > -1) + res = fcntl(audio2[0], F_SETFL, res | O_NONBLOCK); + if (res < 0) { + ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno)); + close(fromast[0]); + close(fromast[1]); + close(toast[0]); + close(toast[1]); + close(audio[0]); + close(audio[1]); + close(audio2[0]); + close(audio2[1]); + return AGI_RESULT_FAILURE; + } + } /* Block SIGHUP during the fork - prevents a race */ sigfillset(&signal_set); @@ -323,6 +359,11 @@ static enum agi_result launch_script(char *script, char *argv[], int *fds, int * } else { close(STDERR_FILENO + 1); } + if (efd2) { + dup2(audio2[1], STDERR_FILENO + 2); + } else { + close(STDERR_FILENO + 2); + } /* Before we unblock our signals, return our trapped signals back to the defaults */ signal(SIGHUP, SIG_DFL); @@ -340,7 +381,7 @@ static enum agi_result launch_script(char *script, char *argv[], int *fds, int * } /* Close everything but stdin/out/error */ - for (x=STDERR_FILENO + 2;x<1024;x++) + for (x=STDERR_FILENO + 3;x<1024;x++) close(x); /* Execute script */ @@ -360,12 +401,18 @@ static enum agi_result launch_script(char *script, char *argv[], int *fds, int * if (efd) { *efd = audio[1]; } + if (efd2) { + *efd2 = audio2[0]; + } /* close what we're not using in the parent */ close(toast[1]); close(fromast[0]); if (efd) close(audio[0]); + if (efd2) { + close(audio2[1]); + } *opid = pid; return AGI_RESULT_SUCCESS; @@ -395,7 +442,7 @@ static void setup_env(struct ast_channel *chan, char *request, int fd, int enhan fdprintf(fd, "agi_context: %s\n", chan->context); fdprintf(fd, "agi_extension: %s\n", chan->exten); fdprintf(fd, "agi_priority: %d\n", chan->priority); - fdprintf(fd, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0"); + fdprintf(fd, "agi_enhanced: %d%s\n", enhanced, ".0"); /* User information */ fdprintf(fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : ""); @@ -1839,8 +1886,13 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi int ms; enum agi_result returnstatus = AGI_RESULT_SUCCESS; struct ast_frame *f; + struct ast_frame fr; char buf[AGI_BUF_LEN]; + char audiobuf[AGI_BUF_LEN]; char *res = NULL; + int audiobytes; + int fds[2]; + int enhanced = 0; FILE *readf; /* how many times we'll retry if ast_waitfor_nandfs will return without either channel or file descriptor in case select is interrupted by a system call (EINTR) */ @@ -1854,10 +1906,22 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi return AGI_RESULT_FAILURE; } setlinebuf(readf); - setup_env(chan, request, agi->fd, (agi->audio > -1)); + if (agi->audio > -1) { + enhanced = 1; + } + if (agi->audio_in > -1) { + enhanced++; + } + setup_env(chan, request, agi->fd, enhanced); + fds[0] = agi->ctrl; + fds[1] = agi->audio_in; for (;;) { ms = -1; - c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms); + if (agi->audio_in > -1) { + c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, fds, 2, NULL, &outfd, &ms); + } else { + c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms); + } if (c) { retry = AGI_NANDFS_RETRY; /* Idle the channel until we get a command */ @@ -1875,6 +1939,16 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi ast_frfree(f); } } else if (outfd > -1) { + if ((agi->audio_in > -1) && (outfd == agi->audio_in)) { + audiobytes = read(agi->audio_in, audiobuf, sizeof(audiobuf)); + if (audiobytes > 0) { + fr.frametype = AST_FRAME_VOICE; + fr.subclass = AST_FORMAT_SLINEAR; + fr.datalen = audiobytes; + fr.data = audiobuf; + ast_write(chan, &fr); + } + } else { size_t len = sizeof(buf); size_t buflen = 0; @@ -1926,6 +2000,7 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) { break; } + } } else { if (--retry <= 0) { ast_log(LOG_WARNING, "No channel, no fd?\n"); @@ -2038,6 +2113,7 @@ static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int int argc = 0; int fds[2]; int efd = -1; + int efd2 = -1; int pid; char *stringp; AGI agi; @@ -2064,12 +2140,13 @@ static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int } #endif ast_replace_sigchld(); - res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, &pid); + res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, (enhanced == 2) ? &efd2 : NULL, &pid); if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) { int status = 0; agi.fd = fds[1]; agi.ctrl = fds[0]; agi.audio = efd; + agi.audio_in = efd2; agi.fast = (res == AGI_RESULT_SUCCESS_FAST) ? 1 : 0; res = run_agi(chan, argv[0], &agi, pid, &status, dead); /* If the fork'd process returns non-zero, set AGISTATUS to FAILURE */ @@ -2079,6 +2156,8 @@ static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int close(fds[1]); if (efd > -1) close(efd); + if (efd2 > -1) + close(efd2); } ast_unreplace_sigchld(); ast_module_user_remove(u); @@ -2127,6 +2206,35 @@ static int eagi_exec(struct ast_channel *chan, void *data) return res; } +static int xagi_exec(struct ast_channel *chan, void *data) +{ + int readformat, writeformat; + int res; + + if (chan->_softhangup) + ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n"); + readformat = chan->readformat; + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name); + return -1; + } + writeformat = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name); + return -1; + } + res = agi_exec_full(chan, data, 2, 0); + if (!res) { + if (ast_set_read_format(chan, readformat)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat)); + } + if (ast_set_write_format(chan, writeformat)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(writeformat)); + } + } + return res; +} + static int deadagi_exec(struct ast_channel *chan, void *data) { if (!ast_check_hangup(chan)) @@ -2182,6 +2290,7 @@ static int unload_module(void) { ast_module_user_hangup_all(); ast_cli_unregister_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry)); + ast_unregister_application(xapp); ast_unregister_application(eapp); ast_unregister_application(deadapp); return ast_unregister_application(app); @@ -2192,6 +2301,7 @@ static int load_module(void) ast_cli_register_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry)); ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip); ast_register_application(eapp, eagi_exec, esynopsis, descrip); + ast_register_application(xapp, xagi_exec, xsynopsis, descrip); return ast_register_application(app, agi_exec, synopsis, descrip); } -- 2.11.4.GIT