Added a warning to the documentation for Macro in response to bug 7776
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Dial plan macro Implementation
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/options.h"
44 #include "asterisk/config.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/lock.h"
47
48 #define MAX_ARGS 80
49
50 /* special result value used to force macro exit */
51 #define MACRO_EXIT_RESULT 1024
52
53 static char *descrip =
54 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
55 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
56 "executing each step, then returning when the steps end. \n"
57 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
58 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
59 "${ARG1}, ${ARG2}, etc in the macro context.\n"
60 "If you Goto out of the Macro context, the Macro will terminate and control\n"
61 "will be returned at the location of the Goto.\n"
62 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
63 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
64 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
65 "         contained within it via sub-engine), and a fixed per-thread\n"
66 "         memory stack allowance, macros are limited to 7 levels\n"
67 "         of nesting (macro calling macro calling macro, etc.); It\n"
68 "         may be possible that stack-intensive applications in deeply nested macros\n"
69 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
70 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
71 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
72
73 static char *if_descrip =
74 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
75 "Executes macro defined in <macroname_a> if <expr> is true\n"
76 "(otherwise <macroname_b> if provided)\n"
77 "Arguments and return values as in application macro()\n";
78
79 static char *exclusive_descrip =
80 "  MacroExclusive(macroname|arg1|arg2...):\n"
81 "Executes macro defined in the context 'macro-macroname'\n"
82 "Only one call at a time may run the macro.\n"
83 "(we'll wait if another call is busy executing in the Macro)\n"
84 "Arguments and return values as in application Macro()\n";
85
86 static char *exit_descrip =
87 "  MacroExit():\n"
88 "Causes the currently running macro to exit as if it had\n"
89 "ended normally by running out of priorities to execute.\n"
90 "If used outside a macro, will likely cause unexpected\n"
91 "behavior.\n";
92
93 static char *app = "Macro";
94 static char *if_app = "MacroIf";
95 static char *exclusive_app = "MacroExclusive";
96 static char *exit_app = "MacroExit";
97
98 static char *synopsis = "Macro Implementation";
99 static char *if_synopsis = "Conditional Macro Implementation";
100 static char *exclusive_synopsis = "Exclusive Macro Implementation";
101 static char *exit_synopsis = "Exit From Macro";
102
103
104 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
105 {
106         const char *s;
107
108         char *tmp;
109         char *cur, *rest;
110         char *macro;
111         char fullmacro[80];
112         char varname[80];
113         char *oldargs[MAX_ARGS + 1] = { NULL, };
114         int argc, x;
115         int res=0;
116         char oldexten[256]="";
117         int oldpriority;
118         char pc[80], depthc[12];
119         char oldcontext[AST_MAX_CONTEXT] = "";
120         int offset, depth = 0, maxdepth = 7;
121         int setmacrocontext=0;
122         int autoloopflag, dead = 0;
123   
124         char *save_macro_exten;
125         char *save_macro_context;
126         char *save_macro_priority;
127         char *save_macro_offset;
128         struct ast_module_user *u;
129  
130         if (ast_strlen_zero(data)) {
131                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
132                 return -1;
133         }
134
135         u = ast_module_user_add(chan);
136
137         /* does the user want a deeper rabbit hole? */
138         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
139         if (s)
140                 sscanf(s, "%d", &maxdepth);
141
142         /* Count how many levels deep the rabbit hole goes */
143         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
144         if (s)
145                 sscanf(s, "%d", &depth);
146         if (depth >= maxdepth) {
147                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
148                 ast_module_user_remove(u);
149                 return 0;
150         }
151         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
152         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
153
154         tmp = ast_strdupa(data);
155         rest = tmp;
156         macro = strsep(&rest, "|");
157         if (ast_strlen_zero(macro)) {
158                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
159                 ast_module_user_remove(u);
160                 return 0;
161         }
162
163         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
164         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
165                 if (!ast_context_find(fullmacro)) 
166                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
167                 else
168                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
169                 ast_module_user_remove(u);
170                 return 0;
171         }
172
173         /* If we are to run the macro exclusively, take the mutex */
174         if (exclusive) {
175                 if (option_debug)
176                         ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
177                 ast_autoservice_start(chan);
178                 if (ast_context_lockmacro(fullmacro)) {
179                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
180                         ast_autoservice_stop(chan);
181                         ast_module_user_remove(u);
182
183                         return 0;
184                 }
185                 ast_autoservice_stop(chan);
186         }
187         
188         /* Save old info */
189         oldpriority = chan->priority;
190         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
191         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
192         if (ast_strlen_zero(chan->macrocontext)) {
193                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
194                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
195                 chan->macropriority = chan->priority;
196                 setmacrocontext=1;
197         }
198         argc = 1;
199         /* Save old macro variables */
200         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
201         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
202
203         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
204         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
205
206         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
207         snprintf(pc, sizeof(pc), "%d", oldpriority);
208         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
209   
210         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
211         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
212
213         /* Setup environment for new run */
214         chan->exten[0] = 's';
215         chan->exten[1] = '\0';
216         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
217         chan->priority = 1;
218
219         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
220                 const char *s;
221                 /* Save copy of old arguments if we're overwriting some, otherwise
222                 let them pass through to the other macro */
223                 snprintf(varname, sizeof(varname), "ARG%d", argc);
224                 s = pbx_builtin_getvar_helper(chan, varname);
225                 if (s)
226                         oldargs[argc] = ast_strdup(s);
227                 pbx_builtin_setvar_helper(chan, varname, cur);
228                 argc++;
229         }
230         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
231         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
232         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
233                 /* Reset the macro depth, if it was changed in the last iteration */
234                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
235                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
236                         /* Something bad happened, or a hangup has been requested. */
237                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
238                         (res == '*') || (res == '#')) {
239                                 /* Just return result as to the previous application as if it had been dialed */
240                                 if (option_debug)
241                                         ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
242                                 break;
243                         }
244                         switch(res) {
245                         case MACRO_EXIT_RESULT:
246                                 res = 0;
247                                 goto out;
248                         case AST_PBX_KEEPALIVE:
249                                 if (option_debug)
250                                         ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
251                                 else if (option_verbose > 1)
252                                         ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
253                                 goto out;
254                                 break;
255                         default:
256                                 if (option_debug)
257                                         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);
258                                 else if (option_verbose > 1)
259                                         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);
260                                 dead = 1;
261                                 goto out;
262                         }
263                 }
264                 if (strcasecmp(chan->context, fullmacro)) {
265                         if (option_verbose > 1)
266                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
267                         break;
268                 }
269                 /* don't stop executing extensions when we're in "h" */
270                 if (chan->_softhangup && strcasecmp(oldexten,"h") && strcasecmp(chan->macroexten,"h")) {
271                         if (option_debug)
272                                 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
273                                         chan->exten, chan->macroexten, chan->priority);
274                         goto out;
275                 }
276                 chan->priority++;
277         }
278         out:
279         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
280         snprintf(depthc, sizeof(depthc), "%d", depth);
281         if (!dead) {
282                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
283                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
284         }
285
286         for (x = 1; x < argc; x++) {
287                 /* Restore old arguments and delete ours */
288                 snprintf(varname, sizeof(varname), "ARG%d", x);
289                 if (oldargs[x]) {
290                         if (!dead)
291                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
292                         free(oldargs[x]);
293                 } else if (!dead) {
294                         pbx_builtin_setvar_helper(chan, varname, NULL);
295                 }
296         }
297
298         /* Restore macro variables */
299         if (!dead) {
300                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
301                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
302                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
303         }
304         if (save_macro_exten)
305                 free(save_macro_exten);
306         if (save_macro_context)
307                 free(save_macro_context);
308         if (save_macro_priority)
309                 free(save_macro_priority);
310
311         if (!dead && setmacrocontext) {
312                 chan->macrocontext[0] = '\0';
313                 chan->macroexten[0] = '\0';
314                 chan->macropriority = 0;
315         }
316
317         if (!dead && !strcasecmp(chan->context, fullmacro)) {
318                 /* If we're leaving the macro normally, restore original information */
319                 chan->priority = oldpriority;
320                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
321                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
322                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
323                         const char *offsets;
324                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
325                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
326                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
327                                 normally if there is any problem */
328                                 if (sscanf(offsets, "%d", &offset) == 1) {
329                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
330                                                 chan->priority += offset;
331                                         }
332                                 }
333                         }
334                 }
335         }
336
337         if (!dead)
338                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
339         if (save_macro_offset)
340                 free(save_macro_offset);
341
342         /* Unlock the macro */
343         if (exclusive) {
344                 if (option_debug)
345                         ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
346                 if (ast_context_unlockmacro(fullmacro)) {
347                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
348                         res = 0;
349                 }
350         }
351         
352         ast_module_user_remove(u);
353
354         return res;
355 }
356
357 static int macro_exec(struct ast_channel *chan, void *data)
358 {
359         return _macro_exec(chan, data, 0);
360 }
361
362 static int macroexclusive_exec(struct ast_channel *chan, void *data)
363 {
364         return _macro_exec(chan, data, 1);
365 }
366
367 static int macroif_exec(struct ast_channel *chan, void *data) 
368 {
369         char *expr = NULL, *label_a = NULL, *label_b = NULL;
370         int res = 0;
371         struct ast_module_user *u;
372
373         u = ast_module_user_add(chan);
374
375         if (!(expr = ast_strdupa(data))) {
376                 ast_module_user_remove(u);
377                 return -1;
378         }
379
380         if ((label_a = strchr(expr, '?'))) {
381                 *label_a = '\0';
382                 label_a++;
383                 if ((label_b = strchr(label_a, ':'))) {
384                         *label_b = '\0';
385                         label_b++;
386                 }
387                 if (pbx_checkcondition(expr))
388                         macro_exec(chan, label_a);
389                 else if (label_b) 
390                         macro_exec(chan, label_b);
391         } else
392                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
393
394         ast_module_user_remove(u);
395
396         return res;
397 }
398                         
399 static int macro_exit_exec(struct ast_channel *chan, void *data)
400 {
401         return MACRO_EXIT_RESULT;
402 }
403
404 static int unload_module(void)
405 {
406         int res;
407
408         res = ast_unregister_application(if_app);
409         res |= ast_unregister_application(exit_app);
410         res |= ast_unregister_application(app);
411         res |= ast_unregister_application(exclusive_app);
412
413         ast_module_user_hangup_all();
414
415         return res;
416 }
417
418 static int load_module(void)
419 {
420         int res;
421
422         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
423         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
424         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
425         res |= ast_register_application(app, macro_exec, synopsis, descrip);
426
427         return res;
428 }
429
430 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");