Sat Nov 25 00:45:28 2006

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  * \ingroup applications
00024  */
00025 
00026 #include <stdlib.h>
00027 #include <stdio.h>
00028 #include <string.h>
00029 #include <unistd.h>
00030 #include <sys/types.h>
00031 
00032 #include "asterisk.h"
00033 
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 44343 $")
00035 
00036 #include "asterisk/file.h"
00037 #include "asterisk/logger.h"
00038 #include "asterisk/channel.h"
00039 #include "asterisk/pbx.h"
00040 #include "asterisk/module.h"
00041 #include "asterisk/options.h"
00042 #include "asterisk/config.h"
00043 #include "asterisk/utils.h"
00044 #include "asterisk/lock.h"
00045 
00046 #define MAX_ARGS 80
00047 
00048 /* special result value used to force macro exit */
00049 #define MACRO_EXIT_RESULT 1024
00050 
00051 static char *tdesc = "Extension Macros";
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 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
00065 "         contained within it via sub-engine), and a fixed per-thread\n"
00066 "         memory stack allowance, macros are limited to 7 levels\n"
00067 "         of nesting (macro calling macro calling macro, etc.); It\n"
00068 "         may be possible that stack-intensive applications in deeply nested\n"
00069 "         macros could cause asterisk to crash earlier than this limit.\n";
00070 
00071 static char *if_descrip =
00072 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
00073 "Executes macro defined in <macroname_a> if <expr> is true\n"
00074 "(otherwise <macroname_b> if provided)\n"
00075 "Arguments and return values as in application macro()\n";
00076 
00077 static char *exit_descrip =
00078 "  MacroExit():\n"
00079 "Causes the currently running macro to exit as if it had\n"
00080 "ended normally by running out of priorities to execute.\n"
00081 "If used outside a macro, will likely cause unexpected\n"
00082 "behavior.\n";
00083 
00084 static char *app = "Macro";
00085 static char *if_app = "MacroIf";
00086 static char *exit_app = "MacroExit";
00087 
00088 static char *synopsis = "Macro Implementation";
00089 static char *if_synopsis = "Conditional Macro Implementation";
00090 static char *exit_synopsis = "Exit From Macro";
00091 
00092 STANDARD_LOCAL_USER;
00093 
00094 LOCAL_USER_DECL;
00095 
00096 static int macro_exec(struct ast_channel *chan, void *data)
00097 {
00098    char *tmp;
00099    char *cur, *rest;
00100    char *macro;
00101    char fullmacro[80];
00102    char varname[80];
00103    char *oldargs[MAX_ARGS + 1] = { NULL, };
00104    int argc, x;
00105    int res=0;
00106    char oldexten[256]="";
00107    int oldpriority;
00108    char pc[80], depthc[12];
00109    char oldcontext[AST_MAX_CONTEXT] = "";
00110    char *offsets, *s;
00111    int offset, depth = 0, maxdepth = 7;
00112    int setmacrocontext=0;
00113    int autoloopflag, dead = 0;
00114   
00115    char *save_macro_exten;
00116    char *save_macro_context;
00117    char *save_macro_priority;
00118    char *save_macro_offset;
00119    struct localuser *u;
00120  
00121    if (ast_strlen_zero(data)) {
00122       ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
00123       return -1;
00124    }
00125 
00126    LOCAL_USER_ADD(u);
00127 
00128    /* does the user want a deeper rabbit hole? */
00129    s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
00130    if (s)
00131       sscanf(s, "%d", &maxdepth);
00132 
00133    /* Count how many levels deep the rabbit hole goes */
00134    tmp = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
00135    if (tmp) {
00136       sscanf(tmp, "%d", &depth);
00137    } else {
00138       depth = 0;
00139    }
00140 
00141    if (depth >= maxdepth) {
00142       ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
00143       LOCAL_USER_REMOVE(u);
00144       return 0;
00145    }
00146    snprintf(depthc, sizeof(depthc), "%d", depth + 1);
00147    pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00148 
00149    tmp = ast_strdupa(data);
00150    rest = tmp;
00151    macro = strsep(&rest, "|");
00152    if (ast_strlen_zero(macro)) {
00153       ast_log(LOG_WARNING, "Invalid macro name specified\n");
00154       LOCAL_USER_REMOVE(u);
00155       return 0;
00156    }
00157    snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
00158    if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
00159       if (!ast_context_find(fullmacro)) 
00160          ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
00161       else
00162          ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
00163       LOCAL_USER_REMOVE(u);
00164       return 0;
00165    }
00166    
00167    /* Save old info */
00168    oldpriority = chan->priority;
00169    ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
00170    ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
00171    if (ast_strlen_zero(chan->macrocontext)) {
00172       ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
00173       ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
00174       chan->macropriority = chan->priority;
00175       setmacrocontext=1;
00176    }
00177    argc = 1;
00178    /* Save old macro variables */
00179    save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
00180    if (save_macro_exten) 
00181       save_macro_exten = strdup(save_macro_exten);
00182    pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
00183 
00184    save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
00185    if (save_macro_context)
00186       save_macro_context = strdup(save_macro_context);
00187    pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
00188 
00189    save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
00190    if (save_macro_priority) 
00191       save_macro_priority = strdup(save_macro_priority);
00192    snprintf(pc, sizeof(pc), "%d", oldpriority);
00193    pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
00194   
00195    save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
00196    if (save_macro_offset) 
00197       save_macro_offset = strdup(save_macro_offset);
00198    pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
00199 
00200    /* Setup environment for new run */
00201    chan->exten[0] = 's';
00202    chan->exten[1] = '\0';
00203    ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
00204    chan->priority = 1;
00205 
00206    while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
00207       /* Save copy of old arguments if we're overwriting some, otherwise
00208          let them pass through to the other macro */
00209       snprintf(varname, sizeof(varname), "ARG%d", argc);
00210       oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
00211       if (oldargs[argc])
00212          oldargs[argc] = strdup(oldargs[argc]);
00213       pbx_builtin_setvar_helper(chan, varname, cur);
00214       argc++;
00215    }
00216    autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
00217    ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
00218    while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
00219       /* Reset the macro depth, if it was changed in the last iteration */
00220       pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00221       if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
00222          /* Something bad happened, or a hangup has been requested. */
00223          if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00224             (res == '*') || (res == '#')) {
00225             /* Just return result as to the previous application as if it had been dialed */
00226             ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
00227             break;
00228          }
00229          switch(res) {
00230          case MACRO_EXIT_RESULT:
00231             res = 0;
00232             goto out;
00233          case AST_PBX_KEEPALIVE:
00234             if (option_debug)
00235                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);
00236             if (option_verbose > 1)
00237                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);
00238             goto out;
00239             break;
00240          default:
00241             if (option_debug)
00242                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);
00243             if (option_verbose > 1)
00244                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);
00245             dead = 1;
00246             goto out;
00247          }
00248       }
00249       if (strcasecmp(chan->context, fullmacro)) {
00250          if (option_verbose > 1)
00251             ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00252          break;
00253       }
00254       /* don't stop executing extensions when we're in "h" */
00255       if (chan->_softhangup && strcasecmp(oldexten,"h") && strcasecmp(chan->macroexten,"h")) {
00256          ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
00257             chan->exten, chan->macroexten, chan->priority);
00258          goto out;
00259       }
00260       chan->priority++;
00261    }
00262    out:
00263    /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
00264    snprintf(depthc, sizeof(depthc), "%d", depth);
00265    if (!dead) {
00266       pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00267 
00268       ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00269    }
00270 
00271    for (x = 1; x < argc; x++) {
00272       /* Restore old arguments and delete ours */
00273       snprintf(varname, sizeof(varname), "ARG%d", x);
00274       if (oldargs[x]) {
00275          if (!dead)
00276             pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00277          free(oldargs[x]);
00278       } else if (!dead) {
00279          pbx_builtin_setvar_helper(chan, varname, NULL);
00280       }
00281    }
00282 
00283    /* Restore macro variables */
00284    if (!dead) {
00285       pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00286       pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00287       pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00288    }
00289    if (save_macro_exten)
00290       free(save_macro_exten);
00291    if (save_macro_context)
00292       free(save_macro_context);
00293    if (save_macro_priority)
00294       free(save_macro_priority);
00295 
00296    if (!dead && setmacrocontext) {
00297       chan->macrocontext[0] = '\0';
00298       chan->macroexten[0] = '\0';
00299       chan->macropriority = 0;
00300    }
00301 
00302    if (!dead && !strcasecmp(chan->context, fullmacro)) {
00303       /* If we're leaving the macro normally, restore original information */
00304       chan->priority = oldpriority;
00305       ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00306       if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
00307          /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
00308          ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00309          if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00310             /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
00311                normally if there is any problem */
00312             if (sscanf(offsets, "%d", &offset) == 1) {
00313                if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00314                   chan->priority += offset;
00315                }
00316             }
00317          }
00318       }
00319    }
00320 
00321    if (!dead)
00322       pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00323    if (save_macro_offset)
00324       free(save_macro_offset);
00325    LOCAL_USER_REMOVE(u);
00326    return res;
00327 }
00328 
00329 static int macroif_exec(struct ast_channel *chan, void *data) 
00330 {
00331    char *expr = NULL, *label_a = NULL, *label_b = NULL;
00332    int res = 0;
00333    struct localuser *u;
00334 
00335    LOCAL_USER_ADD(u);
00336 
00337    expr = ast_strdupa(data);
00338    if (!expr) {
00339       ast_log(LOG_ERROR, "Out of Memory!\n");
00340       LOCAL_USER_REMOVE(u);
00341       return -1;
00342    }
00343 
00344    if ((label_a = strchr(expr, '?'))) {
00345       *label_a = '\0';
00346       label_a++;
00347       if ((label_b = strchr(label_a, ':'))) {
00348          *label_b = '\0';
00349          label_b++;
00350       }
00351       if (pbx_checkcondition(expr))
00352          macro_exec(chan, label_a);
00353       else if (label_b) 
00354          macro_exec(chan, label_b);
00355    } else
00356       ast_log(LOG_WARNING, "Invalid Syntax.\n");
00357 
00358    LOCAL_USER_REMOVE(u);
00359 
00360    return res;
00361 }
00362          
00363 static int macro_exit_exec(struct ast_channel *chan, void *data)
00364 {
00365    return MACRO_EXIT_RESULT;
00366 }
00367 
00368 int unload_module(void)
00369 {
00370    int res;
00371 
00372    res = ast_unregister_application(if_app);
00373    res |= ast_unregister_application(exit_app);
00374    res |= ast_unregister_application(app);
00375 
00376    STANDARD_HANGUP_LOCALUSERS;
00377 
00378    return res;
00379 }
00380 
00381 int load_module(void)
00382 {
00383    int res;
00384 
00385    res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00386    res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00387    res |= ast_register_application(app, macro_exec, synopsis, descrip);
00388 
00389    return res;
00390 }
00391 
00392 char *description(void)
00393 {
00394    return tdesc;
00395 }
00396 
00397 int usecount(void)
00398 {
00399    int res;
00400    STANDARD_USECOUNT(res);
00401    return res;
00402 }
00403 
00404 char *key()
00405 {
00406    return ASTERISK_GPL_KEY;
00407 }

Generated on Sat Nov 25 00:45:28 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.6