Mon Apr 30 07:36:28 2007

Asterisk developer's documentation


app_macro.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Dial plan macro Implementation
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  * 
00025  * \ingroup applications
00026  */
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00031 
00032 #include <stdlib.h>
00033 #include <stdio.h>
00034 #include <string.h>
00035 #include <unistd.h>
00036 #include <sys/types.h>
00037 
00038 #include "asterisk/file.h"
00039 #include "asterisk/logger.h"
00040 #include "asterisk/channel.h"
00041 #include "asterisk/pbx.h"
00042 #include "asterisk/module.h"
00043 #include "asterisk/options.h"
00044 #include "asterisk/config.h"
00045 #include "asterisk/utils.h"
00046 #include "asterisk/lock.h"
00047 
00048 #define MAX_ARGS 80
00049 
00050 /* special result value used to force macro exit */
00051 #define MACRO_EXIT_RESULT 1024
00052 
00053 static char *descrip =
00054 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
00055 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
00056 "executing each step, then returning when the steps end. \n"
00057 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
00058 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
00059 "${ARG1}, ${ARG2}, etc in the macro context.\n"
00060 "If you Goto out of the Macro context, the Macro will terminate and control\n"
00061 "will be returned at the location of the Goto.\n"
00062 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
00063 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
00064 "Extensions: While a macro is being executed, it becomes the current context.\n"
00065 "            This means that if a hangup occurs, for instance, that the macro\n"
00066 "            will be searched for an 'h' extension, NOT the context from which\n"
00067 "            the macro was called. So, make sure to define all appropriate\n"
00068 "            extensions in your macro! (you can use 'catch' in AEL) \n"
00069 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
00070 "         contained within it via sub-engine), and a fixed per-thread\n"
00071 "         memory stack allowance, macros are limited to 7 levels\n"
00072 "         of nesting (macro calling macro calling macro, etc.); It\n"
00073 "         may be possible that stack-intensive applications in deeply nested macros\n"
00074 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
00075 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
00076 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
00077 
00078 static char *if_descrip =
00079 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
00080 "Executes macro defined in <macroname_a> if <expr> is true\n"
00081 "(otherwise <macroname_b> if provided)\n"
00082 "Arguments and return values as in application macro()\n";
00083 
00084 static char *exclusive_descrip =
00085 "  MacroExclusive(macroname|arg1|arg2...):\n"
00086 "Executes macro defined in the context 'macro-macroname'\n"
00087 "Only one call at a time may run the macro.\n"
00088 "(we'll wait if another call is busy executing in the Macro)\n"
00089 "Arguments and return values as in application Macro()\n";
00090 
00091 static char *exit_descrip =
00092 "  MacroExit():\n"
00093 "Causes the currently running macro to exit as if it had\n"
00094 "ended normally by running out of priorities to execute.\n"
00095 "If used outside a macro, will likely cause unexpected\n"
00096 "behavior.\n";
00097 
00098 static char *app = "Macro";
00099 static char *if_app = "MacroIf";
00100 static char *exclusive_app = "MacroExclusive";
00101 static char *exit_app = "MacroExit";
00102 
00103 static char *synopsis = "Macro Implementation";
00104 static char *if_synopsis = "Conditional Macro Implementation";
00105 static char *exclusive_synopsis = "Exclusive Macro Implementation";
00106 static char *exit_synopsis = "Exit From Macro";
00107 
00108 
00109 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
00110 {
00111    struct ast_exten *e;
00112    struct ast_include *i;
00113    struct ast_context *c2;
00114 
00115    for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
00116       if (ast_extension_match(ast_get_extension_name(e), exten)) {
00117          int needmatch = ast_get_extension_matchcid(e);
00118          if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
00119             (!needmatch)) {
00120             /* This is the matching extension we want */
00121             struct ast_exten *p;
00122             for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
00123                if (priority != ast_get_extension_priority(p))
00124                   continue;
00125                return p;
00126             }
00127          }
00128       }
00129    }
00130 
00131    /* No match; run through includes */
00132    for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
00133       for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
00134          if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
00135             e = find_matching_priority(c2, exten, priority, callerid);
00136             if (e)
00137                return e;
00138          }
00139       }
00140    }
00141    return NULL;
00142 }
00143 
00144 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
00145 {
00146    const char *s;
00147    char *tmp;
00148    char *cur, *rest;
00149    char *macro;
00150    char fullmacro[80];
00151    char varname[80];
00152    char *oldargs[MAX_ARGS + 1] = { NULL, };
00153    int argc, x;
00154    int res=0;
00155    char oldexten[256]="";
00156    int oldpriority, gosub_level = 0;
00157    char pc[80], depthc[12];
00158    char oldcontext[AST_MAX_CONTEXT] = "";
00159    const char *inhangupc;
00160    int offset, depth = 0, maxdepth = 7;
00161    int setmacrocontext=0;
00162    int autoloopflag, dead = 0, inhangup = 0;
00163   
00164    char *save_macro_exten;
00165    char *save_macro_context;
00166    char *save_macro_priority;
00167    char *save_macro_offset;
00168    struct ast_module_user *u;
00169  
00170    struct ast_context *c;
00171    struct ast_exten *e;
00172 
00173    if (ast_strlen_zero(data)) {
00174       ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
00175       return -1;
00176    }
00177 
00178    u = ast_module_user_add(chan);
00179 
00180    /* does the user want a deeper rabbit hole? */
00181    s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
00182    if (s)
00183       sscanf(s, "%d", &maxdepth);
00184 
00185    /* Count how many levels deep the rabbit hole goes */
00186    s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
00187    if (s)
00188       sscanf(s, "%d", &depth);
00189    /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
00190    if (strcmp(chan->exten, "h") == 0)
00191       pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
00192    inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
00193    if (!ast_strlen_zero(inhangupc))
00194       sscanf(inhangupc, "%d", &inhangup);
00195 
00196    if (depth >= maxdepth) {
00197       ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
00198       ast_module_user_remove(u);
00199       return 0;
00200    }
00201    snprintf(depthc, sizeof(depthc), "%d", depth + 1);
00202    pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00203 
00204    tmp = ast_strdupa(data);
00205    rest = tmp;
00206    macro = strsep(&rest, "|");
00207    if (ast_strlen_zero(macro)) {
00208       ast_log(LOG_WARNING, "Invalid macro name specified\n");
00209       ast_module_user_remove(u);
00210       return 0;
00211    }
00212 
00213    snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
00214    if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
00215       if (!ast_context_find(fullmacro)) 
00216          ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
00217       else
00218          ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
00219       ast_module_user_remove(u);
00220       return 0;
00221    }
00222 
00223    /* If we are to run the macro exclusively, take the mutex */
00224    if (exclusive) {
00225       ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
00226       ast_autoservice_start(chan);
00227       if (ast_context_lockmacro(fullmacro)) {
00228          ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
00229          ast_autoservice_stop(chan);
00230          ast_module_user_remove(u);
00231 
00232          return 0;
00233       }
00234       ast_autoservice_stop(chan);
00235    }
00236    
00237    /* Save old info */
00238    oldpriority = chan->priority;
00239    ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
00240    ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
00241    if (ast_strlen_zero(chan->macrocontext)) {
00242       ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
00243       ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
00244       chan->macropriority = chan->priority;
00245       setmacrocontext=1;
00246    }
00247    argc = 1;
00248    /* Save old macro variables */
00249    save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
00250    pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
00251 
00252    save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
00253    pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
00254 
00255    save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
00256    snprintf(pc, sizeof(pc), "%d", oldpriority);
00257    pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
00258   
00259    save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
00260    pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
00261 
00262    /* Setup environment for new run */
00263    chan->exten[0] = 's';
00264    chan->exten[1] = '\0';
00265    ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
00266    chan->priority = 1;
00267 
00268    while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
00269       const char *s;
00270       /* Save copy of old arguments if we're overwriting some, otherwise
00271          let them pass through to the other macro */
00272       snprintf(varname, sizeof(varname), "ARG%d", argc);
00273       s = pbx_builtin_getvar_helper(chan, varname);
00274       if (s)
00275          oldargs[argc] = ast_strdup(s);
00276       pbx_builtin_setvar_helper(chan, varname, cur);
00277       argc++;
00278    }
00279    autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
00280    ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
00281    while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
00282       /* What application will execute? */
00283       for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
00284          if (!strcmp(ast_get_context_name(c), chan->context)) {
00285             e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
00286             break;
00287          }
00288       }
00289 
00290       /* Reset the macro depth, if it was changed in the last iteration */
00291       pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00292 
00293       if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
00294          /* Something bad happened, or a hangup has been requested. */
00295          if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00296             (res == '*') || (res == '#')) {
00297             /* Just return result as to the previous application as if it had been dialed */
00298             ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
00299             break;
00300          }
00301          switch(res) {
00302          case MACRO_EXIT_RESULT:
00303             res = 0;
00304             goto out;
00305          case AST_PBX_KEEPALIVE:
00306             if (option_debug)
00307                ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
00308             else if (option_verbose > 1)
00309                ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
00310             goto out;
00311             break;
00312          default:
00313             if (option_debug)
00314                ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00315             else if (option_verbose > 1)
00316                ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00317             dead = 1;
00318             goto out;
00319          }
00320       }
00321 
00322       ast_log(LOG_DEBUG, "Executed application: %s\n", ast_get_extension_app(e));
00323 
00324       if (e && !strcasecmp(ast_get_extension_app(e), "GOSUB")) {
00325          gosub_level++;
00326          ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00327       } else if (e && !strcasecmp(ast_get_extension_app(e), "GOSUBIF")) {
00328          const char *tmp = ast_get_extension_app_data(e);
00329          char tmp2[1024] = "", *cond, *app, *app2 = tmp2;
00330          pbx_substitute_variables_helper(chan, tmp, tmp2, sizeof(tmp2) - 1);
00331          cond = strsep(&app2, "?");
00332          app = strsep(&app2, ":");
00333          if (pbx_checkcondition(cond)) {
00334             if (!ast_strlen_zero(app)) {
00335                gosub_level++;
00336                ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00337             }
00338          } else {
00339             if (!ast_strlen_zero(app2)) {
00340                gosub_level++;
00341                ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00342             }
00343          }
00344       } else if (e && !strcasecmp(ast_get_extension_app(e), "RETURN")) {
00345          gosub_level--;
00346          ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00347       } else if (e && !strcasecmp(ast_get_extension_app(e), "STACKPOP")) {
00348          gosub_level--;
00349          ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00350       } else if (e && !strncasecmp(ast_get_extension_app(e), "EXEC", 4)) {
00351          /* Must evaluate args to find actual app */
00352          const char *tmp = ast_get_extension_app_data(e);
00353          char tmp2[1024] = "", *tmp3 = NULL;
00354          pbx_substitute_variables_helper(chan, tmp, tmp2, sizeof(tmp2) - 1);
00355          if (!strcasecmp(ast_get_extension_app(e), "EXECIF")) {
00356             tmp3 = strchr(tmp2, '|');
00357             if (tmp3)
00358                *tmp3++ = '\0';
00359             if (!pbx_checkcondition(tmp2))
00360                tmp3 = NULL;
00361          } else
00362             tmp3 = tmp2;
00363 
00364          if (tmp3)
00365             ast_log(LOG_DEBUG, "Last app: %s\n", tmp3);
00366 
00367          if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
00368             gosub_level++;
00369             ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00370          } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
00371             gosub_level--;
00372             ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00373          } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
00374             gosub_level--;
00375             ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00376          }
00377       }
00378 
00379       if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
00380          if (option_verbose > 1)
00381             ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00382          break;
00383       }
00384 
00385       /* don't stop executing extensions when we're in "h" */
00386       if (chan->_softhangup && !inhangup) {
00387          ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
00388             chan->exten, chan->macroexten, chan->priority);
00389          goto out;
00390       }
00391       chan->priority++;
00392    }
00393    out:
00394    /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
00395    snprintf(depthc, sizeof(depthc), "%d", depth);
00396    if (!dead) {
00397       pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00398       ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00399    }
00400 
00401    for (x = 1; x < argc; x++) {
00402       /* Restore old arguments and delete ours */
00403       snprintf(varname, sizeof(varname), "ARG%d", x);
00404       if (oldargs[x]) {
00405          if (!dead)
00406             pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00407          free(oldargs[x]);
00408       } else if (!dead) {
00409          pbx_builtin_setvar_helper(chan, varname, NULL);
00410       }
00411    }
00412 
00413    /* Restore macro variables */
00414    if (!dead) {
00415       pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00416       pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00417       pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00418    }
00419    if (save_macro_exten)
00420       free(save_macro_exten);
00421    if (save_macro_context)
00422       free(save_macro_context);
00423    if (save_macro_priority)
00424       free(save_macro_priority);
00425 
00426    if (!dead && setmacrocontext) {
00427       chan->macrocontext[0] = '\0';
00428       chan->macroexten[0] = '\0';
00429       chan->macropriority = 0;
00430    }
00431 
00432    if (!dead && !strcasecmp(chan->context, fullmacro)) {
00433       /* If we're leaving the macro normally, restore original information */
00434       chan->priority = oldpriority;
00435       ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00436       if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
00437          /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
00438          const char *offsets;
00439          ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00440          if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00441             /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
00442                normally if there is any problem */
00443             if (sscanf(offsets, "%d", &offset) == 1) {
00444                if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00445                   chan->priority += offset;
00446                }
00447             }
00448          }
00449       }
00450    }
00451 
00452    if (!dead)
00453       pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00454    if (save_macro_offset)
00455       free(save_macro_offset);
00456 
00457    /* Unlock the macro */
00458    if (exclusive) {
00459       ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
00460       if (ast_context_unlockmacro(fullmacro)) {
00461          ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
00462          res = 0;
00463       }
00464    }
00465    
00466    ast_module_user_remove(u);
00467 
00468    return res;
00469 }
00470 
00471 static int macro_exec(struct ast_channel *chan, void *data)
00472 {
00473    return _macro_exec(chan, data, 0);
00474 }
00475 
00476 static int macroexclusive_exec(struct ast_channel *chan, void *data)
00477 {
00478    return _macro_exec(chan, data, 1);
00479 }
00480 
00481 static int macroif_exec(struct ast_channel *chan, void *data) 
00482 {
00483    char *expr = NULL, *label_a = NULL, *label_b = NULL;
00484    int res = 0;
00485    struct ast_module_user *u;
00486 
00487    u = ast_module_user_add(chan);
00488 
00489    if (!(expr = ast_strdupa(data))) {
00490       ast_module_user_remove(u);
00491       return -1;
00492    }
00493 
00494    if ((label_a = strchr(expr, '?'))) {
00495       *label_a = '\0';
00496       label_a++;
00497       if ((label_b = strchr(label_a, ':'))) {
00498          *label_b = '\0';
00499          label_b++;
00500       }
00501       if (pbx_checkcondition(expr))
00502          macro_exec(chan, label_a);
00503       else if (label_b) 
00504          macro_exec(chan, label_b);
00505    } else
00506       ast_log(LOG_WARNING, "Invalid Syntax.\n");
00507 
00508    ast_module_user_remove(u);
00509 
00510    return res;
00511 }
00512          
00513 static int macro_exit_exec(struct ast_channel *chan, void *data)
00514 {
00515    return MACRO_EXIT_RESULT;
00516 }
00517 
00518 static int unload_module(void)
00519 {
00520    int res;
00521 
00522    res = ast_unregister_application(if_app);
00523    res |= ast_unregister_application(exit_app);
00524    res |= ast_unregister_application(app);
00525    res |= ast_unregister_application(exclusive_app);
00526 
00527    ast_module_user_hangup_all();
00528 
00529    return res;
00530 }
00531 
00532 static int load_module(void)
00533 {
00534    int res;
00535 
00536    res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00537    res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00538    res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
00539    res |= ast_register_application(app, macro_exec, synopsis, descrip);
00540 
00541    return res;
00542 }
00543 
00544 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");

Generated on Mon Apr 30 07:36:28 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1