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 #include "asterisk.h"
00029
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 186806 $")
00031
00032 #include "asterisk/file.h"
00033 #include "asterisk/channel.h"
00034 #include "asterisk/pbx.h"
00035 #include "asterisk/module.h"
00036 #include "asterisk/config.h"
00037 #include "asterisk/utils.h"
00038 #include "asterisk/lock.h"
00039
00040 #define MAX_ARGS 80
00041
00042
00043 #define MACRO_EXIT_RESULT 1024
00044
00045 static char *descrip =
00046 " Macro(macroname,arg1,arg2...): Executes a macro using the context\n"
00047 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
00048 "executing each step, then returning when the steps end. \n"
00049 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
00050 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
00051 "${ARG1}, ${ARG2}, etc in the macro context.\n"
00052 "If you Goto out of the Macro context, the Macro will terminate and control\n"
00053 "will be returned at the location of the Goto.\n"
00054 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
00055 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
00056 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
00057 " contained within it via sub-engine), and a fixed per-thread memory\n"
00058 " stack allowance, macros are limited to 7 levels of nesting (macro\n"
00059 " calling macro calling macro, etc.); It may be possible that\n"
00060 " stack-intensive applications in deeply nested macros could cause\n"
00061 " Asterisk to crash earlier than this limit. It is advised that if you\n"
00062 " need to deeply nest macro calls, that you use the Gosub application\n"
00063 " (now allows arguments like a Macro) with explict Return() calls\n"
00064 " instead.\n";
00065
00066 static char *if_descrip =
00067 " MacroIf(<expr>?macroname_a[,arg1][:macroname_b[,arg1]])\n"
00068 "Executes macro defined in <macroname_a> if <expr> is true\n"
00069 "(otherwise <macroname_b> if provided)\n"
00070 "Arguments and return values as in application Macro()\n";
00071
00072 static char *exclusive_descrip =
00073 " MacroExclusive(macroname,arg1,arg2...):\n"
00074 "Executes macro defined in the context 'macro-macroname'\n"
00075 "Only one call at a time may run the macro.\n"
00076 "(we'll wait if another call is busy executing in the Macro)\n"
00077 "Arguments and return values as in application Macro()\n";
00078
00079 static char *exit_descrip =
00080 " MacroExit():\n"
00081 "Causes the currently running macro to exit as if it had\n"
00082 "ended normally by running out of priorities to execute.\n"
00083 "If used outside a macro, will likely cause unexpected\n"
00084 "behavior.\n";
00085
00086 static char *app = "Macro";
00087 static char *if_app = "MacroIf";
00088 static char *exclusive_app = "MacroExclusive";
00089 static char *exit_app = "MacroExit";
00090
00091 static char *synopsis = "Macro Implementation";
00092 static char *if_synopsis = "Conditional Macro Implementation";
00093 static char *exclusive_synopsis = "Exclusive Macro Implementation";
00094 static char *exit_synopsis = "Exit From Macro";
00095
00096 static void macro_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
00097
00098 struct ast_datastore_info macro_ds_info = {
00099 .type = "MACRO",
00100 .chan_fixup = macro_fixup,
00101 };
00102
00103 static void macro_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00104 {
00105 int i;
00106 char varname[10];
00107 pbx_builtin_setvar_helper(new_chan, "MACRO_DEPTH", "0");
00108 pbx_builtin_setvar_helper(new_chan, "MACRO_CONTEXT", NULL);
00109 pbx_builtin_setvar_helper(new_chan, "MACRO_EXTEN", NULL);
00110 pbx_builtin_setvar_helper(new_chan, "MACRO_PRIORITY", NULL);
00111 pbx_builtin_setvar_helper(new_chan, "MACRO_OFFSET", NULL);
00112 for (i = 1; i < 100; i++) {
00113 snprintf(varname, sizeof(varname), "ARG%d", i);
00114 while (pbx_builtin_getvar_helper(new_chan, varname)) {
00115
00116 pbx_builtin_setvar_helper(new_chan, varname, NULL);
00117 }
00118 }
00119 }
00120
00121 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
00122 {
00123 struct ast_exten *e;
00124 struct ast_include *i;
00125 struct ast_context *c2;
00126
00127 for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
00128 if (ast_extension_match(ast_get_extension_name(e), exten)) {
00129 int needmatch = ast_get_extension_matchcid(e);
00130 if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
00131 (!needmatch)) {
00132
00133 struct ast_exten *p;
00134 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
00135 if (priority != ast_get_extension_priority(p))
00136 continue;
00137 return p;
00138 }
00139 }
00140 }
00141 }
00142
00143
00144 for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
00145 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
00146 if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
00147 e = find_matching_priority(c2, exten, priority, callerid);
00148 if (e)
00149 return e;
00150 }
00151 }
00152 }
00153 return NULL;
00154 }
00155
00156 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
00157 {
00158 const char *s;
00159 char *tmp;
00160 char *cur, *rest;
00161 char *macro;
00162 char fullmacro[80];
00163 char varname[80];
00164 char runningapp[80], runningdata[1024];
00165 char *oldargs[MAX_ARGS + 1] = { NULL, };
00166 int argc, x;
00167 int res=0;
00168 char oldexten[256]="";
00169 int oldpriority, gosub_level = 0;
00170 char pc[80], depthc[12];
00171 char oldcontext[AST_MAX_CONTEXT] = "";
00172 const char *inhangupc;
00173 int offset, depth = 0, maxdepth = 7;
00174 int setmacrocontext=0;
00175 int autoloopflag, inhangup = 0;
00176
00177 char *save_macro_exten;
00178 char *save_macro_context;
00179 char *save_macro_priority;
00180 char *save_macro_offset;
00181 struct ast_datastore *macro_store = ast_channel_datastore_find(chan, ¯o_ds_info, NULL);
00182
00183 if (ast_strlen_zero(data)) {
00184 ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n");
00185 return -1;
00186 }
00187
00188 do {
00189 if (macro_store) {
00190 break;
00191 }
00192 if (!(macro_store = ast_datastore_alloc(¯o_ds_info, NULL))) {
00193 ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
00194 break;
00195 }
00196
00197 macro_store->inheritance = DATASTORE_INHERIT_FOREVER;
00198 ast_channel_datastore_add(chan, macro_store);
00199 } while (0);
00200
00201
00202 ast_channel_lock(chan);
00203 if ((s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"))) {
00204 sscanf(s, "%d", &maxdepth);
00205 }
00206
00207
00208 if ((s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"))) {
00209 sscanf(s, "%d", &depth);
00210 }
00211
00212
00213 if (strcmp(chan->exten, "h") == 0)
00214 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
00215
00216 if ((inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"))) {
00217 sscanf(inhangupc, "%d", &inhangup);
00218 }
00219 ast_channel_unlock(chan);
00220
00221 if (depth >= maxdepth) {
00222 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
00223 return 0;
00224 }
00225 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
00226 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00227
00228 tmp = ast_strdupa(data);
00229 rest = tmp;
00230 macro = strsep(&rest, ",");
00231 if (ast_strlen_zero(macro)) {
00232 ast_log(LOG_WARNING, "Invalid macro name specified\n");
00233 return 0;
00234 }
00235
00236 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
00237 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
00238 if (!ast_context_find(fullmacro))
00239 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
00240 else
00241 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
00242 return 0;
00243 }
00244
00245
00246 if (exclusive) {
00247 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
00248 ast_autoservice_start(chan);
00249 if (ast_context_lockmacro(fullmacro)) {
00250 ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
00251 ast_autoservice_stop(chan);
00252 return 0;
00253 }
00254 ast_autoservice_stop(chan);
00255 }
00256
00257
00258 oldpriority = chan->priority;
00259 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
00260 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
00261 if (ast_strlen_zero(chan->macrocontext)) {
00262 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
00263 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
00264 chan->macropriority = chan->priority;
00265 setmacrocontext=1;
00266 }
00267 argc = 1;
00268
00269 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
00270 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
00271
00272 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
00273 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
00274
00275 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
00276 snprintf(pc, sizeof(pc), "%d", oldpriority);
00277 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
00278
00279 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
00280 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
00281
00282
00283 chan->exten[0] = 's';
00284 chan->exten[1] = '\0';
00285 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
00286 chan->priority = 1;
00287
00288 ast_channel_lock(chan);
00289 while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
00290 const char *argp;
00291
00292
00293 snprintf(varname, sizeof(varname), "ARG%d", argc);
00294 if ((argp = pbx_builtin_getvar_helper(chan, varname))) {
00295 oldargs[argc] = ast_strdup(argp);
00296 }
00297 pbx_builtin_setvar_helper(chan, varname, cur);
00298 argc++;
00299 }
00300 ast_channel_unlock(chan);
00301 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
00302 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
00303 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
00304 struct ast_context *c;
00305 struct ast_exten *e;
00306 int foundx;
00307 runningapp[0] = '\0';
00308 runningdata[0] = '\0';
00309
00310
00311 if (ast_rdlock_contexts()) {
00312 ast_log(LOG_WARNING, "Failed to lock contexts list\n");
00313 } else {
00314 for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
00315 if (!strcmp(ast_get_context_name(c), chan->context)) {
00316 if (ast_rdlock_context(c)) {
00317 ast_log(LOG_WARNING, "Unable to lock context?\n");
00318 } else {
00319 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
00320 if (e) {
00321 ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
00322 ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
00323 }
00324 ast_unlock_context(c);
00325 }
00326 break;
00327 }
00328 }
00329 }
00330 ast_unlock_contexts();
00331
00332
00333 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00334
00335 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
00336
00337 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00338 (res == '*') || (res == '#')) {
00339
00340 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
00341 break;
00342 }
00343 switch(res) {
00344 case MACRO_EXIT_RESULT:
00345 res = 0;
00346 goto out;
00347 default:
00348 ast_debug(2, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00349 ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00350 goto out;
00351 }
00352 }
00353
00354 ast_debug(1, "Executed application: %s\n", runningapp);
00355
00356 if (!strcasecmp(runningapp, "GOSUB")) {
00357 gosub_level++;
00358 ast_debug(1, "Incrementing gosub_level\n");
00359 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
00360 char tmp2[1024], *cond, *app_arg, *app2 = tmp2;
00361 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00362 cond = strsep(&app2, "?");
00363 app_arg = strsep(&app2, ":");
00364 if (pbx_checkcondition(cond)) {
00365 if (!ast_strlen_zero(app_arg)) {
00366 gosub_level++;
00367 ast_debug(1, "Incrementing gosub_level\n");
00368 }
00369 } else {
00370 if (!ast_strlen_zero(app2)) {
00371 gosub_level++;
00372 ast_debug(1, "Incrementing gosub_level\n");
00373 }
00374 }
00375 } else if (!strcasecmp(runningapp, "RETURN")) {
00376 gosub_level--;
00377 ast_debug(1, "Decrementing gosub_level\n");
00378 } else if (!strcasecmp(runningapp, "STACKPOP")) {
00379 gosub_level--;
00380 ast_debug(1, "Decrementing gosub_level\n");
00381 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
00382
00383 char tmp2[1024], *tmp3 = NULL;
00384 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00385 if (!strcasecmp(runningapp, "EXECIF")) {
00386 tmp3 = strchr(tmp2, '|');
00387 if (tmp3)
00388 *tmp3++ = '\0';
00389 if (!pbx_checkcondition(tmp2))
00390 tmp3 = NULL;
00391 } else
00392 tmp3 = tmp2;
00393
00394 if (tmp3)
00395 ast_debug(1, "Last app: %s\n", tmp3);
00396
00397 if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
00398 gosub_level++;
00399 ast_debug(1, "Incrementing gosub_level\n");
00400 } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
00401 gosub_level--;
00402 ast_debug(1, "Decrementing gosub_level\n");
00403 } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
00404 gosub_level--;
00405 ast_debug(1, "Decrementing gosub_level\n");
00406 }
00407 }
00408
00409 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
00410 ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00411 break;
00412 }
00413
00414
00415 if (ast_check_hangup(chan) && !inhangup) {
00416 ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
00417 goto out;
00418 }
00419 chan->priority++;
00420 }
00421 out:
00422
00423
00424 ast_channel_lock(chan);
00425
00426
00427 snprintf(depthc, sizeof(depthc), "%d", depth);
00428 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00429 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00430
00431 for (x = 1; x < argc; x++) {
00432
00433 snprintf(varname, sizeof(varname), "ARG%d", x);
00434 if (oldargs[x]) {
00435 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00436 ast_free(oldargs[x]);
00437 } else {
00438 pbx_builtin_setvar_helper(chan, varname, NULL);
00439 }
00440 }
00441
00442
00443 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00444 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00445 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00446 if (save_macro_exten)
00447 ast_free(save_macro_exten);
00448 if (save_macro_context)
00449 ast_free(save_macro_context);
00450 if (save_macro_priority)
00451 ast_free(save_macro_priority);
00452
00453 if (setmacrocontext) {
00454 chan->macrocontext[0] = '\0';
00455 chan->macroexten[0] = '\0';
00456 chan->macropriority = 0;
00457 }
00458
00459 if (!strcasecmp(chan->context, fullmacro)) {
00460
00461 chan->priority = oldpriority;
00462 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00463 if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
00464
00465 const char *offsets;
00466 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00467 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00468
00469
00470 if (sscanf(offsets, "%d", &offset) == 1) {
00471 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00472 chan->priority += offset;
00473 }
00474 }
00475 }
00476 }
00477 }
00478
00479 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00480 if (save_macro_offset)
00481 ast_free(save_macro_offset);
00482
00483
00484 if (exclusive) {
00485 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
00486 if (ast_context_unlockmacro(fullmacro)) {
00487 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
00488 res = 0;
00489 }
00490 }
00491 ast_channel_unlock(chan);
00492
00493 return res;
00494 }
00495
00496 static int macro_exec(struct ast_channel *chan, void *data)
00497 {
00498 return _macro_exec(chan, data, 0);
00499 }
00500
00501 static int macroexclusive_exec(struct ast_channel *chan, void *data)
00502 {
00503 return _macro_exec(chan, data, 1);
00504 }
00505
00506 static int macroif_exec(struct ast_channel *chan, void *data)
00507 {
00508 char *expr = NULL, *label_a = NULL, *label_b = NULL;
00509 int res = 0;
00510
00511 if (!(expr = ast_strdupa(data)))
00512 return -1;
00513
00514 if ((label_a = strchr(expr, '?'))) {
00515 *label_a = '\0';
00516 label_a++;
00517 if ((label_b = strchr(label_a, ':'))) {
00518 *label_b = '\0';
00519 label_b++;
00520 }
00521 if (pbx_checkcondition(expr))
00522 res = macro_exec(chan, label_a);
00523 else if (label_b)
00524 res = macro_exec(chan, label_b);
00525 } else
00526 ast_log(LOG_WARNING, "Invalid Syntax.\n");
00527
00528 return res;
00529 }
00530
00531 static int macro_exit_exec(struct ast_channel *chan, void *data)
00532 {
00533 return MACRO_EXIT_RESULT;
00534 }
00535
00536 static int unload_module(void)
00537 {
00538 int res;
00539
00540 res = ast_unregister_application(if_app);
00541 res |= ast_unregister_application(exit_app);
00542 res |= ast_unregister_application(app);
00543 res |= ast_unregister_application(exclusive_app);
00544
00545 return res;
00546 }
00547
00548 static int load_module(void)
00549 {
00550 int res;
00551
00552 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00553 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00554 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
00555 res |= ast_register_application(app, macro_exec, synopsis, descrip);
00556
00557 return res;
00558 }
00559
00560 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");