00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #include "asterisk.h"
00033
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 195840 $")
00035
00036 #include "asterisk/pbx.h"
00037 #include "asterisk/module.h"
00038 #include "asterisk/app.h"
00039 #include "asterisk/manager.h"
00040 #include "asterisk/channel.h"
00041
00042
00043 #define ASTERISK_AGI_OPTIONAL
00044 #include "asterisk/agi.h"
00045
00046
00047 static const char *app_gosub = "Gosub";
00048 static const char *app_gosubif = "GosubIf";
00049 static const char *app_return = "Return";
00050 static const char *app_pop = "StackPop";
00051
00052 static const char *gosub_synopsis = "Jump to label, saving return address";
00053 static const char *gosubif_synopsis = "Conditionally jump to label, saving return address";
00054 static const char *return_synopsis = "Return from gosub routine";
00055 static const char *pop_synopsis = "Remove one address from gosub stack";
00056
00057 static const char *gosub_descrip =
00058 " Gosub([[context,]exten,]priority[(arg1[,...][,argN])]):\n"
00059 "Jumps to the label specified, saving the return address.\n";
00060 static const char *gosubif_descrip =
00061 " GosubIf(condition?labeliftrue[(arg1[,...])][:labeliffalse[(arg1[,...])]]):\n"
00062 "If the condition is true, then jump to labeliftrue. If false, jumps to\n"
00063 "labeliffalse, if specified. In either case, a jump saves the return point\n"
00064 "in the dialplan, to be returned to with a Return.\n";
00065 static const char *return_descrip =
00066 " Return([return-value]):\n"
00067 "Jumps to the last label on the stack, removing it. The return value, if\n"
00068 "any, is saved in the channel variable GOSUB_RETVAL.\n";
00069 static const char *pop_descrip =
00070 " StackPop():\n"
00071 "Removes last label on the stack, discarding it.\n";
00072
00073 static void gosub_free(void *data);
00074
00075 static struct ast_datastore_info stack_info = {
00076 .type = "GOSUB",
00077 .destroy = gosub_free,
00078 };
00079
00080 struct gosub_stack_frame {
00081 AST_LIST_ENTRY(gosub_stack_frame) entries;
00082
00083 unsigned char arguments;
00084 struct varshead varshead;
00085 int priority;
00086 char *context;
00087 char extension[0];
00088 };
00089
00090 static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *frame, const char *var, const char *value)
00091 {
00092 struct ast_var_t *variables;
00093 int found = 0;
00094
00095
00096 AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
00097 if (!strcmp(var, ast_var_name(variables))) {
00098 found = 1;
00099 break;
00100 }
00101 }
00102
00103 if (!found) {
00104 variables = ast_var_assign(var, "");
00105 AST_LIST_INSERT_HEAD(&frame->varshead, variables, entries);
00106 pbx_builtin_pushvar_helper(chan, var, value);
00107 } else {
00108 pbx_builtin_setvar_helper(chan, var, value);
00109 }
00110
00111 manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
00112 "Channel: %s\r\n"
00113 "Variable: LOCAL(%s)\r\n"
00114 "Value: %s\r\n"
00115 "Uniqueid: %s\r\n",
00116 chan->name, var, value, chan->uniqueid);
00117 return 0;
00118 }
00119
00120 static void gosub_release_frame(struct ast_channel *chan, struct gosub_stack_frame *frame)
00121 {
00122 struct ast_var_t *vardata;
00123
00124
00125
00126
00127
00128
00129
00130 while ((vardata = AST_LIST_REMOVE_HEAD(&frame->varshead, entries))) {
00131 if (chan)
00132 pbx_builtin_setvar_helper(chan, ast_var_name(vardata), NULL);
00133 ast_var_delete(vardata);
00134 }
00135
00136 ast_free(frame);
00137 }
00138
00139 static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const char *extension, int priority, unsigned char arguments)
00140 {
00141 struct gosub_stack_frame *new = NULL;
00142 int len_extension = strlen(extension), len_context = strlen(context);
00143
00144 if ((new = ast_calloc(1, sizeof(*new) + 2 + len_extension + len_context))) {
00145 AST_LIST_HEAD_INIT_NOLOCK(&new->varshead);
00146 strcpy(new->extension, extension);
00147 new->context = new->extension + len_extension + 1;
00148 strcpy(new->context, context);
00149 new->priority = priority;
00150 new->arguments = arguments;
00151 }
00152 return new;
00153 }
00154
00155 static void gosub_free(void *data)
00156 {
00157 AST_LIST_HEAD(, gosub_stack_frame) *oldlist = data;
00158 struct gosub_stack_frame *oldframe;
00159 AST_LIST_LOCK(oldlist);
00160 while ((oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries))) {
00161 gosub_release_frame(NULL, oldframe);
00162 }
00163 AST_LIST_UNLOCK(oldlist);
00164 AST_LIST_HEAD_DESTROY(oldlist);
00165 ast_free(oldlist);
00166 }
00167
00168 static int pop_exec(struct ast_channel *chan, void *data)
00169 {
00170 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00171 struct gosub_stack_frame *oldframe;
00172 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00173
00174 if (!stack_store) {
00175 ast_log(LOG_WARNING, "%s called with no gosub stack allocated.\n", app_pop);
00176 return 0;
00177 }
00178
00179 oldlist = stack_store->data;
00180 AST_LIST_LOCK(oldlist);
00181 oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
00182 AST_LIST_UNLOCK(oldlist);
00183
00184 if (oldframe) {
00185 gosub_release_frame(chan, oldframe);
00186 } else {
00187 ast_debug(1, "%s called with an empty gosub stack\n", app_pop);
00188 }
00189 return 0;
00190 }
00191
00192 static int return_exec(struct ast_channel *chan, void *data)
00193 {
00194 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00195 struct gosub_stack_frame *oldframe;
00196 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00197 char *retval = data;
00198
00199 if (!stack_store) {
00200 ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n");
00201 return -1;
00202 }
00203
00204 oldlist = stack_store->data;
00205 AST_LIST_LOCK(oldlist);
00206 oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
00207 AST_LIST_UNLOCK(oldlist);
00208
00209 if (!oldframe) {
00210 ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
00211 return -1;
00212 }
00213
00214 ast_explicit_goto(chan, oldframe->context, oldframe->extension, oldframe->priority);
00215 gosub_release_frame(chan, oldframe);
00216
00217
00218 pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, ""));
00219 return 0;
00220 }
00221
00222 static int gosub_exec(struct ast_channel *chan, void *data)
00223 {
00224 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00225 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00226 struct gosub_stack_frame *newframe;
00227 char argname[15], *tmp = ast_strdupa(data), *label, *endparen;
00228 int i;
00229 AST_DECLARE_APP_ARGS(args2,
00230 AST_APP_ARG(argval)[100];
00231 );
00232
00233 if (ast_strlen_zero(data)) {
00234 ast_log(LOG_ERROR, "%s requires an argument: %s([[context,]exten,]priority[(arg1[,...][,argN])])\n", app_gosub, app_gosub);
00235 return -1;
00236 }
00237
00238 if (!stack_store) {
00239 ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan->name);
00240 stack_store = ast_channel_datastore_alloc(&stack_info, NULL);
00241 if (!stack_store) {
00242 ast_log(LOG_ERROR, "Unable to allocate new datastore. Gosub will fail.\n");
00243 return -1;
00244 }
00245
00246 oldlist = ast_calloc(1, sizeof(*oldlist));
00247 if (!oldlist) {
00248 ast_log(LOG_ERROR, "Unable to allocate datastore list head. Gosub will fail.\n");
00249 ast_channel_datastore_free(stack_store);
00250 return -1;
00251 }
00252
00253 stack_store->data = oldlist;
00254 AST_LIST_HEAD_INIT(oldlist);
00255 ast_channel_datastore_add(chan, stack_store);
00256 }
00257
00258
00259
00260 label = strsep(&tmp, "(");
00261 if (tmp) {
00262 endparen = strrchr(tmp, ')');
00263 if (endparen)
00264 *endparen = '\0';
00265 else
00266 ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", (char *)data);
00267 AST_STANDARD_APP_ARGS(args2, tmp);
00268 } else
00269 args2.argc = 0;
00270
00271
00272 newframe = gosub_allocate_frame(chan->context, chan->exten, chan->priority + 1, args2.argc);
00273
00274 if (!newframe) {
00275 return -1;
00276 }
00277
00278 if (ast_parseable_goto(chan, label)) {
00279 ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data);
00280 ast_free(newframe);
00281 return -1;
00282 }
00283
00284 if (!ast_exists_extension(chan, chan->context, chan->exten, ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP) ? chan->priority + 1 : chan->priority, chan->cid.cid_num)) {
00285 ast_log(LOG_ERROR, "Attempt to reach a non-existent destination for gosub: (Context:%s, Extension:%s, Priority:%d)\n",
00286 chan->context, chan->exten, chan->priority);
00287 ast_copy_string(chan->context, newframe->context, sizeof(chan->context));
00288 ast_copy_string(chan->exten, newframe->extension, sizeof(chan->exten));
00289 chan->priority = newframe->priority;
00290 ast_free(newframe);
00291 return -1;
00292 }
00293
00294
00295 for (i = 0; i < args2.argc; i++) {
00296 snprintf(argname, sizeof(argname), "ARG%d", i + 1);
00297 frame_set_var(chan, newframe, argname, args2.argval[i]);
00298 ast_debug(1, "Setting '%s' to '%s'\n", argname, args2.argval[i]);
00299 }
00300
00301
00302 oldlist = stack_store->data;
00303 AST_LIST_LOCK(oldlist);
00304 AST_LIST_INSERT_HEAD(oldlist, newframe, entries);
00305 AST_LIST_UNLOCK(oldlist);
00306
00307 return 0;
00308 }
00309
00310 static int gosubif_exec(struct ast_channel *chan, void *data)
00311 {
00312 char *args;
00313 int res=0;
00314 AST_DECLARE_APP_ARGS(cond,
00315 AST_APP_ARG(ition);
00316 AST_APP_ARG(labels);
00317 );
00318 AST_DECLARE_APP_ARGS(label,
00319 AST_APP_ARG(iftrue);
00320 AST_APP_ARG(iffalse);
00321 );
00322
00323 if (ast_strlen_zero(data)) {
00324 ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
00325 return 0;
00326 }
00327
00328 args = ast_strdupa(data);
00329 AST_NONSTANDARD_APP_ARGS(cond, args, '?');
00330 if (cond.argc != 2) {
00331 ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
00332 return 0;
00333 }
00334
00335 AST_NONSTANDARD_APP_ARGS(label, cond.labels, ':');
00336
00337 if (pbx_checkcondition(cond.ition)) {
00338 if (!ast_strlen_zero(label.iftrue))
00339 res = gosub_exec(chan, label.iftrue);
00340 } else if (!ast_strlen_zero(label.iffalse)) {
00341 res = gosub_exec(chan, label.iffalse);
00342 }
00343
00344 return res;
00345 }
00346
00347 static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00348 {
00349 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00350 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00351 struct gosub_stack_frame *frame;
00352 struct ast_var_t *variables;
00353
00354 if (!stack_store)
00355 return -1;
00356
00357 oldlist = stack_store->data;
00358 AST_LIST_LOCK(oldlist);
00359 frame = AST_LIST_FIRST(oldlist);
00360 AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
00361 if (!strcmp(data, ast_var_name(variables))) {
00362 const char *tmp = pbx_builtin_getvar_helper(chan, data);
00363 ast_copy_string(buf, S_OR(tmp, ""), len);
00364 break;
00365 }
00366 }
00367 AST_LIST_UNLOCK(oldlist);
00368 return 0;
00369 }
00370
00371 static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
00372 {
00373 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00374 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00375 struct gosub_stack_frame *frame;
00376
00377 if (!stack_store) {
00378 ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var);
00379 return -1;
00380 }
00381
00382 oldlist = stack_store->data;
00383 AST_LIST_LOCK(oldlist);
00384 frame = AST_LIST_FIRST(oldlist);
00385
00386 if (frame)
00387 frame_set_var(chan, frame, var, value);
00388
00389 AST_LIST_UNLOCK(oldlist);
00390
00391 return 0;
00392 }
00393
00394 static struct ast_custom_function local_function = {
00395 .name = "LOCAL",
00396 .synopsis = "Variables local to the gosub stack frame",
00397 .syntax = "LOCAL(<varname>)",
00398 .write = local_write,
00399 .read = local_read,
00400 };
00401
00402 static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, char **argv)
00403 {
00404 int old_priority, priority;
00405 char old_context[AST_MAX_CONTEXT], old_extension[AST_MAX_EXTENSION];
00406 struct ast_app *theapp;
00407 char *gosub_args;
00408
00409 if (argc < 4 || argc > 5) {
00410 return RESULT_SHOWUSAGE;
00411 }
00412
00413 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] : "");
00414
00415 if (sscanf(argv[3], "%d", &priority) != 1 || priority < 1) {
00416
00417 if ((priority = ast_findlabel_extension(chan, argv[1], argv[2], argv[3], chan->cid.cid_num)) < 0) {
00418 ast_log(LOG_ERROR, "Priority '%s' not found in '%s@%s'\n", argv[3], argv[2], argv[1]);
00419 ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
00420 return RESULT_FAILURE;
00421 }
00422 } else if (!ast_exists_extension(chan, argv[1], argv[2], priority, chan->cid.cid_num)) {
00423 ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
00424 return RESULT_FAILURE;
00425 }
00426
00427
00428 ast_copy_string(old_context, chan->context, sizeof(old_context));
00429 ast_copy_string(old_extension, chan->exten, sizeof(old_extension));
00430 old_priority = chan->priority;
00431
00432 if (!(theapp = pbx_findapp("Gosub"))) {
00433 ast_log(LOG_ERROR, "Gosub() cannot be found in the list of loaded applications\n");
00434 ast_agi_send(agi->fd, chan, "503 result=-2 Gosub is not loaded\n");
00435 return RESULT_FAILURE;
00436 }
00437
00438
00439
00440
00441
00442
00443
00444 if (argc == 5) {
00445 if (asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority + 1, argv[4]) < 0) {
00446 ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00447 gosub_args = NULL;
00448 }
00449 } else {
00450 if (asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority + 1) < 0) {
00451 ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00452 gosub_args = NULL;
00453 }
00454 }
00455
00456 if (gosub_args) {
00457 int res;
00458
00459 ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args);
00460 ast_copy_string(chan->context, "app_stack_gosub_virtual_context", sizeof(chan->context));
00461 ast_copy_string(chan->exten, "s", sizeof(chan->exten));
00462 chan->priority = 0;
00463
00464 if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) {
00465 struct ast_pbx *pbx = chan->pbx;
00466 struct ast_pbx_args args;
00467 memset(&args, 0, sizeof(args));
00468 args.no_hangup_chan = 1;
00469
00470 chan->pbx = NULL;
00471 ast_agi_send(agi->fd, chan, "100 result=0 Trying...\n");
00472 ast_pbx_run_args(chan, &args);
00473 ast_agi_send(agi->fd, chan, "200 result=0 Gosub complete\n");
00474 if (chan->pbx) {
00475 ast_free(chan->pbx);
00476 }
00477 chan->pbx = pbx;
00478 } else {
00479 ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res);
00480 }
00481 ast_free(gosub_args);
00482 } else {
00483 ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n");
00484 return RESULT_FAILURE;
00485 }
00486
00487
00488 ast_copy_string(chan->context, old_context, sizeof(chan->context));
00489 ast_copy_string(chan->exten, old_extension, sizeof(chan->exten));
00490 chan->priority = old_priority;
00491
00492 return RESULT_SUCCESS;
00493 }
00494
00495 static char usage_gosub[] =
00496 " Usage: GOSUB <context> <extension> <priority> [<optional-argument>]\n"
00497 " Cause the channel to execute the specified dialplan subroutine, returning\n"
00498 " to the dialplan with execution of a Return()\n";
00499
00500 struct agi_command gosub_agi_command =
00501 { { "gosub", NULL }, handle_gosub, "Execute a dialplan subroutine", usage_gosub , 0 };
00502
00503 static int unload_module(void)
00504 {
00505 if (ast_agi_unregister) {
00506 ast_agi_unregister(ast_module_info->self, &gosub_agi_command);
00507 }
00508
00509 ast_unregister_application(app_return);
00510 ast_unregister_application(app_pop);
00511 ast_unregister_application(app_gosubif);
00512 ast_unregister_application(app_gosub);
00513 ast_custom_function_unregister(&local_function);
00514
00515 return 0;
00516 }
00517
00518 static int load_module(void)
00519 {
00520
00521
00522
00523 if (ast_agi_register) {
00524 ast_agi_register(ast_module_info->self, &gosub_agi_command);
00525 }
00526
00527 ast_register_application(app_pop, pop_exec, pop_synopsis, pop_descrip);
00528 ast_register_application(app_return, return_exec, return_synopsis, return_descrip);
00529 ast_register_application(app_gosubif, gosubif_exec, gosubif_synopsis, gosubif_descrip);
00530 ast_register_application(app_gosub, gosub_exec, gosub_synopsis, gosub_descrip);
00531 ast_custom_function_register(&local_function);
00532
00533 return 0;
00534 }
00535
00536 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan subroutines (Gosub, Return, etc)");