This change fixes bug 7731, macros not executing more than one level deep in a hung...
[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 *tdesc = "Extension Macros";
54
55 static char *descrip =
56 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
57 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
58 "executing each step, then returning when the steps end. \n"
59 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
60 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
61 "${ARG1}, ${ARG2}, etc in the macro context.\n"
62 "If you Goto out of the Macro context, the Macro will terminate and control\n"
63 "will be returned at the location of the Goto.\n"
64 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
65 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
66
67 static char *if_descrip =
68 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
69 "Executes macro defined in <macroname_a> if <expr> is true\n"
70 "(otherwise <macroname_b> if provided)\n"
71 "Arguments and return values as in application macro()\n";
72
73 static char *exclusive_descrip =
74 "  MacroExclusive(macroname|arg1|arg2...):\n"
75 "Executes macro defined in the context 'macro-macroname'\n"
76 "Only one call at a time may run the macro.\n"
77 "(we'll wait if another call is busy executing in the Macro)\n"
78 "Arguments and return values as in application Macro()\n";
79
80 static char *exit_descrip =
81 "  MacroExit():\n"
82 "Causes the currently running macro to exit as if it had\n"
83 "ended normally by running out of priorities to execute.\n"
84 "If used outside a macro, will likely cause unexpected\n"
85 "behavior.\n";
86
87 static char *app = "Macro";
88 static char *if_app = "MacroIf";
89 static char *exclusive_app = "MacroExclusive";
90 static char *exit_app = "MacroExit";
91
92 static char *synopsis = "Macro Implementation";
93 static char *if_synopsis = "Conditional Macro Implementation";
94 static char *exclusive_synopsis = "Exclusive Macro Implementation";
95 static char *exit_synopsis = "Exit From Macro";
96
97 LOCAL_USER_DECL;
98
99 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
100 {
101         const char *s;
102
103         char *tmp;
104         char *cur, *rest;
105         char *macro;
106         char fullmacro[80];
107         char varname[80];
108         char *oldargs[MAX_ARGS + 1] = { NULL, };
109         int argc, x;
110         int res=0;
111         char oldexten[256]="";
112         int oldpriority;
113         char pc[80], depthc[12];
114         char oldcontext[AST_MAX_CONTEXT] = "";
115         int offset, depth = 0;
116         int setmacrocontext=0;
117         int autoloopflag, dead = 0;
118   
119         char *save_macro_exten;
120         char *save_macro_context;
121         char *save_macro_priority;
122         char *save_macro_offset;
123         struct localuser *u;
124  
125         if (ast_strlen_zero(data)) {
126                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
127                 return -1;
128         }
129
130         LOCAL_USER_ADD(u);
131
132         /* Count how many levels deep the rabbit hole goes */
133         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
134         if (s)
135                 sscanf(s, "%d", &depth);
136         if (depth >= 20) {
137                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
138                 LOCAL_USER_REMOVE(u);
139                 return 0;
140         }
141         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
142         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
143
144         tmp = ast_strdupa(data);
145         rest = tmp;
146         macro = strsep(&rest, "|");
147         if (ast_strlen_zero(macro)) {
148                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
149                 LOCAL_USER_REMOVE(u);
150                 return 0;
151         }
152
153         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
154         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
155                 if (!ast_context_find(fullmacro)) 
156                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
157                 else
158                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
159                 LOCAL_USER_REMOVE(u);
160                 return 0;
161         }
162
163         /* If we are to run the macro exclusively, take the mutex */
164         if (exclusive) {
165                 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
166                 ast_autoservice_start(chan);
167                 if (ast_context_lockmacro(fullmacro)) {
168                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
169                         ast_autoservice_stop(chan);
170                         LOCAL_USER_REMOVE(u);
171                         return 0;
172                 }
173                 ast_autoservice_stop(chan);
174         }
175         
176         /* Save old info */
177         oldpriority = chan->priority;
178         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
179         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
180         if (ast_strlen_zero(chan->macrocontext)) {
181                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
182                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
183                 chan->macropriority = chan->priority;
184                 setmacrocontext=1;
185         }
186         argc = 1;
187         /* Save old macro variables */
188         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
189         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
190
191         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
192         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
193
194         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
195         snprintf(pc, sizeof(pc), "%d", oldpriority);
196         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
197   
198         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
199         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
200
201         /* Setup environment for new run */
202         chan->exten[0] = 's';
203         chan->exten[1] = '\0';
204         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
205         chan->priority = 1;
206
207         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
208                 const char *s;
209                 /* Save copy of old arguments if we're overwriting some, otherwise
210                 let them pass through to the other macro */
211                 snprintf(varname, sizeof(varname), "ARG%d", argc);
212                 s = pbx_builtin_getvar_helper(chan, varname);
213                 if (s)
214                         oldargs[argc] = ast_strdup(s);
215                 pbx_builtin_setvar_helper(chan, varname, cur);
216                 argc++;
217         }
218         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
219         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
220         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
221                 /* Reset the macro depth, if it was changed in the last iteration */
222                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
223                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
224                         /* Something bad happened, or a hangup has been requested. */
225                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
226                         (res == '*') || (res == '#')) {
227                                 /* Just return result as to the previous application as if it had been dialed */
228                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
229                                 break;
230                         }
231                         switch(res) {
232                         case MACRO_EXIT_RESULT:
233                                 res = 0;
234                                 goto out;
235                         case AST_PBX_KEEPALIVE:
236                                 if (option_debug)
237                                         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);
238                                 else if (option_verbose > 1)
239                                         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);
240                                 goto out;
241                                 break;
242                         default:
243                                 if (option_debug)
244                                         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);
245                                 else if (option_verbose > 1)
246                                         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);
247                                 dead = 1;
248                                 goto out;
249                         }
250                 }
251                 if (strcasecmp(chan->context, fullmacro)) {
252                         if (option_verbose > 1)
253                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
254                         break;
255                 }
256                 /* don't stop executing extensions when we're in "h" */
257                 if (chan->_softhangup && strcasecmp(chan->macroexten,"h")) {
258                         ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
259                                 chan->exten, chan->macroexten, chan->priority);
260                         goto out;
261                 }
262                 chan->priority++;
263         }
264         out:
265         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
266         snprintf(depthc, sizeof(depthc), "%d", depth);
267         if (!dead) {
268                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
269                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
270         }
271
272         for (x = 1; x < argc; x++) {
273                 /* Restore old arguments and delete ours */
274                 snprintf(varname, sizeof(varname), "ARG%d", x);
275                 if (oldargs[x]) {
276                         if (!dead)
277                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
278                         free(oldargs[x]);
279                 } else if (!dead) {
280                         pbx_builtin_setvar_helper(chan, varname, NULL);
281                 }
282         }
283
284         /* Restore macro variables */
285         if (!dead) {
286                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
287                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
288                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
289         }
290         if (save_macro_exten)
291                 free(save_macro_exten);
292         if (save_macro_context)
293                 free(save_macro_context);
294         if (save_macro_priority)
295                 free(save_macro_priority);
296
297         if (!dead && setmacrocontext) {
298                 chan->macrocontext[0] = '\0';
299                 chan->macroexten[0] = '\0';
300                 chan->macropriority = 0;
301         }
302
303         if (!dead && !strcasecmp(chan->context, fullmacro)) {
304                 /* If we're leaving the macro normally, restore original information */
305                 chan->priority = oldpriority;
306                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
307                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
308                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
309                         const char *offsets;
310                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
311                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
312                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
313                                 normally if there is any problem */
314                                 if (sscanf(offsets, "%d", &offset) == 1) {
315                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
316                                                 chan->priority += offset;
317                                         }
318                                 }
319                         }
320                 }
321         }
322
323         if (!dead)
324                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
325         if (save_macro_offset)
326                 free(save_macro_offset);
327
328         /* Unlock the macro */
329         if (exclusive) {
330                 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
331                 if (ast_context_unlockmacro(fullmacro)) {
332                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
333                         res = 0;
334                 }
335         }
336         
337         LOCAL_USER_REMOVE(u);
338         return res;
339 }
340
341 static int macro_exec(struct ast_channel *chan, void *data)
342 {
343         return _macro_exec(chan, data, 0);
344 }
345
346 static int macroexclusive_exec(struct ast_channel *chan, void *data)
347 {
348         return _macro_exec(chan, data, 1);
349 }
350
351 static int macroif_exec(struct ast_channel *chan, void *data) 
352 {
353         char *expr = NULL, *label_a = NULL, *label_b = NULL;
354         int res = 0;
355         struct localuser *u;
356
357         LOCAL_USER_ADD(u);
358
359         if (!(expr = ast_strdupa(data))) {
360                 LOCAL_USER_REMOVE(u);
361                 return -1;
362         }
363
364         if ((label_a = strchr(expr, '?'))) {
365                 *label_a = '\0';
366                 label_a++;
367                 if ((label_b = strchr(label_a, ':'))) {
368                         *label_b = '\0';
369                         label_b++;
370                 }
371                 if (pbx_checkcondition(expr))
372                         macro_exec(chan, label_a);
373                 else if (label_b) 
374                         macro_exec(chan, label_b);
375         } else
376                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
377
378         LOCAL_USER_REMOVE(u);
379
380         return res;
381 }
382                         
383 static int macro_exit_exec(struct ast_channel *chan, void *data)
384 {
385         return MACRO_EXIT_RESULT;
386 }
387
388 static int unload_module(void *mod)
389 {
390         int res;
391
392         res = ast_unregister_application(if_app);
393         res |= ast_unregister_application(exit_app);
394         res |= ast_unregister_application(app);
395         res |= ast_unregister_application(exclusive_app);
396
397         STANDARD_HANGUP_LOCALUSERS;
398
399         return res;
400 }
401
402 static int load_module(void *mod)
403 {
404         int res;
405
406         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
407         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
408         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
409         res |= ast_register_application(app, macro_exec, synopsis, descrip);
410
411         return res;
412 }
413
414 static const char *description(void)
415 {
416         return tdesc;
417 }
418
419 static const char *key(void)
420 {
421         return ASTERISK_GPL_KEY;
422 }
423
424 STD_MOD1;