Add Macro(), MacroExit(), MacroExclusive() and
[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 struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
159 {
160         struct ast_exten *e;
161         struct ast_include *i;
162         struct ast_context *c2;
163
164         for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
165                 if (ast_extension_match(ast_get_extension_name(e), exten)) {
166                         int needmatch = ast_get_extension_matchcid(e);
167                         if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
168                                 (!needmatch)) {
169                                 /* This is the matching extension we want */
170                                 struct ast_exten *p;
171                                 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
172                                         if (priority != ast_get_extension_priority(p))
173                                                 continue;
174                                         return p;
175                                 }
176                         }
177                 }
178         }
179
180         /* No match; run through includes */
181         for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
182                 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
183                         if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
184                                 e = find_matching_priority(c2, exten, priority, callerid);
185                                 if (e)
186                                         return e;
187                         }
188                 }
189         }
190         return NULL;
191 }
192
193 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
194 {
195         const char *s;
196         char *tmp;
197         char *cur, *rest;
198         char *macro;
199         char fullmacro[80];
200         char varname[80];
201         char runningapp[80], runningdata[1024];
202         char *oldargs[MAX_ARGS + 1] = { NULL, };
203         int argc, x;
204         int res=0;
205         char oldexten[256]="";
206         int oldpriority, gosub_level = 0;
207         char pc[80], depthc[12];
208         char oldcontext[AST_MAX_CONTEXT] = "";
209         const char *inhangupc;
210         int offset, depth = 0, maxdepth = 7;
211         int setmacrocontext=0;
212         int autoloopflag, dead = 0, inhangup = 0;
213   
214         char *save_macro_exten;
215         char *save_macro_context;
216         char *save_macro_priority;
217         char *save_macro_offset;
218  
219         if (ast_strlen_zero(data)) {
220                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n");
221                 return -1;
222         }
223
224         /* does the user want a deeper rabbit hole? */
225         ast_channel_lock(chan);
226         if ((s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"))) {
227                 sscanf(s, "%d", &maxdepth);
228         }
229         
230         /* Count how many levels deep the rabbit hole goes */
231         if ((s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"))) {
232                 sscanf(s, "%d", &depth);
233         }
234         
235         /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
236         if (strcmp(chan->exten, "h") == 0)
237                 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
238         
239         if ((inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"))) {
240                 sscanf(inhangupc, "%d", &inhangup);
241         }
242         ast_channel_unlock(chan);
243
244         if (depth >= maxdepth) {
245                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
246                 return 0;
247         }
248         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
249         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
250
251         tmp = ast_strdupa(data);
252         rest = tmp;
253         macro = strsep(&rest, ",");
254         if (ast_strlen_zero(macro)) {
255                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
256                 return 0;
257         }
258
259         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
260         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
261                 if (!ast_context_find(fullmacro)) 
262                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
263                 else
264                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
265                 return 0;
266         }
267
268         /* If we are to run the macro exclusively, take the mutex */
269         if (exclusive) {
270                 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
271                 ast_autoservice_start(chan);
272                 if (ast_context_lockmacro(fullmacro)) {
273                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
274                         ast_autoservice_stop(chan);
275                         return 0;
276                 }
277                 ast_autoservice_stop(chan);
278         }
279         
280         /* Save old info */
281         oldpriority = chan->priority;
282         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
283         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
284         if (ast_strlen_zero(chan->macrocontext)) {
285                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
286                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
287                 chan->macropriority = chan->priority;
288                 setmacrocontext=1;
289         }
290         argc = 1;
291         /* Save old macro variables */
292         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
293         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
294
295         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
296         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
297
298         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
299         snprintf(pc, sizeof(pc), "%d", oldpriority);
300         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
301   
302         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
303         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
304
305         /* Setup environment for new run */
306         chan->exten[0] = 's';
307         chan->exten[1] = '\0';
308         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
309         chan->priority = 1;
310
311         ast_channel_lock(chan);
312         while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
313                 const char *argp;
314                 /* Save copy of old arguments if we're overwriting some, otherwise
315                 let them pass through to the other macro */
316                 snprintf(varname, sizeof(varname), "ARG%d", argc);
317                 if ((argp = pbx_builtin_getvar_helper(chan, varname))) {
318                         oldargs[argc] = ast_strdup(argp);
319                 }
320                 pbx_builtin_setvar_helper(chan, varname, cur);
321                 argc++;
322         }
323         ast_channel_unlock(chan);
324         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
325         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
326         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
327                 struct ast_context *c;
328                 struct ast_exten *e;
329                 int foundx;
330                 runningapp[0] = '\0';
331                 runningdata[0] = '\0';
332
333                 /* What application will execute? */
334                 if (ast_rdlock_contexts()) {
335                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
336                 } else {
337                         for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
338                                 if (!strcmp(ast_get_context_name(c), chan->context)) {
339                                         if (ast_rdlock_context(c)) {
340                                                 ast_log(LOG_WARNING, "Unable to lock context?\n");
341                                         } else {
342                                                 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
343                                                 if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
344                                                         ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
345                                                         ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
346                                                 }
347                                                 ast_unlock_context(c);
348                                         }
349                                         break;
350                                 }
351                         }
352                 }
353                 ast_unlock_contexts();
354
355                 /* Reset the macro depth, if it was changed in the last iteration */
356                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
357
358                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
359                         /* Something bad happened, or a hangup has been requested. */
360                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
361                         (res == '*') || (res == '#')) {
362                                 /* Just return result as to the previous application as if it had been dialed */
363                                 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
364                                 break;
365                         }
366                         switch(res) {
367                         case MACRO_EXIT_RESULT:
368                                 res = 0;
369                                 goto out;
370                         case AST_PBX_KEEPALIVE:
371                                 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);
372                                 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);
373                                 goto out;
374                                 break;
375                         default:
376                                 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);
377                                 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);
378                                 dead = 1;
379                                 goto out;
380                         }
381                 }
382
383                 ast_debug(1, "Executed application: %s\n", runningapp);
384
385                 if (!strcasecmp(runningapp, "GOSUB")) {
386                         gosub_level++;
387                         ast_debug(1, "Incrementing gosub_level\n");
388                 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
389                         char tmp2[1024], *cond, *app_arg, *app2 = tmp2;
390                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
391                         cond = strsep(&app2, "?");
392                         app_arg = strsep(&app2, ":");
393                         if (pbx_checkcondition(cond)) {
394                                 if (!ast_strlen_zero(app_arg)) {
395                                         gosub_level++;
396                                         ast_debug(1, "Incrementing gosub_level\n");
397                                 }
398                         } else {
399                                 if (!ast_strlen_zero(app2)) {
400                                         gosub_level++;
401                                         ast_debug(1, "Incrementing gosub_level\n");
402                                 }
403                         }
404                 } else if (!strcasecmp(runningapp, "RETURN")) {
405                         gosub_level--;
406                         ast_debug(1, "Decrementing gosub_level\n");
407                 } else if (!strcasecmp(runningapp, "STACKPOP")) {
408                         gosub_level--;
409                         ast_debug(1, "Decrementing gosub_level\n");
410                 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
411                         /* Must evaluate args to find actual app */
412                         char tmp2[1024], *tmp3 = NULL;
413                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
414                         if (!strcasecmp(runningapp, "EXECIF")) {
415                                 tmp3 = strchr(tmp2, '|');
416                                 if (tmp3)
417                                         *tmp3++ = '\0';
418                                 if (!pbx_checkcondition(tmp2))
419                                         tmp3 = NULL;
420                         } else
421                                 tmp3 = tmp2;
422
423                         if (tmp3)
424                                 ast_debug(1, "Last app: %s\n", tmp3);
425
426                         if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
427                                 gosub_level++;
428                                 ast_debug(1, "Incrementing gosub_level\n");
429                         } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
430                                 gosub_level--;
431                                 ast_debug(1, "Decrementing gosub_level\n");
432                         } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
433                                 gosub_level--;
434                                 ast_debug(1, "Decrementing gosub_level\n");
435                         }
436                 }
437
438                 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
439                         ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
440                         break;
441                 }
442
443                 /* don't stop executing extensions when we're in "h" */
444                 if (ast_check_hangup(chan) && !inhangup) {
445                         ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
446                         goto out;
447                 }
448                 chan->priority++;
449         }
450         out:
451         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
452         snprintf(depthc, sizeof(depthc), "%d", depth);
453         if (!dead) {
454                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
455                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
456         }
457
458         for (x = 1; x < argc; x++) {
459                 /* Restore old arguments and delete ours */
460                 snprintf(varname, sizeof(varname), "ARG%d", x);
461                 if (oldargs[x]) {
462                         if (!dead)
463                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
464                         ast_free(oldargs[x]);
465                 } else if (!dead) {
466                         pbx_builtin_setvar_helper(chan, varname, NULL);
467                 }
468         }
469
470         /* Restore macro variables */
471         if (!dead) {
472                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
473                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
474                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
475         }
476         if (save_macro_exten)
477                 ast_free(save_macro_exten);
478         if (save_macro_context)
479                 ast_free(save_macro_context);
480         if (save_macro_priority)
481                 ast_free(save_macro_priority);
482
483         if (!dead && setmacrocontext) {
484                 chan->macrocontext[0] = '\0';
485                 chan->macroexten[0] = '\0';
486                 chan->macropriority = 0;
487         }
488
489         if (!dead && !strcasecmp(chan->context, fullmacro)) {
490                 /* If we're leaving the macro normally, restore original information */
491                 chan->priority = oldpriority;
492                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
493                 if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
494                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
495                         const char *offsets;
496                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
497                         ast_channel_lock(chan);
498                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
499                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
500                                 normally if there is any problem */
501                                 if (sscanf(offsets, "%d", &offset) == 1) {
502                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
503                                                 chan->priority += offset;
504                                         }
505                                 }
506                         }
507                         ast_channel_unlock(chan);
508                 }
509         }
510
511         if (!dead)
512                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
513         if (save_macro_offset)
514                 ast_free(save_macro_offset);
515
516         /* Unlock the macro */
517         if (exclusive) {
518                 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
519                 if (ast_context_unlockmacro(fullmacro)) {
520                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
521                         res = 0;
522                 }
523         }
524
525         return res;
526 }
527
528 static int macro_exec(struct ast_channel *chan, void *data)
529 {
530         return _macro_exec(chan, data, 0);
531 }
532
533 static int macroexclusive_exec(struct ast_channel *chan, void *data)
534 {
535         return _macro_exec(chan, data, 1);
536 }
537
538 static int macroif_exec(struct ast_channel *chan, void *data) 
539 {
540         char *expr = NULL, *label_a = NULL, *label_b = NULL;
541         int res = 0;
542
543         if (!(expr = ast_strdupa(data)))
544                 return -1;
545
546         if ((label_a = strchr(expr, '?'))) {
547                 *label_a = '\0';
548                 label_a++;
549                 if ((label_b = strchr(label_a, ':'))) {
550                         *label_b = '\0';
551                         label_b++;
552                 }
553                 if (pbx_checkcondition(expr))
554                         res = macro_exec(chan, label_a);
555                 else if (label_b) 
556                         res = macro_exec(chan, label_b);
557         } else
558                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
559
560         return res;
561 }
562                         
563 static int macro_exit_exec(struct ast_channel *chan, void *data)
564 {
565         return MACRO_EXIT_RESULT;
566 }
567
568 static int unload_module(void)
569 {
570         int res;
571
572         res = ast_unregister_application(if_app);
573         res |= ast_unregister_application(exit_app);
574         res |= ast_unregister_application(app);
575         res |= ast_unregister_application(exclusive_app);
576
577         return res;
578 }
579
580 static int load_module(void)
581 {
582         int res;
583
584         res = ast_register_application_xml(exit_app, macro_exit_exec);
585         res |= ast_register_application_xml(if_app, macroif_exec);
586         res |= ast_register_application_xml(exclusive_app, macroexclusive_exec);
587         res |= ast_register_application_xml(app, macro_exec);
588
589         return res;
590 }
591
592 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");