Index: apps/app_macro.c =================================================================== --- apps/app_macro.c (revision 139829) +++ apps/app_macro.c (working copy) @@ -103,7 +103,53 @@ static char *exclusive_synopsis = "Exclusive Macro Implementation"; static char *exit_synopsis = "Exit From Macro"; +static void macro_free(void *data); +static struct ast_datastore_info stack_info = { + .type = "MACRO", + .destroy = macro_free, +}; + +struct macro_stack_frame { + AST_LIST_ENTRY(macro_stack_frame) entries; + int recursion_level; + int priority; + char runningapp[80]; + char runningdata[1024]; + char context[80]; + char extension[80]; +}; + +static void macro_release_frame(struct ast_channel *chan, struct macro_stack_frame *frame) +{ + ast_free(frame); +} + +static struct macro_stack_frame *macro_allocate_frame(const char *context, const char *extension, int priority) +{ + struct macro_stack_frame *new = NULL; + + if ((new = ast_calloc(1, sizeof(*new)))) { + strcpy(new->extension, extension); + strcpy(new->context, context); + new->priority = priority; + } + return new; +} + +static void macro_free(void *data) +{ + AST_LIST_HEAD(, macro_stack_frame) *oldlist = data; + struct macro_stack_frame *oldframe; + AST_LIST_LOCK(oldlist); + while ((oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries))) { + macro_release_frame(NULL, oldframe); + } + AST_LIST_UNLOCK(oldlist); + AST_LIST_HEAD_DESTROY(oldlist); + ast_free(oldlist); +} + static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid) { struct ast_exten *e; @@ -147,25 +193,24 @@ char *macro; char fullmacro[80]; char varname[80]; - char runningapp[80], runningdata[1024]; - char *oldargs[MAX_ARGS + 1] = { NULL, }; int argc, x; int res=0; - char oldexten[256]=""; - int oldpriority, gosub_level = 0; - char pc[80], depthc[12]; - char oldcontext[AST_MAX_CONTEXT] = ""; + int gosub_level = 0; + char pc[12]; const char *inhangupc; int offset, depth = 0, maxdepth = 7; int setmacrocontext=0; int autoloopflag, dead = 0, inhangup = 0; - + struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL); + AST_LIST_HEAD(, macro_stack_frame) *oldlist; + struct macro_stack_frame *prevframe, *newframe; + char *save_macro_exten; char *save_macro_context; char *save_macro_priority; char *save_macro_offset; struct ast_module_user *u; - + if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n"); return -1; @@ -173,15 +218,46 @@ u = ast_module_user_add(chan); + if (!stack_store) { + if (option_debug) { + ast_log(LOG_DEBUG, "Channel %s has no datastore, so we're allocating one.\n", chan->name); + } + stack_store = ast_channel_datastore_alloc(&stack_info, NULL); + if (!stack_store) { + ast_log(LOG_ERROR, "Unable to allocate new datastore. Macro will fail.\n"); + ast_module_user_remove(u); + return -1; + } + + oldlist = ast_calloc(1, sizeof(*oldlist)); + if (!oldlist) { + ast_log(LOG_ERROR, "Unable to allocate datastore list head. Macro will fail.\n"); + ast_channel_datastore_free(stack_store); + ast_module_user_remove(u); + return -1; + } + + stack_store->data = oldlist; + AST_LIST_HEAD_INIT(oldlist); + ast_channel_datastore_add(chan, stack_store); + } + + if (!(newframe = macro_allocate_frame(chan->context, chan->exten, chan->priority))) { + ast_module_user_remove(u); + return -1; + } + + oldlist = stack_store->data; + prevframe = AST_LIST_FIRST(oldlist); + /* does the user want a deeper rabbit hole? */ s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"); if (s) sscanf(s, "%d", &maxdepth); /* Count how many levels deep the rabbit hole goes */ - s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"); - if (s) - sscanf(s, "%d", &depth); + depth = prevframe ? prevframe->recursion_level : 0; + /* Used for detecting whether to return when a Macro is called from another Macro after hangup */ if (strcmp(chan->exten, "h") == 0) pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1"); @@ -191,17 +267,18 @@ if (depth >= maxdepth) { ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n"); + ast_free(newframe); ast_module_user_remove(u); return 0; } - snprintf(depthc, sizeof(depthc), "%d", depth + 1); - pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); + newframe->recursion_level = depth + 1; tmp = ast_strdupa(data); rest = tmp; macro = strsep(&rest, "|"); if (ast_strlen_zero(macro)) { ast_log(LOG_WARNING, "Invalid macro name specified\n"); + ast_free(newframe); ast_module_user_remove(u); return 0; } @@ -212,6 +289,7 @@ ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro); else ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro); + ast_free(newframe); ast_module_user_remove(u); return 0; } @@ -223,6 +301,7 @@ if (ast_context_lockmacro(fullmacro)) { ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro); ast_autoservice_stop(chan); + ast_free(newframe); ast_module_user_remove(u); return 0; @@ -231,9 +310,6 @@ } /* Save old info */ - oldpriority = chan->priority; - ast_copy_string(oldexten, chan->exten, sizeof(oldexten)); - ast_copy_string(oldcontext, chan->context, sizeof(oldcontext)); if (ast_strlen_zero(chan->macrocontext)) { ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext)); ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten)); @@ -243,13 +319,13 @@ argc = 1; /* Save old macro variables */ save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN")); - pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten); + pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", newframe->extension); save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT")); - pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext); + pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", newframe->context); save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY")); - snprintf(pc, sizeof(pc), "%d", oldpriority); + snprintf(pc, sizeof(pc), "%d", newframe->priority); pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc); save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET")); @@ -262,23 +338,33 @@ chan->priority = 1; while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) { - const char *s; - /* Save copy of old arguments if we're overwriting some, otherwise - let them pass through to the other macro */ snprintf(varname, sizeof(varname), "ARG%d", argc); - s = pbx_builtin_getvar_helper(chan, varname); - if (s) - oldargs[argc] = ast_strdup(s); - pbx_builtin_setvar_helper(chan, varname, cur); + pbx_builtin_pushvar_helper(chan, varname, cur); argc++; } + + if (depth == 0) { + /* Set an additional blank variable at the root level. This will assist + * with transfers, where the existing variable list may be inherited. + * We cannot set at any other level, due to backwards compatibility. */ + snprintf(varname, sizeof(varname), "ARG%d", argc); + pbx_builtin_pushvar_helper(chan, varname, ""); + argc++; + } + autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP); ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP); + + /* Save return address data */ + AST_LIST_LOCK(oldlist); + AST_LIST_INSERT_HEAD(oldlist, newframe, entries); + AST_LIST_UNLOCK(oldlist); + while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) { struct ast_context *c; struct ast_exten *e; - runningapp[0] = '\0'; - runningdata[0] = '\0'; + newframe->runningapp[0] = '\0'; + newframe->runningdata[0] = '\0'; /* What application will execute? */ if (ast_rdlock_contexts()) { @@ -291,8 +377,8 @@ } else { e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num); if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */ - ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp)); - ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata)); + ast_copy_string(newframe->runningapp, ast_get_extension_app(e), sizeof(newframe->runningapp)); + ast_copy_string(newframe->runningdata, ast_get_extension_app_data(e), sizeof(newframe->runningdata)); } ast_unlock_context(c); } @@ -302,9 +388,6 @@ } ast_unlock_contexts(); - /* Reset the macro depth, if it was changed in the last iteration */ - pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); - if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) { /* Something bad happened, or a hangup has been requested. */ if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) || @@ -334,14 +417,14 @@ } } - ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp); + ast_log(LOG_DEBUG, "Executed application: %s\n", newframe->runningapp); - if (!strcasecmp(runningapp, "GOSUB")) { + if (!strcasecmp(newframe->runningapp, "GOSUB")) { gosub_level++; ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); - } else if (!strcasecmp(runningapp, "GOSUBIF")) { + } else if (!strcasecmp(newframe->runningapp, "GOSUBIF")) { char tmp2[1024] = "", *cond, *app, *app2 = tmp2; - pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1); + pbx_substitute_variables_helper(chan, newframe->runningdata, tmp2, sizeof(tmp2) - 1); cond = strsep(&app2, "?"); app = strsep(&app2, ":"); if (pbx_checkcondition(cond)) { @@ -355,17 +438,17 @@ ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); } } - } else if (!strcasecmp(runningapp, "RETURN")) { + } else if (!strcasecmp(newframe->runningapp, "RETURN")) { gosub_level--; ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); - } else if (!strcasecmp(runningapp, "STACKPOP")) { + } else if (!strcasecmp(newframe->runningapp, "STACKPOP")) { gosub_level--; ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); - } else if (!strncasecmp(runningapp, "EXEC", 4)) { + } else if (!strncasecmp(newframe->runningapp, "EXEC", 4)) { /* Must evaluate args to find actual app */ char tmp2[1024] = "", *tmp3 = NULL; - pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1); - if (!strcasecmp(runningapp, "EXECIF")) { + pbx_substitute_variables_helper(chan, newframe->runningdata, tmp2, sizeof(tmp2) - 1); + if (!strcasecmp(newframe->runningapp, "EXECIF")) { tmp3 = strchr(tmp2, '|'); if (tmp3) *tmp3++ = '\0'; @@ -404,21 +487,14 @@ chan->priority++; } out: - /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */ - snprintf(depthc, sizeof(depthc), "%d", depth); if (!dead) { - pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP); } for (x = 1; x < argc; x++) { /* Restore old arguments and delete ours */ snprintf(varname, sizeof(varname), "ARG%d", x); - if (oldargs[x]) { - if (!dead) - pbx_builtin_setvar_helper(chan, varname, oldargs[x]); - free(oldargs[x]); - } else if (!dead) { + if (!dead) { pbx_builtin_setvar_helper(chan, varname, NULL); } } @@ -444,12 +520,12 @@ if (!dead && !strcasecmp(chan->context, fullmacro)) { /* If we're leaving the macro normally, restore original information */ - chan->priority = oldpriority; - ast_copy_string(chan->context, oldcontext, sizeof(chan->context)); + chan->priority = newframe->priority; + ast_copy_string(chan->context, newframe->context, sizeof(chan->context)); if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) { /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */ const char *offsets; - ast_copy_string(chan->exten, oldexten, sizeof(chan->exten)); + ast_copy_string(chan->exten, newframe->extension, sizeof(chan->exten)); if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) { /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue normally if there is any problem */