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