Added a warning to the documentation for Macro in response to bug 7776
[asterisk/asterisk.git] / apps / app_macro.c
index 2410d47..c35208f 100644 (file)
@@ -50,8 +50,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 /* special result value used to force macro exit */
 #define MACRO_EXIT_RESULT 1024
 
 /* 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"
 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"
@@ -62,7 +60,15 @@ static char *descrip =
 "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"
 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\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"
 "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"
 
 static char *if_descrip =
 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
@@ -70,6 +76,13 @@ static char *if_descrip =
 "(otherwise <macroname_b> if provided)\n"
 "Arguments and return values as in application macro()\n";
 
 "(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"
 static char *exit_descrip =
 "  MacroExit():\n"
 "Causes the currently running macro to exit as if it had\n"
@@ -79,15 +92,16 @@ static char *exit_descrip =
 
 static char *app = "Macro";
 static char *if_app = "MacroIf";
 
 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 *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";
 
 static char *exit_synopsis = "Exit From Macro";
 
-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;
 
 {
        const char *s;
 
@@ -103,7 +117,7 @@ static int macro_exec(struct ast_channel *chan, void *data)
        int oldpriority;
        char pc[80], depthc[12];
        char oldcontext[AST_MAX_CONTEXT] = "";
        int oldpriority;
        char pc[80], depthc[12];
        char oldcontext[AST_MAX_CONTEXT] = "";
-       int offset, depth = 0;
+       int offset, depth = 0, maxdepth = 7;
        int setmacrocontext=0;
        int autoloopflag, dead = 0;
   
        int setmacrocontext=0;
        int autoloopflag, dead = 0;
   
@@ -111,22 +125,27 @@ static int macro_exec(struct ast_channel *chan, void *data)
        char *save_macro_context;
        char *save_macro_priority;
        char *save_macro_offset;
        char *save_macro_context;
        char *save_macro_priority;
        char *save_macro_offset;
-       struct localuser *u;
+       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;
        }
 
  
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
                return -1;
        }
 
-       LOCAL_USER_ADD(u);
+       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);
 
        /* 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 >= 7) {
+       if (depth >= maxdepth) {
                ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
                ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
-               LOCAL_USER_REMOVE(u);
+               ast_module_user_remove(u);
                return 0;
        }
        snprintf(depthc, sizeof(depthc), "%d", depth + 1);
                return 0;
        }
        snprintf(depthc, sizeof(depthc), "%d", depth + 1);
@@ -137,18 +156,34 @@ static int macro_exec(struct ast_channel *chan, void *data)
        macro = strsep(&rest, "|");
        if (ast_strlen_zero(macro)) {
                ast_log(LOG_WARNING, "Invalid macro name specified\n");
        macro = strsep(&rest, "|");
        if (ast_strlen_zero(macro)) {
                ast_log(LOG_WARNING, "Invalid macro name specified\n");
-               LOCAL_USER_REMOVE(u);
+               ast_module_user_remove(u);
                return 0;
        }
                return 0;
        }
+
        snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
        if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
        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, "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);
-               LOCAL_USER_REMOVE(u);
+                       ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
+               ast_module_user_remove(u);
                return 0;
        }
                return 0;
        }
+
+       /* 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;
        
        /* Save old info */
        oldpriority = chan->priority;
@@ -202,7 +237,8 @@ static int macro_exec(struct ast_channel *chan, void *data)
                        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 */
                        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) {
                                break;
                        }
                        switch(res) {
@@ -231,9 +267,10 @@ static int macro_exec(struct ast_channel *chan, void *data)
                        break;
                }
                /* don't stop executing extensions when we're in "h" */
                        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++;
                        goto out;
                }
                chan->priority++;
@@ -243,7 +280,6 @@ static int macro_exec(struct ast_channel *chan, void *data)
        snprintf(depthc, sizeof(depthc), "%d", depth);
        if (!dead) {
                pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
        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);
        }
 
                ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
        }
 
@@ -302,20 +338,42 @@ static int macro_exec(struct ast_channel *chan, void *data)
                pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
        if (save_macro_offset)
                free(save_macro_offset);
                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;
 }
 
        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;
 static int macroif_exec(struct ast_channel *chan, void *data) 
 {
        char *expr = NULL, *label_a = NULL, *label_b = NULL;
        int res = 0;
-       struct localuser *u;
+       struct ast_module_user *u;
 
 
-       LOCAL_USER_ADD(u);
+       u = ast_module_user_add(chan);
 
        if (!(expr = ast_strdupa(data))) {
 
        if (!(expr = ast_strdupa(data))) {
-               LOCAL_USER_REMOVE(u);
+               ast_module_user_remove(u);
                return -1;
        }
 
                return -1;
        }
 
@@ -333,7 +391,7 @@ static int macroif_exec(struct ast_channel *chan, void *data)
        } else
                ast_log(LOG_WARNING, "Invalid Syntax.\n");
 
        } else
                ast_log(LOG_WARNING, "Invalid Syntax.\n");
 
-       LOCAL_USER_REMOVE(u);
+       ast_module_user_remove(u);
 
        return res;
 }
 
        return res;
 }
@@ -343,38 +401,30 @@ static int macro_exit_exec(struct ast_channel *chan, void *data)
        return MACRO_EXIT_RESULT;
 }
 
        return MACRO_EXIT_RESULT;
 }
 
-static int unload_module(void *mod)
+static int unload_module(void)
 {
        int res;
 
        res = ast_unregister_application(if_app);
        res |= ast_unregister_application(exit_app);
        res |= ast_unregister_application(app);
 {
        int res;
 
        res = ast_unregister_application(if_app);
        res |= ast_unregister_application(exit_app);
        res |= ast_unregister_application(app);
+       res |= ast_unregister_application(exclusive_app);
 
 
-       STANDARD_HANGUP_LOCALUSERS;
+       ast_module_user_hangup_all();
 
        return res;
 }
 
 
        return res;
 }
 
-static int load_module(void *mod)
+static int load_module(void)
 {
        int 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);
 {
        int 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;
 }
 
        res |= ast_register_application(app, macro_exec, synopsis, descrip);
 
        return res;
 }
 
-static const char *description(void)
-{
-       return tdesc;
-}
-
-static const char *key(void)
-{
-       return ASTERISK_GPL_KEY;
-}
-
-STD_MOD1;
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");