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 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
32 #include <sys/types.h>
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/options.h"
44 #include "asterisk/config.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/lock.h"
50 /* special result value used to force macro exit */
51 #define MACRO_EXIT_RESULT 1024
53 static char *tdesc = "Extension Macros";
55 static char *descrip =
56 " Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
57 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
58 "executing each step, then returning when the steps end. \n"
59 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
60 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
61 "${ARG1}, ${ARG2}, etc in the macro context.\n"
62 "If you Goto out of the Macro context, the Macro will terminate and control\n"
63 "will be returned at the location of the Goto.\n"
64 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
65 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
67 static char *if_descrip =
68 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
69 "Executes macro defined in <macroname_a> if <expr> is true\n"
70 "(otherwise <macroname_b> if provided)\n"
71 "Arguments and return values as in application macro()\n";
73 static char *exit_descrip =
75 "Causes the currently running macro to exit as if it had\n"
76 "ended normally by running out of priorities to execute.\n"
77 "If used outside a macro, will likely cause unexpected\n"
80 static char *app = "Macro";
81 static char *if_app = "MacroIf";
82 static char *exit_app = "MacroExit";
84 static char *synopsis = "Macro Implementation";
85 static char *if_synopsis = "Conditional Macro Implementation";
86 static char *exit_synopsis = "Exit From Macro";
92 static int macro_exec(struct ast_channel *chan, void *data)
101 char *oldargs[MAX_ARGS + 1] = { NULL, };
104 char oldexten[256]="";
106 char pc[80], depthc[12];
107 char oldcontext[AST_MAX_CONTEXT] = "";
108 int offset, depth = 0;
109 int setmacrocontext=0;
112 char *save_macro_exten;
113 char *save_macro_context;
114 char *save_macro_priority;
115 char *save_macro_offset;
118 if (ast_strlen_zero(data)) {
119 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
125 /* Count how many levels deep the rabbit hole goes */
126 s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
128 sscanf(s, "%d", &depth);
130 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
131 LOCAL_USER_REMOVE(u);
134 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
135 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
137 tmp = ast_strdupa(data);
139 macro = strsep(&rest, "|");
140 if (ast_strlen_zero(macro)) {
141 ast_log(LOG_WARNING, "Invalid macro name specified\n");
142 LOCAL_USER_REMOVE(u);
145 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
146 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
147 if (!ast_context_find(fullmacro))
148 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
150 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
151 LOCAL_USER_REMOVE(u);
156 oldpriority = chan->priority;
157 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
158 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
159 if (ast_strlen_zero(chan->macrocontext)) {
160 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
161 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
162 chan->macropriority = chan->priority;
166 /* Save old macro variables */
167 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
168 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
170 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
171 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
173 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
174 snprintf(pc, sizeof(pc), "%d", oldpriority);
175 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
177 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
178 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
180 /* Setup environment for new run */
181 chan->exten[0] = 's';
182 chan->exten[1] = '\0';
183 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
186 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
188 /* Save copy of old arguments if we're overwriting some, otherwise
189 let them pass through to the other macro */
190 snprintf(varname, sizeof(varname), "ARG%d", argc);
191 s = pbx_builtin_getvar_helper(chan, varname);
193 oldargs[argc] = strdup(s);
194 pbx_builtin_setvar_helper(chan, varname, cur);
197 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
198 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
199 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
200 /* Reset the macro depth, if it was changed in the last iteration */
201 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
202 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
203 /* Something bad happened, or a hangup has been requested. */
204 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
205 (res == '*') || (res == '#')) {
206 /* Just return result as to the previous application as if it had been dialed */
207 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
211 case MACRO_EXIT_RESULT:
214 case AST_PBX_KEEPALIVE:
216 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);
217 else if (option_verbose > 1)
218 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);
223 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);
224 else if (option_verbose > 1)
225 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);
229 if (strcasecmp(chan->context, fullmacro)) {
230 if (option_verbose > 1)
231 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
234 /* don't stop executing extensions when we're in "h" */
235 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
236 ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
237 chan->exten, chan->priority);
243 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
244 snprintf(depthc, sizeof(depthc), "%d", depth);
245 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
247 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
248 for (x=1; x<argc; x++) {
249 /* Restore old arguments and delete ours */
250 snprintf(varname, sizeof(varname), "ARG%d", x);
252 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
255 pbx_builtin_setvar_helper(chan, varname, NULL);
259 /* Restore macro variables */
260 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
261 if (save_macro_exten)
262 free(save_macro_exten);
263 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
264 if (save_macro_context)
265 free(save_macro_context);
266 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
267 if (save_macro_priority)
268 free(save_macro_priority);
269 if (setmacrocontext) {
270 chan->macrocontext[0] = '\0';
271 chan->macroexten[0] = '\0';
272 chan->macropriority = 0;
275 if (!strcasecmp(chan->context, fullmacro)) {
276 /* If we're leaving the macro normally, restore original information */
277 chan->priority = oldpriority;
278 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
279 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
280 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
282 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
283 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
284 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
285 normally if there is any problem */
286 if (sscanf(offsets, "%d", &offset) == 1) {
287 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
288 chan->priority += offset;
295 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
296 if (save_macro_offset)
297 free(save_macro_offset);
298 LOCAL_USER_REMOVE(u);
302 static int macroif_exec(struct ast_channel *chan, void *data)
304 char *expr = NULL, *label_a = NULL, *label_b = NULL;
310 expr = ast_strdupa(data);
312 ast_log(LOG_ERROR, "Out of Memory!\n");
313 LOCAL_USER_REMOVE(u);
317 if ((label_a = strchr(expr, '?'))) {
320 if ((label_b = strchr(label_a, ':'))) {
325 macro_exec(chan, label_a);
327 macro_exec(chan, label_b);
329 ast_log(LOG_WARNING, "Invalid Syntax.\n");
331 LOCAL_USER_REMOVE(u);
336 static int macro_exit_exec(struct ast_channel *chan, void *data)
338 return MACRO_EXIT_RESULT;
341 int unload_module(void)
345 res = ast_unregister_application(if_app);
346 res |= ast_unregister_application(exit_app);
347 res |= ast_unregister_application(app);
349 STANDARD_HANGUP_LOCALUSERS;
354 int load_module(void)
358 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
359 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
360 res |= ast_register_application(app, macro_exec, synopsis, descrip);
365 char *description(void)
373 STANDARD_USECOUNT(res);
379 return ASTERISK_GPL_KEY;