2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (c) 2004-2006 Tilghman Lesher <app_stack_v003@the-tilghman.com>.
6 * This code is released by the author with no restrictions on usage.
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Stack applications Gosub, Return, etc.
23 * \author Tilghman Lesher <app_stack_v003@the-tilghman.com>
25 * \ingroup applications
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
32 #include "asterisk/pbx.h"
33 #include "asterisk/module.h"
34 #include "asterisk/app.h"
35 #include "asterisk/manager.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/agi.h"
39 static int agi_loaded
= 0;
41 static const char *app_gosub
= "Gosub";
42 static const char *app_gosubif
= "GosubIf";
43 static const char *app_return
= "Return";
44 static const char *app_pop
= "StackPop";
46 static const char *gosub_synopsis
= "Jump to label, saving return address";
47 static const char *gosubif_synopsis
= "Conditionally jump to label, saving return address";
48 static const char *return_synopsis
= "Return from gosub routine";
49 static const char *pop_synopsis
= "Remove one address from gosub stack";
51 static const char *gosub_descrip
=
52 " Gosub([[context,]exten,]priority[(arg1[,...][,argN])]):\n"
53 "Jumps to the label specified, saving the return address.\n";
54 static const char *gosubif_descrip
=
55 " GosubIf(condition?labeliftrue[(arg1[,...])][:labeliffalse[(arg1[,...])]]):\n"
56 "If the condition is true, then jump to labeliftrue. If false, jumps to\n"
57 "labeliffalse, if specified. In either case, a jump saves the return point\n"
58 "in the dialplan, to be returned to with a Return.\n";
59 static const char *return_descrip
=
60 " Return([return-value]):\n"
61 "Jumps to the last label on the stack, removing it. The return value, if\n"
62 "any, is saved in the channel variable GOSUB_RETVAL.\n";
63 static const char *pop_descrip
=
65 "Removes last label on the stack, discarding it.\n";
67 static void gosub_free(void *data
);
69 static struct ast_datastore_info stack_info
= {
71 .destroy
= gosub_free
,
74 struct gosub_stack_frame
{
75 AST_LIST_ENTRY(gosub_stack_frame
) entries
;
76 /* 100 arguments is all that we support anyway, but this will handle up to 255 */
77 unsigned char arguments
;
78 struct varshead varshead
;
84 static int frame_set_var(struct ast_channel
*chan
, struct gosub_stack_frame
*frame
, const char *var
, const char *value
)
86 struct ast_var_t
*variables
;
89 /* Does this variable already exist? */
90 AST_LIST_TRAVERSE(&frame
->varshead
, variables
, entries
) {
91 if (!strcmp(var
, ast_var_name(variables
))) {
97 if (!ast_strlen_zero(value
)) {
99 variables
= ast_var_assign(var
, "");
100 AST_LIST_INSERT_HEAD(&frame
->varshead
, variables
, entries
);
101 pbx_builtin_pushvar_helper(chan
, var
, value
);
103 pbx_builtin_setvar_helper(chan
, var
, value
);
105 manager_event(EVENT_FLAG_DIALPLAN
, "VarSet",
107 "Variable: LOCAL(%s)\r\n"
110 chan
->name
, var
, value
, chan
->uniqueid
);
115 static void gosub_release_frame(struct ast_channel
*chan
, struct gosub_stack_frame
*frame
)
119 struct ast_var_t
*vardata
;
121 /* If chan is not defined, then we're calling it as part of gosub_free,
122 * and the channel variables will be deallocated anyway. Otherwise, we're
123 * just releasing a single frame, so we need to clean up the arguments for
124 * that frame, so that we re-expose the variables from the previous frame
125 * that were hidden by this one.
128 for (i
= 1; i
<= frame
->arguments
&& i
!= 0; i
++) {
129 snprintf(argname
, sizeof(argname
), "ARG%hhd", i
);
130 pbx_builtin_setvar_helper(chan
, argname
, NULL
);
134 /* Delete local variables */
135 while ((vardata
= AST_LIST_REMOVE_HEAD(&frame
->varshead
, entries
))) {
137 pbx_builtin_setvar_helper(chan
, ast_var_name(vardata
), NULL
);
138 ast_var_delete(vardata
);
144 static struct gosub_stack_frame
*gosub_allocate_frame(const char *context
, const char *extension
, int priority
, unsigned char arguments
)
146 struct gosub_stack_frame
*new = NULL
;
147 int len_extension
= strlen(extension
), len_context
= strlen(context
);
149 if ((new = ast_calloc(1, sizeof(*new) + 2 + len_extension
+ len_context
))) {
150 AST_LIST_HEAD_INIT_NOLOCK(&new->varshead
);
151 strcpy(new->extension
, extension
);
152 new->context
= new->extension
+ len_extension
+ 1;
153 strcpy(new->context
, context
);
154 new->priority
= priority
;
155 new->arguments
= arguments
;
160 static void gosub_free(void *data
)
162 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
= data
;
163 struct gosub_stack_frame
*oldframe
;
164 AST_LIST_LOCK(oldlist
);
165 while ((oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
))) {
166 gosub_release_frame(NULL
, oldframe
);
168 AST_LIST_UNLOCK(oldlist
);
169 AST_LIST_HEAD_DESTROY(oldlist
);
173 static int pop_exec(struct ast_channel
*chan
, void *data
)
175 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
176 struct gosub_stack_frame
*oldframe
;
177 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
180 ast_log(LOG_WARNING
, "%s called with no gosub stack allocated.\n", app_pop
);
184 oldlist
= stack_store
->data
;
185 AST_LIST_LOCK(oldlist
);
186 oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
);
187 AST_LIST_UNLOCK(oldlist
);
190 gosub_release_frame(chan
, oldframe
);
192 ast_debug(1, "%s called with an empty gosub stack\n", app_pop
);
197 static int return_exec(struct ast_channel
*chan
, void *data
)
199 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
200 struct gosub_stack_frame
*oldframe
;
201 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
205 ast_log(LOG_ERROR
, "Return without Gosub: stack is unallocated\n");
209 oldlist
= stack_store
->data
;
210 AST_LIST_LOCK(oldlist
);
211 oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
);
212 AST_LIST_UNLOCK(oldlist
);
215 ast_log(LOG_ERROR
, "Return without Gosub: stack is empty\n");
219 ast_explicit_goto(chan
, oldframe
->context
, oldframe
->extension
, oldframe
->priority
);
220 gosub_release_frame(chan
, oldframe
);
222 /* Set a return value, if any */
223 pbx_builtin_setvar_helper(chan
, "GOSUB_RETVAL", S_OR(retval
, ""));
227 static int gosub_exec(struct ast_channel
*chan
, void *data
)
229 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
230 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
231 struct gosub_stack_frame
*newframe
;
232 char argname
[15], *tmp
= ast_strdupa(data
), *label
, *endparen
;
234 AST_DECLARE_APP_ARGS(args2
,
235 AST_APP_ARG(argval
)[100];
238 if (ast_strlen_zero(data
)) {
239 ast_log(LOG_ERROR
, "%s requires an argument: %s([[context,]exten,]priority[(arg1[,...][,argN])])\n", app_gosub
, app_gosub
);
244 ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan
->name
);
245 stack_store
= ast_datastore_alloc(&stack_info
, NULL
);
247 ast_log(LOG_ERROR
, "Unable to allocate new datastore. Gosub will fail.\n");
251 oldlist
= ast_calloc(1, sizeof(*oldlist
));
253 ast_log(LOG_ERROR
, "Unable to allocate datastore list head. Gosub will fail.\n");
254 ast_datastore_free(stack_store
);
258 stack_store
->data
= oldlist
;
259 AST_LIST_HEAD_INIT(oldlist
);
260 ast_channel_datastore_add(chan
, stack_store
);
263 /* Separate the arguments from the label */
264 /* NOTE: you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */
265 label
= strsep(&tmp
, "(");
267 endparen
= strrchr(tmp
, ')');
271 ast_log(LOG_WARNING
, "Ouch. No closing paren: '%s'?\n", (char *)data
);
272 AST_STANDARD_APP_ARGS(args2
, tmp
);
276 /* Create the return address, but don't save it until we know that the Gosub destination exists */
277 newframe
= gosub_allocate_frame(chan
->context
, chan
->exten
, chan
->priority
+ 1, args2
.argc
);
282 if (ast_parseable_goto(chan
, label
)) {
283 ast_log(LOG_ERROR
, "Gosub address is invalid: '%s'\n", (char *)data
);
288 /* Now that we know for certain that we're going to a new location, set our arguments */
289 for (i
= 0; i
< args2
.argc
; i
++) {
290 snprintf(argname
, sizeof(argname
), "ARG%d", i
+ 1);
291 frame_set_var(chan
, newframe
, argname
, args2
.argval
[i
]);
292 ast_debug(1, "Setting '%s' to '%s'\n", argname
, args2
.argval
[i
]);
295 /* And finally, save our return address */
296 oldlist
= stack_store
->data
;
297 AST_LIST_LOCK(oldlist
);
298 AST_LIST_INSERT_HEAD(oldlist
, newframe
, entries
);
299 AST_LIST_UNLOCK(oldlist
);
304 static int gosubif_exec(struct ast_channel
*chan
, void *data
)
308 AST_DECLARE_APP_ARGS(cond
,
312 AST_DECLARE_APP_ARGS(label
,
314 AST_APP_ARG(iffalse
);
317 if (ast_strlen_zero(data
)) {
318 ast_log(LOG_WARNING
, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
322 args
= ast_strdupa(data
);
323 AST_NONSTANDARD_APP_ARGS(cond
, args
, '?');
324 if (cond
.argc
!= 2) {
325 ast_log(LOG_WARNING
, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
329 AST_NONSTANDARD_APP_ARGS(label
, cond
.labels
, ':');
331 if (pbx_checkcondition(cond
.ition
)) {
332 if (!ast_strlen_zero(label
.iftrue
))
333 res
= gosub_exec(chan
, label
.iftrue
);
334 } else if (!ast_strlen_zero(label
.iffalse
)) {
335 res
= gosub_exec(chan
, label
.iffalse
);
341 static int local_read(struct ast_channel
*chan
, const char *cmd
, char *data
, char *buf
, size_t len
)
343 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
344 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
345 struct gosub_stack_frame
*frame
;
346 struct ast_var_t
*variables
;
351 oldlist
= stack_store
->data
;
352 AST_LIST_LOCK(oldlist
);
353 frame
= AST_LIST_FIRST(oldlist
);
354 AST_LIST_TRAVERSE(&frame
->varshead
, variables
, entries
) {
355 if (!strcmp(data
, ast_var_name(variables
))) {
357 ast_channel_lock(chan
);
358 tmp
= pbx_builtin_getvar_helper(chan
, data
);
359 ast_copy_string(buf
, S_OR(tmp
, ""), len
);
360 ast_channel_unlock(chan
);
364 AST_LIST_UNLOCK(oldlist
);
368 static int local_write(struct ast_channel
*chan
, const char *cmd
, char *var
, const char *value
)
370 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
371 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
372 struct gosub_stack_frame
*frame
;
375 ast_log(LOG_ERROR
, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var
);
379 oldlist
= stack_store
->data
;
380 AST_LIST_LOCK(oldlist
);
381 frame
= AST_LIST_FIRST(oldlist
);
384 frame_set_var(chan
, frame
, var
, value
);
386 AST_LIST_UNLOCK(oldlist
);
391 static struct ast_custom_function local_function
= {
393 .synopsis
= "Variables local to the gosub stack frame",
394 .syntax
= "LOCAL(<varname>)",
395 .write
= local_write
,
399 static int handle_gosub(struct ast_channel
*chan
, AGI
*agi
, int argc
, char **argv
)
401 int old_priority
, priority
;
402 char old_context
[AST_MAX_CONTEXT
], old_extension
[AST_MAX_EXTENSION
];
403 struct ast_app
*theapp
;
406 if (argc
< 4 || argc
> 5) {
407 return RESULT_SHOWUSAGE
;
410 ast_debug(1, "Gosub called with %d arguments: 0:%s 1:%s 2:%s 3:%s 4:%s\n", argc
, argv
[0], argv
[1], argv
[2], argv
[3], argc
== 5 ? argv
[4] : "");
412 if (sscanf(argv
[3], "%d", &priority
) != 1 || priority
< 1) {
413 /* Lookup the priority label */
414 if ((priority
= ast_findlabel_extension(chan
, argv
[1], argv
[2], argv
[3], chan
->cid
.cid_num
)) < 0) {
415 ast_log(LOG_ERROR
, "Priority '%s' not found in '%s@%s'\n", argv
[3], argv
[2], argv
[1]);
416 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=-1 Gosub label not found\n");
417 return RESULT_FAILURE
;
419 } else if (!ast_exists_extension(chan
, argv
[1], argv
[2], priority
, chan
->cid
.cid_num
)) {
420 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=-1 Gosub label not found\n");
421 return RESULT_FAILURE
;
424 /* Save previous location, since we're going to change it */
425 ast_copy_string(old_context
, chan
->context
, sizeof(old_context
));
426 ast_copy_string(old_extension
, chan
->exten
, sizeof(old_extension
));
427 old_priority
= chan
->priority
;
429 if (!(theapp
= pbx_findapp("Gosub"))) {
430 ast_log(LOG_ERROR
, "Gosub() cannot be found in the list of loaded applications\n");
431 ast_agi_fdprintf(chan
, agi
->fd
, "503 result=-2 Gosub is not loaded\n");
432 return RESULT_FAILURE
;
435 /* Apparently, if you run ast_pbx_run on a channel that already has a pbx
436 * structure, you need to add 1 to the priority to get it to go to the
437 * right place. But if it doesn't have a pbx structure, then leaving off
438 * the 1 is the right thing to do. See how this code differs when we
439 * call a Gosub for the CALLEE channel in Dial or Queue.
442 asprintf(&gosub_args
, "%s,%s,%d(%s)", argv
[1], argv
[2], priority
+ 1, argv
[4]);
444 asprintf(&gosub_args
, "%s,%s,%d", argv
[1], argv
[2], priority
+ 1);
450 ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args
);
451 ast_copy_string(chan
->context
, "app_stack_gosub_virtual_context", sizeof(chan
->context
));
452 ast_copy_string(chan
->exten
, "s", sizeof(chan
->exten
));
455 if ((res
= pbx_exec(chan
, theapp
, gosub_args
)) == 0) {
456 struct ast_pbx
*pbx
= chan
->pbx
;
457 /* Suppress warning about PBX already existing */
459 ast_agi_fdprintf(chan
, agi
->fd
, "100 result=0 Trying...\n");
461 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=0 Gosub complete\n");
467 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=%d Gosub failed\n", res
);
469 ast_free(gosub_args
);
471 ast_agi_fdprintf(chan
, agi
->fd
, "503 result=-2 Memory allocation failure\n");
472 return RESULT_FAILURE
;
475 /* Restore previous location */
476 ast_copy_string(chan
->context
, old_context
, sizeof(chan
->context
));
477 ast_copy_string(chan
->exten
, old_extension
, sizeof(chan
->exten
));
478 chan
->priority
= old_priority
;
480 return RESULT_SUCCESS
;
483 static char usage_gosub
[] =
484 " Usage: GOSUB <context> <extension> <priority> [<optional-argument>]\n"
485 " Cause the channel to execute the specified dialplan subroutine, returning\n"
486 " to the dialplan with execution of a Return()\n";
488 struct agi_command gosub_agi_command
=
489 { { "gosub", NULL
}, handle_gosub
, "Execute a dialplan subroutine", usage_gosub
, 0 };
491 static int unload_module(void)
493 struct ast_context
*con
;
496 ast_agi_unregister(ast_module_info
->self
, &gosub_agi_command
);
498 if ((con
= ast_context_find("app_stack_gosub_virtual_context"))) {
499 ast_context_remove_extension2(con
, "s", 1, NULL
, 0);
500 ast_context_destroy(con
, "app_stack"); /* leave nothing behind */
504 ast_unregister_application(app_return
);
505 ast_unregister_application(app_pop
);
506 ast_unregister_application(app_gosubif
);
507 ast_unregister_application(app_gosub
);
508 ast_custom_function_unregister(&local_function
);
513 static int load_module(void)
515 struct ast_context
*con
;
517 if (!ast_module_check("res_agi.so")) {
518 if (ast_load_resource("res_agi.so") == AST_MODULE_LOAD_SUCCESS
) {
526 con
= ast_context_find_or_create(NULL
, NULL
, "app_stack_gosub_virtual_context", "app_stack");
528 ast_log(LOG_ERROR
, "Virtual context 'app_stack_gosub_virtual_context' does not exist and unable to create\n");
529 return AST_MODULE_LOAD_DECLINE
;
531 ast_add_extension2(con
, 1, "s", 1, NULL
, NULL
, "KeepAlive", ast_strdup(""), ast_free_ptr
, "app_stack");
534 ast_agi_register(ast_module_info
->self
, &gosub_agi_command
);
537 ast_register_application(app_pop
, pop_exec
, pop_synopsis
, pop_descrip
);
538 ast_register_application(app_return
, return_exec
, return_synopsis
, return_descrip
);
539 ast_register_application(app_gosubif
, gosubif_exec
, gosubif_synopsis
, gosubif_descrip
);
540 ast_register_application(app_gosub
, gosub_exec
, gosub_synopsis
, gosub_descrip
);
541 ast_custom_function_register(&local_function
);
546 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Dialplan subroutines (Gosub, Return, etc)");