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 const char *app_gosub
= "Gosub";
40 static const char *app_gosubif
= "GosubIf";
41 static const char *app_return
= "Return";
42 static const char *app_pop
= "StackPop";
44 static const char *gosub_synopsis
= "Jump to label, saving return address";
45 static const char *gosubif_synopsis
= "Conditionally jump to label, saving return address";
46 static const char *return_synopsis
= "Return from gosub routine";
47 static const char *pop_synopsis
= "Remove one address from gosub stack";
49 static const char *gosub_descrip
=
50 " Gosub([[context,]exten,]priority[(arg1[,...][,argN])]):\n"
51 "Jumps to the label specified, saving the return address.\n";
52 static const char *gosubif_descrip
=
53 " GosubIf(condition?labeliftrue[(arg1[,...])][:labeliffalse[(arg1[,...])]]):\n"
54 "If the condition is true, then jump to labeliftrue. If false, jumps to\n"
55 "labeliffalse, if specified. In either case, a jump saves the return point\n"
56 "in the dialplan, to be returned to with a Return.\n";
57 static const char *return_descrip
=
58 " Return([return-value]):\n"
59 "Jumps to the last label on the stack, removing it. The return value, if\n"
60 "any, is saved in the channel variable GOSUB_RETVAL.\n";
61 static const char *pop_descrip
=
63 "Removes last label on the stack, discarding it.\n";
65 static void gosub_free(void *data
);
67 static struct ast_datastore_info stack_info
= {
69 .destroy
= gosub_free
,
72 struct gosub_stack_frame
{
73 AST_LIST_ENTRY(gosub_stack_frame
) entries
;
74 /* 100 arguments is all that we support anyway, but this will handle up to 255 */
75 unsigned char arguments
;
76 struct varshead varshead
;
82 static int frame_set_var(struct ast_channel
*chan
, struct gosub_stack_frame
*frame
, const char *var
, const char *value
)
84 struct ast_var_t
*variables
;
87 /* Does this variable already exist? */
88 AST_LIST_TRAVERSE(&frame
->varshead
, variables
, entries
) {
89 if (!strcmp(var
, ast_var_name(variables
))) {
95 if (!ast_strlen_zero(value
)) {
97 variables
= ast_var_assign(var
, "");
98 AST_LIST_INSERT_HEAD(&frame
->varshead
, variables
, entries
);
99 pbx_builtin_pushvar_helper(chan
, var
, value
);
101 pbx_builtin_setvar_helper(chan
, var
, value
);
103 manager_event(EVENT_FLAG_DIALPLAN
, "VarSet",
105 "Variable: LOCAL(%s)\r\n"
108 chan
->name
, var
, value
, chan
->uniqueid
);
113 static void gosub_release_frame(struct ast_channel
*chan
, struct gosub_stack_frame
*frame
)
117 struct ast_var_t
*vardata
;
119 /* If chan is not defined, then we're calling it as part of gosub_free,
120 * and the channel variables will be deallocated anyway. Otherwise, we're
121 * just releasing a single frame, so we need to clean up the arguments for
122 * that frame, so that we re-expose the variables from the previous frame
123 * that were hidden by this one.
126 for (i
= 1; i
<= frame
->arguments
&& i
!= 0; i
++) {
127 snprintf(argname
, sizeof(argname
), "ARG%hhd", i
);
128 pbx_builtin_setvar_helper(chan
, argname
, NULL
);
132 /* Delete local variables */
133 while ((vardata
= AST_LIST_REMOVE_HEAD(&frame
->varshead
, entries
))) {
135 pbx_builtin_setvar_helper(chan
, ast_var_name(vardata
), NULL
);
136 ast_var_delete(vardata
);
142 static struct gosub_stack_frame
*gosub_allocate_frame(const char *context
, const char *extension
, int priority
, unsigned char arguments
)
144 struct gosub_stack_frame
*new = NULL
;
145 int len_extension
= strlen(extension
), len_context
= strlen(context
);
147 if ((new = ast_calloc(1, sizeof(*new) + 2 + len_extension
+ len_context
))) {
148 AST_LIST_HEAD_INIT_NOLOCK(&new->varshead
);
149 strcpy(new->extension
, extension
);
150 new->context
= new->extension
+ len_extension
+ 1;
151 strcpy(new->context
, context
);
152 new->priority
= priority
;
153 new->arguments
= arguments
;
158 static void gosub_free(void *data
)
160 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
= data
;
161 struct gosub_stack_frame
*oldframe
;
162 AST_LIST_LOCK(oldlist
);
163 while ((oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
))) {
164 gosub_release_frame(NULL
, oldframe
);
166 AST_LIST_UNLOCK(oldlist
);
167 AST_LIST_HEAD_DESTROY(oldlist
);
171 static int pop_exec(struct ast_channel
*chan
, void *data
)
173 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
174 struct gosub_stack_frame
*oldframe
;
175 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
178 ast_log(LOG_WARNING
, "%s called with no gosub stack allocated.\n", app_pop
);
182 oldlist
= stack_store
->data
;
183 AST_LIST_LOCK(oldlist
);
184 oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
);
185 AST_LIST_UNLOCK(oldlist
);
188 gosub_release_frame(chan
, oldframe
);
190 ast_debug(1, "%s called with an empty gosub stack\n", app_pop
);
195 static int return_exec(struct ast_channel
*chan
, void *data
)
197 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
198 struct gosub_stack_frame
*oldframe
;
199 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
203 ast_log(LOG_ERROR
, "Return without Gosub: stack is unallocated\n");
207 oldlist
= stack_store
->data
;
208 AST_LIST_LOCK(oldlist
);
209 oldframe
= AST_LIST_REMOVE_HEAD(oldlist
, entries
);
210 AST_LIST_UNLOCK(oldlist
);
213 ast_log(LOG_ERROR
, "Return without Gosub: stack is empty\n");
217 ast_explicit_goto(chan
, oldframe
->context
, oldframe
->extension
, oldframe
->priority
);
218 gosub_release_frame(chan
, oldframe
);
220 /* Set a return value, if any */
221 pbx_builtin_setvar_helper(chan
, "GOSUB_RETVAL", S_OR(retval
, ""));
225 static int gosub_exec(struct ast_channel
*chan
, void *data
)
227 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
228 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
229 struct gosub_stack_frame
*newframe
;
230 char argname
[15], *tmp
= ast_strdupa(data
), *label
, *endparen
;
232 AST_DECLARE_APP_ARGS(args2
,
233 AST_APP_ARG(argval
)[100];
236 if (ast_strlen_zero(data
)) {
237 ast_log(LOG_ERROR
, "%s requires an argument: %s([[context,]exten,]priority[(arg1[,...][,argN])])\n", app_gosub
, app_gosub
);
242 ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan
->name
);
243 stack_store
= ast_channel_datastore_alloc(&stack_info
, NULL
);
245 ast_log(LOG_ERROR
, "Unable to allocate new datastore. Gosub will fail.\n");
249 oldlist
= ast_calloc(1, sizeof(*oldlist
));
251 ast_log(LOG_ERROR
, "Unable to allocate datastore list head. Gosub will fail.\n");
252 ast_channel_datastore_free(stack_store
);
256 stack_store
->data
= oldlist
;
257 AST_LIST_HEAD_INIT(oldlist
);
258 ast_channel_datastore_add(chan
, stack_store
);
261 /* Separate the arguments from the label */
262 /* NOTE: you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */
263 label
= strsep(&tmp
, "(");
265 endparen
= strrchr(tmp
, ')');
269 ast_log(LOG_WARNING
, "Ouch. No closing paren: '%s'?\n", (char *)data
);
270 AST_STANDARD_APP_ARGS(args2
, tmp
);
274 /* Create the return address, but don't save it until we know that the Gosub destination exists */
275 newframe
= gosub_allocate_frame(chan
->context
, chan
->exten
, chan
->priority
+ 1, args2
.argc
);
280 if (ast_parseable_goto(chan
, label
)) {
281 ast_log(LOG_ERROR
, "Gosub address is invalid: '%s'\n", (char *)data
);
286 /* Now that we know for certain that we're going to a new location, set our arguments */
287 for (i
= 0; i
< args2
.argc
; i
++) {
288 snprintf(argname
, sizeof(argname
), "ARG%d", i
+ 1);
289 frame_set_var(chan
, newframe
, argname
, args2
.argval
[i
]);
290 ast_debug(1, "Setting '%s' to '%s'\n", argname
, args2
.argval
[i
]);
293 /* And finally, save our return address */
294 oldlist
= stack_store
->data
;
295 AST_LIST_LOCK(oldlist
);
296 AST_LIST_INSERT_HEAD(oldlist
, newframe
, entries
);
297 AST_LIST_UNLOCK(oldlist
);
302 static int gosubif_exec(struct ast_channel
*chan
, void *data
)
306 AST_DECLARE_APP_ARGS(cond
,
310 AST_DECLARE_APP_ARGS(label
,
312 AST_APP_ARG(iffalse
);
315 if (ast_strlen_zero(data
)) {
316 ast_log(LOG_WARNING
, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
320 args
= ast_strdupa(data
);
321 AST_NONSTANDARD_APP_ARGS(cond
, args
, '?');
322 if (cond
.argc
!= 2) {
323 ast_log(LOG_WARNING
, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
327 AST_NONSTANDARD_APP_ARGS(label
, cond
.labels
, ':');
329 if (pbx_checkcondition(cond
.ition
)) {
330 if (!ast_strlen_zero(label
.iftrue
))
331 res
= gosub_exec(chan
, label
.iftrue
);
332 } else if (!ast_strlen_zero(label
.iffalse
)) {
333 res
= gosub_exec(chan
, label
.iffalse
);
339 static int local_read(struct ast_channel
*chan
, const char *cmd
, char *data
, char *buf
, size_t len
)
341 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
342 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
343 struct gosub_stack_frame
*frame
;
344 struct ast_var_t
*variables
;
349 oldlist
= stack_store
->data
;
350 AST_LIST_LOCK(oldlist
);
351 frame
= AST_LIST_FIRST(oldlist
);
352 AST_LIST_TRAVERSE(&frame
->varshead
, variables
, entries
) {
353 if (!strcmp(data
, ast_var_name(variables
))) {
355 ast_channel_lock(chan
);
356 tmp
= pbx_builtin_getvar_helper(chan
, data
);
357 ast_copy_string(buf
, S_OR(tmp
, ""), len
);
358 ast_channel_unlock(chan
);
362 AST_LIST_UNLOCK(oldlist
);
366 static int local_write(struct ast_channel
*chan
, const char *cmd
, char *var
, const char *value
)
368 struct ast_datastore
*stack_store
= ast_channel_datastore_find(chan
, &stack_info
, NULL
);
369 AST_LIST_HEAD(, gosub_stack_frame
) *oldlist
;
370 struct gosub_stack_frame
*frame
;
373 ast_log(LOG_ERROR
, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var
);
377 oldlist
= stack_store
->data
;
378 AST_LIST_LOCK(oldlist
);
379 frame
= AST_LIST_FIRST(oldlist
);
382 frame_set_var(chan
, frame
, var
, value
);
384 AST_LIST_UNLOCK(oldlist
);
389 static struct ast_custom_function local_function
= {
391 .synopsis
= "Variables local to the gosub stack frame",
392 .syntax
= "LOCAL(<varname>)",
393 .write
= local_write
,
397 static int handle_gosub(struct ast_channel
*chan
, AGI
*agi
, int argc
, char **argv
)
399 int old_priority
, priority
;
400 char old_context
[AST_MAX_CONTEXT
], old_extension
[AST_MAX_EXTENSION
];
401 struct ast_app
*theapp
;
404 if (argc
< 4 || argc
> 5) {
405 return RESULT_SHOWUSAGE
;
408 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] : "");
410 if (sscanf(argv
[3], "%d", &priority
) != 1 || priority
< 1) {
411 /* Lookup the priority label */
412 if ((priority
= ast_findlabel_extension(chan
, argv
[1], argv
[2], argv
[3], chan
->cid
.cid_num
)) < 0) {
413 ast_log(LOG_ERROR
, "Priority '%s' not found in '%s@%s'\n", argv
[3], argv
[2], argv
[1]);
414 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=-1 Gosub label not found\n");
415 return RESULT_FAILURE
;
417 } else if (!ast_exists_extension(chan
, argv
[1], argv
[2], priority
, chan
->cid
.cid_num
)) {
418 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=-1 Gosub label not found\n");
419 return RESULT_FAILURE
;
422 /* Save previous location, since we're going to change it */
423 ast_copy_string(old_context
, chan
->context
, sizeof(old_context
));
424 ast_copy_string(old_extension
, chan
->exten
, sizeof(old_extension
));
425 old_priority
= chan
->priority
;
427 if (!(theapp
= pbx_findapp("Gosub"))) {
428 ast_log(LOG_ERROR
, "Gosub() cannot be found in the list of loaded applications\n");
429 ast_agi_fdprintf(chan
, agi
->fd
, "503 result=-2 Gosub is not loaded\n");
430 return RESULT_FAILURE
;
433 /* Apparently, if you run ast_pbx_run on a channel that already has a pbx
434 * structure, you need to add 1 to the priority to get it to go to the
435 * right place. But if it doesn't have a pbx structure, then leaving off
436 * the 1 is the right thing to do. See how this code differs when we
437 * call a Gosub for the CALLEE channel in Dial or Queue.
440 asprintf(&gosub_args
, "%s,%s,%d(%s)", argv
[1], argv
[2], priority
+ 1, argv
[4]);
442 asprintf(&gosub_args
, "%s,%s,%d", argv
[1], argv
[2], priority
+ 1);
448 ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args
);
449 ast_copy_string(chan
->context
, "app_stack_gosub_virtual_context", sizeof(chan
->context
));
450 ast_copy_string(chan
->exten
, "s", sizeof(chan
->exten
));
453 if ((res
= pbx_exec(chan
, theapp
, gosub_args
)) == 0) {
454 struct ast_pbx
*pbx
= chan
->pbx
;
455 /* Suppress warning about PBX already existing */
457 ast_agi_fdprintf(chan
, agi
->fd
, "100 result=0 Trying...\n");
459 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=0 Gosub complete\n");
465 ast_agi_fdprintf(chan
, agi
->fd
, "200 result=%d Gosub failed\n", res
);
467 ast_free(gosub_args
);
469 ast_agi_fdprintf(chan
, agi
->fd
, "503 result=-2 Memory allocation failure\n");
470 return RESULT_FAILURE
;
473 /* Restore previous location */
474 ast_copy_string(chan
->context
, old_context
, sizeof(chan
->context
));
475 ast_copy_string(chan
->exten
, old_extension
, sizeof(chan
->exten
));
476 chan
->priority
= old_priority
;
478 return RESULT_SUCCESS
;
481 static char usage_gosub
[] =
482 " Usage: GOSUB <context> <extension> <priority> [<optional-argument>]\n"
483 " Cause the channel to execute the specified dialplan subroutine, returning\n"
484 " to the dialplan with execution of a Return()\n";
486 struct agi_command gosub_agi_command
=
487 { { "gosub", NULL
}, handle_gosub
, "Execute a dialplan subroutine", usage_gosub
, 0 };
489 static int unload_module(void)
491 struct ast_context
*con
;
493 if ((con
= ast_context_find("app_stack_gosub_virtual_context"))) {
494 ast_context_remove_extension2(con
, "s", 1, NULL
);
495 ast_context_destroy(con
, "app_stack"); /* leave nothing behind */
498 ast_agi_unregister(ast_module_info
->self
, &gosub_agi_command
);
499 ast_unregister_application(app_return
);
500 ast_unregister_application(app_pop
);
501 ast_unregister_application(app_gosubif
);
502 ast_unregister_application(app_gosub
);
503 ast_custom_function_unregister(&local_function
);
508 static int load_module(void)
510 struct ast_context
*con
;
511 con
= ast_context_find_or_create(NULL
, NULL
, "app_stack_gosub_virtual_context", "app_stack");
513 ast_log(LOG_ERROR
, "Virtual context 'app_stack_gosub_virtual_context' does not exist and unable to create\n");
514 return AST_MODULE_LOAD_DECLINE
;
516 ast_add_extension2(con
, 1, "s", 1, NULL
, NULL
, "KeepAlive", ast_strdup(""), ast_free_ptr
, "app_stack");
519 ast_register_application(app_pop
, pop_exec
, pop_synopsis
, pop_descrip
);
520 ast_register_application(app_return
, return_exec
, return_synopsis
, return_descrip
);
521 ast_register_application(app_gosubif
, gosubif_exec
, gosubif_synopsis
, gosubif_descrip
);
522 ast_register_application(app_gosub
, gosub_exec
, gosub_synopsis
, gosub_descrip
);
523 ast_agi_register(ast_module_info
->self
, &gosub_agi_command
);
524 ast_custom_function_register(&local_function
);
529 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Dialplan subroutines (Gosub, Return, etc)");