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