Lock around variables retrieved, and copy the values, if they stay persistent,
[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 "asterisk/file.h"
33 #include "asterisk/channel.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/module.h"
36 #include "asterisk/config.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/lock.h"
39
40 #define MAX_ARGS 80
41
42 /* special result value used to force macro exit */
43 #define MACRO_EXIT_RESULT 1024
44
45 static char *descrip =
46 "  Macro(macroname,arg1,arg2...): Executes a macro using the context\n"
47 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
48 "executing each step, then returning when the steps end. \n"
49 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
50 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
51 "${ARG1}, ${ARG2}, etc in the macro context.\n"
52 "If you Goto out of the Macro context, the Macro will terminate and control\n"
53 "will be returned at the location of the Goto.\n"
54 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
55 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
56 "Extensions: While a macro is being executed, it becomes the current context.\n"
57 "            This means that if a hangup occurs, for instance, that the macro\n"
58 "            will be searched for an 'h' extension, NOT the context from which\n"
59 "            the macro was called. So, make sure to define all appropriate\n"
60 "            extensions in your macro! (Note: AEL does not use macros)\n"
61 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
62 "         contained within it via sub-engine), and a fixed per-thread\n"
63 "         memory stack allowance, macros are limited to 7 levels\n"
64 "         of nesting (macro calling macro calling macro, etc.); It\n"
65 "         may be possible that stack-intensive applications in deeply nested macros\n"
66 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
67 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
68 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
69
70 static char *if_descrip =
71 "  MacroIf(<expr>?macroname_a[,arg1][:macroname_b[,arg1]])\n"
72 "Executes macro defined in <macroname_a> if <expr> is true\n"
73 "(otherwise <macroname_b> if provided)\n"
74 "Arguments and return values as in application Macro()\n";
75
76 static char *exclusive_descrip =
77 "  MacroExclusive(macroname,arg1,arg2...):\n"
78 "Executes macro defined in the context 'macro-macroname'\n"
79 "Only one call at a time may run the macro.\n"
80 "(we'll wait if another call is busy executing in the Macro)\n"
81 "Arguments and return values as in application Macro()\n";
82
83 static char *exit_descrip =
84 "  MacroExit():\n"
85 "Causes the currently running macro to exit as if it had\n"
86 "ended normally by running out of priorities to execute.\n"
87 "If used outside a macro, will likely cause unexpected\n"
88 "behavior.\n";
89
90 static char *app = "Macro";
91 static char *if_app = "MacroIf";
92 static char *exclusive_app = "MacroExclusive";
93 static char *exit_app = "MacroExit";
94
95 static char *synopsis = "Macro Implementation";
96 static char *if_synopsis = "Conditional Macro Implementation";
97 static char *exclusive_synopsis = "Exclusive Macro Implementation";
98 static char *exit_synopsis = "Exit From Macro";
99
100
101 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
102 {
103         struct ast_exten *e;
104         struct ast_include *i;
105         struct ast_context *c2;
106
107         for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
108                 if (ast_extension_match(ast_get_extension_name(e), exten)) {
109                         int needmatch = ast_get_extension_matchcid(e);
110                         if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
111                                 (!needmatch)) {
112                                 /* This is the matching extension we want */
113                                 struct ast_exten *p;
114                                 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
115                                         if (priority != ast_get_extension_priority(p))
116                                                 continue;
117                                         return p;
118                                 }
119                         }
120                 }
121         }
122
123         /* No match; run through includes */
124         for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
125                 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
126                         if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
127                                 e = find_matching_priority(c2, exten, priority, callerid);
128                                 if (e)
129                                         return e;
130                         }
131                 }
132         }
133         return NULL;
134 }
135
136 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
137 {
138         const char *s;
139         char *tmp;
140         char *cur, *rest;
141         char *macro;
142         char fullmacro[80];
143         char varname[80];
144         char runningapp[80], runningdata[1024];
145         char *oldargs[MAX_ARGS + 1] = { NULL, };
146         int argc, x;
147         int res=0;
148         char oldexten[256]="";
149         int oldpriority, gosub_level = 0;
150         char pc[80], depthc[12];
151         char oldcontext[AST_MAX_CONTEXT] = "";
152         const char *inhangupc;
153         int offset, depth = 0, maxdepth = 7;
154         int setmacrocontext=0;
155         int autoloopflag, dead = 0, inhangup = 0;
156   
157         char *save_macro_exten;
158         char *save_macro_context;
159         char *save_macro_priority;
160         char *save_macro_offset;
161  
162         if (ast_strlen_zero(data)) {
163                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n");
164                 return -1;
165         }
166
167         /* does the user want a deeper rabbit hole? */
168         ast_channel_lock(chan);
169         if ((s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"))) {
170                 sscanf(s, "%d", &maxdepth);
171         }
172         
173         /* Count how many levels deep the rabbit hole goes */
174         if ((s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"))) {
175                 sscanf(s, "%d", &depth);
176         }
177         
178         /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
179         if (strcmp(chan->exten, "h") == 0)
180                 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
181         
182         if ((inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"))) {
183                 sscanf(inhangupc, "%d", &inhangup);
184         }
185         ast_channel_unlock(chan);
186
187         if (depth >= maxdepth) {
188                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
189                 return 0;
190         }
191         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
192         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
193
194         tmp = ast_strdupa(data);
195         rest = tmp;
196         macro = strsep(&rest, ",");
197         if (ast_strlen_zero(macro)) {
198                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
199                 return 0;
200         }
201
202         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
203         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
204                 if (!ast_context_find(fullmacro)) 
205                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
206                 else
207                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
208                 return 0;
209         }
210
211         /* If we are to run the macro exclusively, take the mutex */
212         if (exclusive) {
213                 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
214                 ast_autoservice_start(chan);
215                 if (ast_context_lockmacro(fullmacro)) {
216                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
217                         ast_autoservice_stop(chan);
218                         return 0;
219                 }
220                 ast_autoservice_stop(chan);
221         }
222         
223         /* Save old info */
224         oldpriority = chan->priority;
225         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
226         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
227         if (ast_strlen_zero(chan->macrocontext)) {
228                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
229                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
230                 chan->macropriority = chan->priority;
231                 setmacrocontext=1;
232         }
233         argc = 1;
234         /* Save old macro variables */
235         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
236         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
237
238         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
239         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
240
241         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
242         snprintf(pc, sizeof(pc), "%d", oldpriority);
243         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
244   
245         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
246         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
247
248         /* Setup environment for new run */
249         chan->exten[0] = 's';
250         chan->exten[1] = '\0';
251         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
252         chan->priority = 1;
253
254         ast_channel_lock(chan);
255         while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
256                 const char *s;
257                 /* Save copy of old arguments if we're overwriting some, otherwise
258                 let them pass through to the other macro */
259                 snprintf(varname, sizeof(varname), "ARG%d", argc);
260                 if ((s = pbx_builtin_getvar_helper(chan, varname))) {
261                         oldargs[argc] = ast_strdup(s);
262                 }
263                 pbx_builtin_setvar_helper(chan, varname, cur);
264                 argc++;
265         }
266         ast_channel_unlock(chan);
267         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
268         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
269         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
270                 struct ast_context *c;
271                 struct ast_exten *e;
272                 int foundx;
273                 runningapp[0] = '\0';
274                 runningdata[0] = '\0';
275
276                 /* What application will execute? */
277                 if (ast_rdlock_contexts()) {
278                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
279                 } else {
280                         for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
281                                 if (!strcmp(ast_get_context_name(c), chan->context)) {
282                                         if (ast_rdlock_context(c)) {
283                                                 ast_log(LOG_WARNING, "Unable to lock context?\n");
284                                         } else {
285                                                 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
286                                                 if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
287                                                         ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
288                                                         ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
289                                                 }
290                                                 ast_unlock_context(c);
291                                         }
292                                         break;
293                                 }
294                         }
295                 }
296                 ast_unlock_contexts();
297
298                 /* Reset the macro depth, if it was changed in the last iteration */
299                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
300
301                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
302                         /* Something bad happened, or a hangup has been requested. */
303                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
304                         (res == '*') || (res == '#')) {
305                                 /* Just return result as to the previous application as if it had been dialed */
306                                 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
307                                 break;
308                         }
309                         switch(res) {
310                         case MACRO_EXIT_RESULT:
311                                 res = 0;
312                                 goto out;
313                         case AST_PBX_KEEPALIVE:
314                                 ast_debug(2, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
315                                 ast_verb(2, "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
316                                 goto out;
317                                 break;
318                         default:
319                                 ast_debug(2, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
320                                 ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
321                                 dead = 1;
322                                 goto out;
323                         }
324                 }
325
326                 ast_debug(1, "Executed application: %s\n", runningapp);
327
328                 if (!strcasecmp(runningapp, "GOSUB")) {
329                         gosub_level++;
330                         ast_debug(1, "Incrementing gosub_level\n");
331                 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
332                         char tmp2[1024], *cond, *app, *app2 = tmp2;
333                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
334                         cond = strsep(&app2, "?");
335                         app = strsep(&app2, ":");
336                         if (pbx_checkcondition(cond)) {
337                                 if (!ast_strlen_zero(app)) {
338                                         gosub_level++;
339                                         ast_debug(1, "Incrementing gosub_level\n");
340                                 }
341                         } else {
342                                 if (!ast_strlen_zero(app2)) {
343                                         gosub_level++;
344                                         ast_debug(1, "Incrementing gosub_level\n");
345                                 }
346                         }
347                 } else if (!strcasecmp(runningapp, "RETURN")) {
348                         gosub_level--;
349                         ast_debug(1, "Decrementing gosub_level\n");
350                 } else if (!strcasecmp(runningapp, "STACKPOP")) {
351                         gosub_level--;
352                         ast_debug(1, "Decrementing gosub_level\n");
353                 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
354                         /* Must evaluate args to find actual app */
355                         char tmp2[1024], *tmp3 = NULL;
356                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
357                         if (!strcasecmp(runningapp, "EXECIF")) {
358                                 tmp3 = strchr(tmp2, '|');
359                                 if (tmp3)
360                                         *tmp3++ = '\0';
361                                 if (!pbx_checkcondition(tmp2))
362                                         tmp3 = NULL;
363                         } else
364                                 tmp3 = tmp2;
365
366                         if (tmp3)
367                                 ast_debug(1, "Last app: %s\n", tmp3);
368
369                         if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
370                                 gosub_level++;
371                                 ast_debug(1, "Incrementing gosub_level\n");
372                         } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
373                                 gosub_level--;
374                                 ast_debug(1, "Decrementing gosub_level\n");
375                         } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
376                                 gosub_level--;
377                                 ast_debug(1, "Decrementing gosub_level\n");
378                         }
379                 }
380
381                 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
382                         ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
383                         break;
384                 }
385
386                 /* don't stop executing extensions when we're in "h" */
387                 if (ast_check_hangup(chan) && !inhangup) {
388                         ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
389                         goto out;
390                 }
391                 chan->priority++;
392         }
393         out:
394         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
395         snprintf(depthc, sizeof(depthc), "%d", depth);
396         if (!dead) {
397                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
398                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
399         }
400
401         for (x = 1; x < argc; x++) {
402                 /* Restore old arguments and delete ours */
403                 snprintf(varname, sizeof(varname), "ARG%d", x);
404                 if (oldargs[x]) {
405                         if (!dead)
406                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
407                         ast_free(oldargs[x]);
408                 } else if (!dead) {
409                         pbx_builtin_setvar_helper(chan, varname, NULL);
410                 }
411         }
412
413         /* Restore macro variables */
414         if (!dead) {
415                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
416                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
417                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
418         }
419         if (save_macro_exten)
420                 ast_free(save_macro_exten);
421         if (save_macro_context)
422                 ast_free(save_macro_context);
423         if (save_macro_priority)
424                 ast_free(save_macro_priority);
425
426         if (!dead && setmacrocontext) {
427                 chan->macrocontext[0] = '\0';
428                 chan->macroexten[0] = '\0';
429                 chan->macropriority = 0;
430         }
431
432         if (!dead && !strcasecmp(chan->context, fullmacro)) {
433                 /* If we're leaving the macro normally, restore original information */
434                 chan->priority = oldpriority;
435                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
436                 if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
437                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
438                         const char *offsets;
439                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
440                         ast_channel_lock(chan);
441                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
442                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
443                                 normally if there is any problem */
444                                 if (sscanf(offsets, "%d", &offset) == 1) {
445                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
446                                                 chan->priority += offset;
447                                         }
448                                 }
449                         }
450                         ast_channel_unlock(chan);
451                 }
452         }
453
454         if (!dead)
455                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
456         if (save_macro_offset)
457                 ast_free(save_macro_offset);
458
459         /* Unlock the macro */
460         if (exclusive) {
461                 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
462                 if (ast_context_unlockmacro(fullmacro)) {
463                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
464                         res = 0;
465                 }
466         }
467
468         return res;
469 }
470
471 static int macro_exec(struct ast_channel *chan, void *data)
472 {
473         return _macro_exec(chan, data, 0);
474 }
475
476 static int macroexclusive_exec(struct ast_channel *chan, void *data)
477 {
478         return _macro_exec(chan, data, 1);
479 }
480
481 static int macroif_exec(struct ast_channel *chan, void *data) 
482 {
483         char *expr = NULL, *label_a = NULL, *label_b = NULL;
484         int res = 0;
485
486         if (!(expr = ast_strdupa(data)))
487                 return -1;
488
489         if ((label_a = strchr(expr, '?'))) {
490                 *label_a = '\0';
491                 label_a++;
492                 if ((label_b = strchr(label_a, ':'))) {
493                         *label_b = '\0';
494                         label_b++;
495                 }
496                 if (pbx_checkcondition(expr))
497                         res = macro_exec(chan, label_a);
498                 else if (label_b) 
499                         res = macro_exec(chan, label_b);
500         } else
501                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
502
503         return res;
504 }
505                         
506 static int macro_exit_exec(struct ast_channel *chan, void *data)
507 {
508         return MACRO_EXIT_RESULT;
509 }
510
511 static int unload_module(void)
512 {
513         int res;
514
515         res = ast_unregister_application(if_app);
516         res |= ast_unregister_application(exit_app);
517         res |= ast_unregister_application(app);
518         res |= ast_unregister_application(exclusive_app);
519
520         return res;
521 }
522
523 static int load_module(void)
524 {
525         int res;
526
527         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
528         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
529         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
530         res |= ast_register_application(app, macro_exec, synopsis, descrip);
531
532         return res;
533 }
534
535 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");