Added a warning to the documentation for Macro in response to bug 7776
[asterisk/asterisk.git] / apps / app_macro.c
old mode 100755 (executable)
new mode 100644 (file)
index b0d175d..c35208f
@@ -1,38 +1,55 @@
 /*
- * Asterisk -- A telephony toolkit for Linux.
+ * Asterisk -- An open source telephony toolkit.
  *
- * Macro Implementation
- * 
- * Copyright (C) 2003 - 2005, Digium, Inc.
+ * Copyright (C) 1999 - 2005, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
  * This program is free software, distributed under the terms of
- * the GNU General Public License
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
  */
 
-#include <sys/types.h>
-#include <asterisk/file.h>
-#include <asterisk/logger.h>
-#include <asterisk/channel.h>
-#include <asterisk/pbx.h>
-#include <asterisk/module.h>
-#include <asterisk/options.h>
-#include <asterisk/config.h>
-#include <asterisk/utils.h>
-#include <asterisk/lock.h>
+/*! \file
+ *
+ * \brief Dial plan macro Implementation
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * 
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
 #include <stdlib.h>
-#include <unistd.h>
+#include <stdio.h>
 #include <string.h>
-#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/config.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
 
 #define MAX_ARGS 80
 
 /* special result value used to force macro exit */
 #define MACRO_EXIT_RESULT 1024
 
-static char *tdesc = "Extension Macros";
-
 static char *descrip =
 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
@@ -42,9 +59,16 @@ static char *descrip =
 "${ARG1}, ${ARG2}, etc in the macro context.\n"
 "If you Goto out of the Macro context, the Macro will terminate and control\n"
 "will be returned at the location of the Goto.\n"
-"Macro returns -1 if any step in the macro returns -1, and 0 otherwise.\n" 
 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
-"at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
+"at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
+"WARNING: Because of the way Macro is implemented (it executes the priorities\n"
+"         contained within it via sub-engine), and a fixed per-thread\n"
+"         memory stack allowance, macros are limited to 7 levels\n"
+"         of nesting (macro calling macro calling macro, etc.); It\n"
+"         may be possible that stack-intensive applications in deeply nested macros\n"
+"         could cause asterisk to crash earlier than this limit. It is advised that\n"
+"         if you need to deeply nest macro calls, that you use the Gosub application\n"
+"         (now allows arguments like a Macro) with explict Return() calls instead.\n";
 
 static char *if_descrip =
 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
@@ -52,6 +76,13 @@ static char *if_descrip =
 "(otherwise <macroname_b> if provided)\n"
 "Arguments and return values as in application macro()\n";
 
+static char *exclusive_descrip =
+"  MacroExclusive(macroname|arg1|arg2...):\n"
+"Executes macro defined in the context 'macro-macroname'\n"
+"Only one call at a time may run the macro.\n"
+"(we'll wait if another call is busy executing in the Macro)\n"
+"Arguments and return values as in application Macro()\n";
+
 static char *exit_descrip =
 "  MacroExit():\n"
 "Causes the currently running macro to exit as if it had\n"
@@ -61,18 +92,19 @@ static char *exit_descrip =
 
 static char *app = "Macro";
 static char *if_app = "MacroIf";
+static char *exclusive_app = "MacroExclusive";
 static char *exit_app = "MacroExit";
 
 static char *synopsis = "Macro Implementation";
 static char *if_synopsis = "Conditional Macro Implementation";
+static char *exclusive_synopsis = "Exclusive Macro Implementation";
 static char *exit_synopsis = "Exit From Macro";
 
-STANDARD_LOCAL_USER;
 
-LOCAL_USER_DECL;
-
-static int macro_exec(struct ast_channel *chan, void *data)
+static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
 {
+       const char *s;
+
        char *tmp;
        char *cur, *rest;
        char *macro;
@@ -83,101 +115,135 @@ static int macro_exec(struct ast_channel *chan, void *data)
        int res=0;
        char oldexten[256]="";
        int oldpriority;
-       char pc[80];
-       char oldcontext[256] = "";
-       char *offsets;
-       int offset;
+       char pc[80], depthc[12];
+       char oldcontext[AST_MAX_CONTEXT] = "";
+       int offset, depth = 0, maxdepth = 7;
        int setmacrocontext=0;
+       int autoloopflag, dead = 0;
   
        char *save_macro_exten;
        char *save_macro_context;
        char *save_macro_priority;
        char *save_macro_offset;
-       struct localuser *u;
-  
-       if (!data || ast_strlen_zero(data)) {
+       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;
+       }
+
+       u = ast_module_user_add(chan);
+
+       /* 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);
+       if (depth >= maxdepth) {
+               ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
+               ast_module_user_remove(u);
                return 0;
        }
+       snprintf(depthc, sizeof(depthc), "%d", depth + 1);
+       pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
 
-       tmp = ast_strdupa((char *) data);
+       tmp = ast_strdupa(data);
        rest = tmp;
        macro = strsep(&rest, "|");
-       if (!macro || ast_strlen_zero(macro)) {
+       if (ast_strlen_zero(macro)) {
                ast_log(LOG_WARNING, "Invalid macro name specified\n");
+               ast_module_user_remove(u);
                return 0;
        }
+
        snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
        if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
-               if (!ast_context_find(fullmacro)) 
+               if (!ast_context_find(fullmacro)) 
                        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_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
+               ast_module_user_remove(u);
                return 0;
        }
 
-       LOCAL_USER_ADD(u);
+       /* If we are to run the macro exclusively, take the mutex */
+       if (exclusive) {
+               if (option_debug)
+                       ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
+               ast_autoservice_start(chan);
+               if (ast_context_lockmacro(fullmacro)) {
+                       ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
+                       ast_autoservice_stop(chan);
+                       ast_module_user_remove(u);
+
+                       return 0;
+               }
+               ast_autoservice_stop(chan);
+       }
+       
        /* Save old info */
        oldpriority = chan->priority;
-       strncpy(oldexten, chan->exten, sizeof(oldexten) - 1);
-       strncpy(oldcontext, chan->context, sizeof(oldcontext) - 1);
+       ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
+       ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
        if (ast_strlen_zero(chan->macrocontext)) {
-               strncpy(chan->macrocontext, chan->context, sizeof(chan->macrocontext) - 1);
-               strncpy(chan->macroexten, chan->exten, sizeof(chan->macroexten) - 1);
+               ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
+               ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
                chan->macropriority = chan->priority;
                setmacrocontext=1;
        }
        argc = 1;
        /* Save old macro variables */
-       save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
-       if (save_macro_exten) 
-               save_macro_exten = strdup(save_macro_exten);
+       save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
        pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
 
-       save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
-       if (save_macro_context)
-               save_macro_context = strdup(save_macro_context);
+       save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
        pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
 
-       save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
-       if (save_macro_priority) 
-               save_macro_priority = strdup(save_macro_priority);
+       save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
        snprintf(pc, sizeof(pc), "%d", oldpriority);
        pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
   
-       save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
-       if (save_macro_offset) 
-               save_macro_offset = strdup(save_macro_offset);
+       save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
        pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
 
        /* Setup environment for new run */
        chan->exten[0] = 's';
        chan->exten[1] = '\0';
-       strncpy(chan->context, fullmacro, sizeof(chan->context) - 1);
+       ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
        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);
-               oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
-               if (oldargs[argc])
-                       oldargs[argc] = strdup(oldargs[argc]);
+               s = pbx_builtin_getvar_helper(chan, varname);
+               if (s)
+                       oldargs[argc] = ast_strdup(s);
                pbx_builtin_setvar_helper(chan, varname, cur);
                argc++;
        }
+       autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
+       ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
        while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+               /* 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')) ||
                        (res == '*') || (res == '#')) {
                                /* Just return result as to the previous application as if it had been dialed */
-                               ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
+                               if (option_debug)
+                                       ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
                                break;
                        }
                        switch(res) {
-                       case MACRO_EXIT_RESULT:
-                               res = 0;
+                       case MACRO_EXIT_RESULT:
+                               res = 0;
                                goto out;
                        case AST_PBX_KEEPALIVE:
                                if (option_debug)
@@ -191,6 +257,7 @@ static int macro_exec(struct ast_channel *chan, void *data)
                                        ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
                                else if (option_verbose > 1)
                                        ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
+                               dead = 1;
                                goto out;
                        }
                }
@@ -200,48 +267,61 @@ static int macro_exec(struct ast_channel *chan, void *data)
                        break;
                }
                /* don't stop executing extensions when we're in "h" */
-               if (chan->_softhangup && strcasecmp(oldexten,"h")) {
-                       ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
-                               chan->exten, chan->priority);
+               if (chan->_softhangup && strcasecmp(oldexten,"h") && strcasecmp(chan->macroexten,"h")) {
+                       if (option_debug)
+                               ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
+                                       chan->exten, chan->macroexten, chan->priority);
                        goto out;
                }
                chan->priority++;
        }
        out:
-       for (x=1; x<argc; x++) {
+       /* 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]) {
-                       pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
+                       if (!dead)
+                               pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
                        free(oldargs[x]);
-               } else {
+               } else if (!dead) {
                        pbx_builtin_setvar_helper(chan, varname, NULL);
                }
        }
 
        /* Restore macro variables */
-       pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
+       if (!dead) {
+               pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
+               pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
+               pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
+       }
        if (save_macro_exten)
                free(save_macro_exten);
-       pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
        if (save_macro_context)
                free(save_macro_context);
-       pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
        if (save_macro_priority)
                free(save_macro_priority);
-       if (setmacrocontext) {
+
+       if (!dead && setmacrocontext) {
                chan->macrocontext[0] = '\0';
                chan->macroexten[0] = '\0';
                chan->macropriority = 0;
        }
 
-       if (!strcasecmp(chan->context, fullmacro)) {
+       if (!dead && !strcasecmp(chan->context, fullmacro)) {
                /* If we're leaving the macro normally, restore original information */
                chan->priority = oldpriority;
-               strncpy(chan->context, oldcontext, sizeof(chan->context) - 1);
+               ast_copy_string(chan->context, oldcontext, 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 */
-                       strncpy(chan->exten, oldexten, sizeof(chan->exten) - 1);
+                       const char *offsets;
+                       ast_copy_string(chan->exten, oldexten, 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 */
@@ -254,35 +334,65 @@ static int macro_exec(struct ast_channel *chan, void *data)
                }
        }
 
-       pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
+       if (!dead)
+               pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
        if (save_macro_offset)
                free(save_macro_offset);
-       LOCAL_USER_REMOVE(u);
+
+       /* Unlock the macro */
+       if (exclusive) {
+               if (option_debug)
+                       ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
+               if (ast_context_unlockmacro(fullmacro)) {
+                       ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
+                       res = 0;
+               }
+       }
+       
+       ast_module_user_remove(u);
+
        return res;
 }
 
+static int macro_exec(struct ast_channel *chan, void *data)
+{
+       return _macro_exec(chan, data, 0);
+}
+
+static int macroexclusive_exec(struct ast_channel *chan, void *data)
+{
+       return _macro_exec(chan, data, 1);
+}
+
 static int macroif_exec(struct ast_channel *chan, void *data) 
 {
        char *expr = NULL, *label_a = NULL, *label_b = NULL;
        int res = 0;
+       struct ast_module_user *u;
+
+       u = ast_module_user_add(chan);
+
+       if (!(expr = ast_strdupa(data))) {
+               ast_module_user_remove(u);
+               return -1;
+       }
+
+       if ((label_a = strchr(expr, '?'))) {
+               *label_a = '\0';
+               label_a++;
+               if ((label_b = strchr(label_a, ':'))) {
+                       *label_b = '\0';
+                       label_b++;
+               }
+               if (pbx_checkcondition(expr))
+                       macro_exec(chan, label_a);
+               else if (label_b) 
+                       macro_exec(chan, label_b);
+       } else
+               ast_log(LOG_WARNING, "Invalid Syntax.\n");
+
+       ast_module_user_remove(u);
 
-       if((expr = ast_strdupa((char *) data))) {
-               if ((label_a = strchr(expr, '?'))) {
-                       *label_a = '\0';
-                       label_a++;
-                       if ((label_b = strchr(label_a, ':'))) {
-                               *label_b = '\0';
-                               label_b++;
-                       }
-                       if (ast_true(expr))
-                               macro_exec(chan, label_a);
-                       else if (label_b) 
-                               macro_exec(chan, label_b);
-                       
-               } else
-                       ast_log(LOG_WARNING, "Invalid Syntax.\n");
-       } else 
-               ast_log(LOG_ERROR, "Out of Memory!\n");
        return res;
 }
                        
@@ -291,34 +401,30 @@ static int macro_exit_exec(struct ast_channel *chan, void *data)
        return MACRO_EXIT_RESULT;
 }
 
-int unload_module(void)
+static int unload_module(void)
 {
-       STANDARD_HANGUP_LOCALUSERS;
-       ast_unregister_application(if_app);
-       ast_unregister_application(exit_app);
-       return ast_unregister_application(app);
-}
+       int res;
 
-int load_module(void)
-{
-       ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
-       ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
-       return ast_register_application(app, macro_exec, synopsis, descrip);
-}
+       res = ast_unregister_application(if_app);
+       res |= ast_unregister_application(exit_app);
+       res |= ast_unregister_application(app);
+       res |= ast_unregister_application(exclusive_app);
 
-char *description(void)
-{
-       return tdesc;
+       ast_module_user_hangup_all();
+
+       return res;
 }
 
-int usecount(void)
+static int load_module(void)
 {
        int res;
-       STANDARD_USECOUNT(res);
+
+       res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
+       res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
+       res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
+       res |= ast_register_application(app, macro_exec, synopsis, descrip);
+
        return res;
 }
 
-char *key()
-{
-       return ASTERISK_GPL_KEY;
-}
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");