2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief Dial plan macro Implementation
23 * \ingroup applications
30 #include <sys/types.h>
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include "asterisk/file.h"
37 #include "asterisk/logger.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/pbx.h"
40 #include "asterisk/module.h"
41 #include "asterisk/options.h"
42 #include "asterisk/config.h"
43 #include "asterisk/utils.h"
44 #include "asterisk/lock.h"
48 /* special result value used to force macro exit */
49 #define MACRO_EXIT_RESULT 1024
51 static char *tdesc = "Extension Macros";
53 static char *descrip =
54 " Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
55 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
56 "executing each step, then returning when the steps end. \n"
57 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
58 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
59 "${ARG1}, ${ARG2}, etc in the macro context.\n"
60 "If you Goto out of the Macro context, the Macro will terminate and control\n"
61 "will be returned at the location of the Goto.\n"
62 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
63 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
65 static char *if_descrip =
66 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
67 "Executes macro defined in <macroname_a> if <expr> is true\n"
68 "(otherwise <macroname_b> if provided)\n"
69 "Arguments and return values as in application macro()\n";
71 static char *exit_descrip =
73 "Causes the currently running macro to exit as if it had\n"
74 "ended normally by running out of priorities to execute.\n"
75 "If used outside a macro, will likely cause unexpected\n"
78 static char *app = "Macro";
79 static char *if_app = "MacroIf";
80 static char *exit_app = "MacroExit";
82 static char *synopsis = "Macro Implementation";
83 static char *if_synopsis = "Conditional Macro Implementation";
84 static char *exit_synopsis = "Exit From Macro";
90 static int macro_exec(struct ast_channel *chan, void *data)
97 char *oldargs[MAX_ARGS + 1] = { NULL, };
100 char oldexten[256]="";
102 char pc[80], depthc[12];
103 char oldcontext[AST_MAX_CONTEXT] = "";
106 int setmacrocontext=0;
109 char *save_macro_exten;
110 char *save_macro_context;
111 char *save_macro_priority;
112 char *save_macro_offset;
115 if (ast_strlen_zero(data)) {
116 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
122 /* Count how many levels deep the rabbit hole goes */
123 tmp = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
125 sscanf(tmp, "%d", &depth);
131 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
132 LOCAL_USER_REMOVE(u);
135 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
136 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
138 tmp = ast_strdupa(data);
140 macro = strsep(&rest, "|");
141 if (ast_strlen_zero(macro)) {
142 ast_log(LOG_WARNING, "Invalid macro name specified\n");
143 LOCAL_USER_REMOVE(u);
146 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
147 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
148 if (!ast_context_find(fullmacro))
149 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
151 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
152 LOCAL_USER_REMOVE(u);
157 oldpriority = chan->priority;
158 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
159 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
160 if (ast_strlen_zero(chan->macrocontext)) {
161 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
162 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
163 chan->macropriority = chan->priority;
167 /* Save old macro variables */
168 save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
169 if (save_macro_exten)
170 save_macro_exten = strdup(save_macro_exten);
171 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
173 save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
174 if (save_macro_context)
175 save_macro_context = strdup(save_macro_context);
176 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
178 save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
179 if (save_macro_priority)
180 save_macro_priority = strdup(save_macro_priority);
181 snprintf(pc, sizeof(pc), "%d", oldpriority);
182 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
184 save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
185 if (save_macro_offset)
186 save_macro_offset = strdup(save_macro_offset);
187 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
189 /* Setup environment for new run */
190 chan->exten[0] = 's';
191 chan->exten[1] = '\0';
192 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
195 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
196 /* Save copy of old arguments if we're overwriting some, otherwise
197 let them pass through to the other macro */
198 snprintf(varname, sizeof(varname), "ARG%d", argc);
199 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
201 oldargs[argc] = strdup(oldargs[argc]);
202 pbx_builtin_setvar_helper(chan, varname, cur);
205 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
206 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
207 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
208 /* Reset the macro depth, if it was changed in the last iteration */
209 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
210 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
211 /* Something bad happened, or a hangup has been requested. */
212 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
213 (res == '*') || (res == '#')) {
214 /* Just return result as to the previous application as if it had been dialed */
215 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
219 case MACRO_EXIT_RESULT:
222 case AST_PBX_KEEPALIVE:
224 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);
225 else if (option_verbose > 1)
226 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);
231 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);
232 else if (option_verbose > 1)
233 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);
237 if (strcasecmp(chan->context, fullmacro)) {
238 if (option_verbose > 1)
239 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
242 /* don't stop executing extensions when we're in "h" */
243 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
244 ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
245 chan->exten, chan->priority);
251 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
252 snprintf(depthc, sizeof(depthc), "%d", depth);
253 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
255 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
256 for (x=1; x<argc; x++) {
257 /* Restore old arguments and delete ours */
258 snprintf(varname, sizeof(varname), "ARG%d", x);
260 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
263 pbx_builtin_setvar_helper(chan, varname, NULL);
267 /* Restore macro variables */
268 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
269 if (save_macro_exten)
270 free(save_macro_exten);
271 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
272 if (save_macro_context)
273 free(save_macro_context);
274 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
275 if (save_macro_priority)
276 free(save_macro_priority);
277 if (setmacrocontext) {
278 chan->macrocontext[0] = '\0';
279 chan->macroexten[0] = '\0';
280 chan->macropriority = 0;
283 if (!strcasecmp(chan->context, fullmacro)) {
284 /* If we're leaving the macro normally, restore original information */
285 chan->priority = oldpriority;
286 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
287 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
288 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
289 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
290 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
291 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
292 normally if there is any problem */
293 if (sscanf(offsets, "%d", &offset) == 1) {
294 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
295 chan->priority += offset;
302 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
303 if (save_macro_offset)
304 free(save_macro_offset);
305 LOCAL_USER_REMOVE(u);
309 static int macroif_exec(struct ast_channel *chan, void *data)
311 char *expr = NULL, *label_a = NULL, *label_b = NULL;
317 expr = ast_strdupa(data);
319 ast_log(LOG_ERROR, "Out of Memory!\n");
320 LOCAL_USER_REMOVE(u);
324 if ((label_a = strchr(expr, '?'))) {
327 if ((label_b = strchr(label_a, ':'))) {
332 macro_exec(chan, label_a);
334 macro_exec(chan, label_b);
336 ast_log(LOG_WARNING, "Invalid Syntax.\n");
338 LOCAL_USER_REMOVE(u);
343 static int macro_exit_exec(struct ast_channel *chan, void *data)
345 return MACRO_EXIT_RESULT;
348 int unload_module(void)
352 res = ast_unregister_application(if_app);
353 res |= ast_unregister_application(exit_app);
354 res |= ast_unregister_application(app);
356 STANDARD_HANGUP_LOCALUSERS;
361 int load_module(void)
365 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
366 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
367 res |= ast_register_application(app, macro_exec, synopsis, descrip);
372 char *description(void)
380 STANDARD_USECOUNT(res);
386 return ASTERISK_GPL_KEY;