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 * Macro Implementation
25 #include <sys/types.h>
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include "asterisk/file.h"
35 #include "asterisk/logger.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/pbx.h"
38 #include "asterisk/module.h"
39 #include "asterisk/options.h"
40 #include "asterisk/config.h"
41 #include "asterisk/utils.h"
42 #include "asterisk/lock.h"
46 /* special result value used to force macro exit */
47 #define MACRO_EXIT_RESULT 1024
49 static char *tdesc = "Extension Macros";
51 static char *descrip =
52 " Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
53 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
54 "executing each step, then returning when the steps end. \n"
55 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
56 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
57 "${ARG1}, ${ARG2}, etc in the macro context.\n"
58 "If you Goto out of the Macro context, the Macro will terminate and control\n"
59 "will be returned at the location of the Goto.\n"
60 "Macro returns -1 if any step in the macro returns -1, and 0 otherwise.\n"
61 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
62 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
64 static char *if_descrip =
65 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
66 "Executes macro defined in <macroname_a> if <expr> is true\n"
67 "(otherwise <macroname_b> if provided)\n"
68 "Arguments and return values as in application macro()\n";
70 static char *exit_descrip =
72 "Causes the currently running macro to exit as if it had\n"
73 "ended normally by running out of priorities to execute.\n"
74 "If used outside a macro, will likely cause unexpected\n"
77 static char *app = "Macro";
78 static char *if_app = "MacroIf";
79 static char *exit_app = "MacroExit";
81 static char *synopsis = "Macro Implementation";
82 static char *if_synopsis = "Conditional Macro Implementation";
83 static char *exit_synopsis = "Exit From Macro";
89 static int macro_exec(struct ast_channel *chan, void *data)
96 char *oldargs[MAX_ARGS + 1] = { NULL, };
99 char oldexten[256]="";
101 char pc[80], depthc[12];
102 char oldcontext[AST_MAX_CONTEXT] = "";
105 int setmacrocontext=0;
108 char *save_macro_exten;
109 char *save_macro_context;
110 char *save_macro_priority;
111 char *save_macro_offset;
114 if (!data || ast_strlen_zero(data)) {
115 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
121 /* Count how many levels deep the rabbit hole goes */
122 tmp = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
124 sscanf(tmp, "%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 (!macro || 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 = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
168 if (save_macro_exten)
169 save_macro_exten = strdup(save_macro_exten);
170 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
172 save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
173 if (save_macro_context)
174 save_macro_context = strdup(save_macro_context);
175 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
177 save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
178 if (save_macro_priority)
179 save_macro_priority = strdup(save_macro_priority);
180 snprintf(pc, sizeof(pc), "%d", oldpriority);
181 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
183 save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
184 if (save_macro_offset)
185 save_macro_offset = strdup(save_macro_offset);
186 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
188 /* Setup environment for new run */
189 chan->exten[0] = 's';
190 chan->exten[1] = '\0';
191 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
194 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
195 /* Save copy of old arguments if we're overwriting some, otherwise
196 let them pass through to the other macro */
197 snprintf(varname, sizeof(varname), "ARG%d", argc);
198 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
200 oldargs[argc] = strdup(oldargs[argc]);
201 pbx_builtin_setvar_helper(chan, varname, cur);
204 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
205 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
206 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
207 /* Reset the macro depth, if it was changed in the last iteration */
208 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
209 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
210 /* Something bad happened, or a hangup has been requested. */
211 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
212 (res == '*') || (res == '#')) {
213 /* Just return result as to the previous application as if it had been dialed */
214 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
218 case MACRO_EXIT_RESULT:
221 case AST_PBX_KEEPALIVE:
223 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);
224 else if (option_verbose > 1)
225 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);
230 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);
231 else if (option_verbose > 1)
232 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);
236 if (strcasecmp(chan->context, fullmacro)) {
237 if (option_verbose > 1)
238 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
241 /* don't stop executing extensions when we're in "h" */
242 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
243 ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
244 chan->exten, chan->priority);
250 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
251 snprintf(depthc, sizeof(depthc), "%d", depth);
252 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
254 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
255 for (x=1; x<argc; x++) {
256 /* Restore old arguments and delete ours */
257 snprintf(varname, sizeof(varname), "ARG%d", x);
259 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
262 pbx_builtin_setvar_helper(chan, varname, NULL);
266 /* Restore macro variables */
267 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
268 if (save_macro_exten)
269 free(save_macro_exten);
270 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
271 if (save_macro_context)
272 free(save_macro_context);
273 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
274 if (save_macro_priority)
275 free(save_macro_priority);
276 if (setmacrocontext) {
277 chan->macrocontext[0] = '\0';
278 chan->macroexten[0] = '\0';
279 chan->macropriority = 0;
282 if (!strcasecmp(chan->context, fullmacro)) {
283 /* If we're leaving the macro normally, restore original information */
284 chan->priority = oldpriority;
285 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
286 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
287 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
288 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
289 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
290 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
291 normally if there is any problem */
292 if (sscanf(offsets, "%d", &offset) == 1) {
293 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
294 chan->priority += offset;
301 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
302 if (save_macro_offset)
303 free(save_macro_offset);
304 LOCAL_USER_REMOVE(u);
308 static int macroif_exec(struct ast_channel *chan, void *data)
310 char *expr = NULL, *label_a = NULL, *label_b = NULL;
316 expr = ast_strdupa(data);
318 ast_log(LOG_ERROR, "Out of Memory!\n");
319 LOCAL_USER_REMOVE(u);
323 if ((label_a = strchr(expr, '?'))) {
326 if ((label_b = strchr(label_a, ':'))) {
331 macro_exec(chan, label_a);
333 macro_exec(chan, label_b);
335 ast_log(LOG_WARNING, "Invalid Syntax.\n");
337 LOCAL_USER_REMOVE(u);
342 static int macro_exit_exec(struct ast_channel *chan, void *data)
344 return MACRO_EXIT_RESULT;
347 int unload_module(void)
351 res = ast_unregister_application(if_app);
352 res |= ast_unregister_application(exit_app);
353 res |= ast_unregister_application(app);
355 STANDARD_HANGUP_LOCALUSERS;
360 int load_module(void)
364 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
365 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
366 res |= ast_register_application(app, macro_exec, synopsis, descrip);
371 char *description(void)
379 STANDARD_USECOUNT(res);
385 return ASTERISK_GPL_KEY;