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
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include <sys/types.h>
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 *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 *exclusive_descrip =
72 " MacroExclusive(macroname|arg1|arg2...):\n"
73 "Executes macro defined in the context 'macro-macroname'\n"
74 "Only one call at a time may run the macro.\n"
75 "(we'll wait if another call is busy executing in the Macro)\n"
76 "Arguments and return values as in application Macro()\n";
78 static char *exit_descrip =
80 "Causes the currently running macro to exit as if it had\n"
81 "ended normally by running out of priorities to execute.\n"
82 "If used outside a macro, will likely cause unexpected\n"
85 static char *app = "Macro";
86 static char *if_app = "MacroIf";
87 static char *exclusive_app = "MacroExclusive";
88 static char *exit_app = "MacroExit";
90 static char *synopsis = "Macro Implementation";
91 static char *if_synopsis = "Conditional Macro Implementation";
92 static char *exclusive_synopsis = "Exclusive Macro Implementation";
93 static char *exit_synopsis = "Exit From Macro";
96 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
105 char *oldargs[MAX_ARGS + 1] = { NULL, };
108 char oldexten[256]="";
110 char pc[80], depthc[12];
111 char oldcontext[AST_MAX_CONTEXT] = "";
112 int offset, depth = 0, maxdepth = 7;
113 int setmacrocontext=0;
114 int autoloopflag, dead = 0;
116 char *save_macro_exten;
117 char *save_macro_context;
118 char *save_macro_priority;
119 char *save_macro_offset;
120 struct ast_module_user *u;
122 if (ast_strlen_zero(data)) {
123 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
127 u = ast_module_user_add(chan);
129 /* does the user want a deeper rabbit hole? */
130 s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
132 sscanf(s, "%d", &maxdepth);
134 /* Count how many levels deep the rabbit hole goes */
135 s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
137 sscanf(s, "%d", &depth);
138 if (depth >= maxdepth) {
139 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
140 ast_module_user_remove(u);
143 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
144 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
146 tmp = ast_strdupa(data);
148 macro = strsep(&rest, "|");
149 if (ast_strlen_zero(macro)) {
150 ast_log(LOG_WARNING, "Invalid macro name specified\n");
151 ast_module_user_remove(u);
155 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
156 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
157 if (!ast_context_find(fullmacro))
158 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
160 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
161 ast_module_user_remove(u);
165 /* If we are to run the macro exclusively, take the mutex */
167 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
168 ast_autoservice_start(chan);
169 if (ast_context_lockmacro(fullmacro)) {
170 ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
171 ast_autoservice_stop(chan);
172 ast_module_user_remove(u);
176 ast_autoservice_stop(chan);
180 oldpriority = chan->priority;
181 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
182 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
183 if (ast_strlen_zero(chan->macrocontext)) {
184 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
185 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
186 chan->macropriority = chan->priority;
190 /* Save old macro variables */
191 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
192 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
194 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
195 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
197 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
198 snprintf(pc, sizeof(pc), "%d", oldpriority);
199 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
201 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
202 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
204 /* Setup environment for new run */
205 chan->exten[0] = 's';
206 chan->exten[1] = '\0';
207 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
210 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
212 /* Save copy of old arguments if we're overwriting some, otherwise
213 let them pass through to the other macro */
214 snprintf(varname, sizeof(varname), "ARG%d", argc);
215 s = pbx_builtin_getvar_helper(chan, varname);
217 oldargs[argc] = ast_strdup(s);
218 pbx_builtin_setvar_helper(chan, varname, cur);
221 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
222 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
223 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
224 /* Reset the macro depth, if it was changed in the last iteration */
225 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
226 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
227 /* Something bad happened, or a hangup has been requested. */
228 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
229 (res == '*') || (res == '#')) {
230 /* Just return result as to the previous application as if it had been dialed */
231 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
235 case MACRO_EXIT_RESULT:
238 case AST_PBX_KEEPALIVE:
240 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);
241 else if (option_verbose > 1)
242 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);
247 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);
248 else if (option_verbose > 1)
249 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);
254 if (strcasecmp(chan->context, fullmacro)) {
255 if (option_verbose > 1)
256 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
259 /* don't stop executing extensions when we're in "h" */
260 if (chan->_softhangup && strcasecmp(chan->macroexten,"h")) {
261 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
262 chan->exten, chan->macroexten, chan->priority);
268 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
269 snprintf(depthc, sizeof(depthc), "%d", depth);
271 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
272 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
275 for (x = 1; x < argc; x++) {
276 /* Restore old arguments and delete ours */
277 snprintf(varname, sizeof(varname), "ARG%d", x);
280 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
283 pbx_builtin_setvar_helper(chan, varname, NULL);
287 /* Restore macro variables */
289 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
290 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
291 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
293 if (save_macro_exten)
294 free(save_macro_exten);
295 if (save_macro_context)
296 free(save_macro_context);
297 if (save_macro_priority)
298 free(save_macro_priority);
300 if (!dead && setmacrocontext) {
301 chan->macrocontext[0] = '\0';
302 chan->macroexten[0] = '\0';
303 chan->macropriority = 0;
306 if (!dead && !strcasecmp(chan->context, fullmacro)) {
307 /* If we're leaving the macro normally, restore original information */
308 chan->priority = oldpriority;
309 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
310 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
311 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
313 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
314 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
315 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
316 normally if there is any problem */
317 if (sscanf(offsets, "%d", &offset) == 1) {
318 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
319 chan->priority += offset;
327 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
328 if (save_macro_offset)
329 free(save_macro_offset);
331 /* Unlock the macro */
333 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
334 if (ast_context_unlockmacro(fullmacro)) {
335 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
340 ast_module_user_remove(u);
345 static int macro_exec(struct ast_channel *chan, void *data)
347 return _macro_exec(chan, data, 0);
350 static int macroexclusive_exec(struct ast_channel *chan, void *data)
352 return _macro_exec(chan, data, 1);
355 static int macroif_exec(struct ast_channel *chan, void *data)
357 char *expr = NULL, *label_a = NULL, *label_b = NULL;
359 struct ast_module_user *u;
361 u = ast_module_user_add(chan);
363 if (!(expr = ast_strdupa(data))) {
364 ast_module_user_remove(u);
368 if ((label_a = strchr(expr, '?'))) {
371 if ((label_b = strchr(label_a, ':'))) {
375 if (pbx_checkcondition(expr))
376 macro_exec(chan, label_a);
378 macro_exec(chan, label_b);
380 ast_log(LOG_WARNING, "Invalid Syntax.\n");
382 ast_module_user_remove(u);
387 static int macro_exit_exec(struct ast_channel *chan, void *data)
389 return MACRO_EXIT_RESULT;
392 static int unload_module(void)
396 res = ast_unregister_application(if_app);
397 res |= ast_unregister_application(exit_app);
398 res |= ast_unregister_application(app);
399 res |= ast_unregister_application(exclusive_app);
401 ast_module_user_hangup_all();
406 static int load_module(void)
410 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
411 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
412 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
413 res |= ast_register_application(app, macro_exec, synopsis, descrip);
418 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");