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"
64 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
65 " contained within it via sub-engine), and a fixed per-thread\n"
66 " memory stack allowance, macros are limited to 7 levels\n"
67 " of nesting (macro calling macro calling macro, etc.); It\n"
68 " may be possible that stack-intensive applications in deeply nested macros\n"
69 " could cause asterisk to crash earlier than this limit.\n";
71 static char *if_descrip
=
72 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
73 "Executes macro defined in <macroname_a> if <expr> is true\n"
74 "(otherwise <macroname_b> if provided)\n"
75 "Arguments and return values as in application macro()\n";
77 static char *exclusive_descrip
=
78 " MacroExclusive(macroname|arg1|arg2...):\n"
79 "Executes macro defined in the context 'macro-macroname'\n"
80 "Only one call at a time may run the macro.\n"
81 "(we'll wait if another call is busy executing in the Macro)\n"
82 "Arguments and return values as in application Macro()\n";
84 static char *exit_descrip
=
86 "Causes the currently running macro to exit as if it had\n"
87 "ended normally by running out of priorities to execute.\n"
88 "If used outside a macro, will likely cause unexpected\n"
91 static char *app
= "Macro";
92 static char *if_app
= "MacroIf";
93 static char *exclusive_app
= "MacroExclusive";
94 static char *exit_app
= "MacroExit";
96 static char *synopsis
= "Macro Implementation";
97 static char *if_synopsis
= "Conditional Macro Implementation";
98 static char *exclusive_synopsis
= "Exclusive Macro Implementation";
99 static char *exit_synopsis
= "Exit From Macro";
102 static int _macro_exec(struct ast_channel
*chan
, void *data
, int exclusive
)
111 char *oldargs
[MAX_ARGS
+ 1] = { NULL
, };
114 char oldexten
[256]="";
116 char pc
[80], depthc
[12];
117 char oldcontext
[AST_MAX_CONTEXT
] = "";
118 int offset
, depth
= 0, maxdepth
= 7;
119 int setmacrocontext
=0;
120 int autoloopflag
, dead
= 0;
122 char *save_macro_exten
;
123 char *save_macro_context
;
124 char *save_macro_priority
;
125 char *save_macro_offset
;
126 struct ast_module_user
*u
;
128 if (ast_strlen_zero(data
)) {
129 ast_log(LOG_WARNING
, "Macro() requires arguments. See \"show application macro\" for help.\n");
133 u
= ast_module_user_add(chan
);
135 /* does the user want a deeper rabbit hole? */
136 s
= pbx_builtin_getvar_helper(chan
, "MACRO_RECURSION");
138 sscanf(s
, "%d", &maxdepth
);
140 /* Count how many levels deep the rabbit hole goes */
141 s
= pbx_builtin_getvar_helper(chan
, "MACRO_DEPTH");
143 sscanf(s
, "%d", &depth
);
144 if (depth
>= maxdepth
) {
145 ast_log(LOG_ERROR
, "Macro(): possible infinite loop detected. Returning early.\n");
146 ast_module_user_remove(u
);
149 snprintf(depthc
, sizeof(depthc
), "%d", depth
+ 1);
150 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
152 tmp
= ast_strdupa(data
);
154 macro
= strsep(&rest
, "|");
155 if (ast_strlen_zero(macro
)) {
156 ast_log(LOG_WARNING
, "Invalid macro name specified\n");
157 ast_module_user_remove(u
);
161 snprintf(fullmacro
, sizeof(fullmacro
), "macro-%s", macro
);
162 if (!ast_exists_extension(chan
, fullmacro
, "s", 1, chan
->cid
.cid_num
)) {
163 if (!ast_context_find(fullmacro
))
164 ast_log(LOG_WARNING
, "No such context '%s' for macro '%s'\n", fullmacro
, macro
);
166 ast_log(LOG_WARNING
, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro
, macro
);
167 ast_module_user_remove(u
);
171 /* If we are to run the macro exclusively, take the mutex */
173 ast_log(LOG_DEBUG
, "Locking macrolock for '%s'\n", fullmacro
);
174 ast_autoservice_start(chan
);
175 if (ast_context_lockmacro(fullmacro
)) {
176 ast_log(LOG_WARNING
, "Failed to lock macro '%s' as in-use\n", fullmacro
);
177 ast_autoservice_stop(chan
);
178 ast_module_user_remove(u
);
182 ast_autoservice_stop(chan
);
186 oldpriority
= chan
->priority
;
187 ast_copy_string(oldexten
, chan
->exten
, sizeof(oldexten
));
188 ast_copy_string(oldcontext
, chan
->context
, sizeof(oldcontext
));
189 if (ast_strlen_zero(chan
->macrocontext
)) {
190 ast_copy_string(chan
->macrocontext
, chan
->context
, sizeof(chan
->macrocontext
));
191 ast_copy_string(chan
->macroexten
, chan
->exten
, sizeof(chan
->macroexten
));
192 chan
->macropriority
= chan
->priority
;
196 /* Save old macro variables */
197 save_macro_exten
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_EXTEN"));
198 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", oldexten
);
200 save_macro_context
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_CONTEXT"));
201 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", oldcontext
);
203 save_macro_priority
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_PRIORITY"));
204 snprintf(pc
, sizeof(pc
), "%d", oldpriority
);
205 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", pc
);
207 save_macro_offset
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"));
208 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", NULL
);
210 /* Setup environment for new run */
211 chan
->exten
[0] = 's';
212 chan
->exten
[1] = '\0';
213 ast_copy_string(chan
->context
, fullmacro
, sizeof(chan
->context
));
216 while((cur
= strsep(&rest
, "|")) && (argc
< MAX_ARGS
)) {
218 /* Save copy of old arguments if we're overwriting some, otherwise
219 let them pass through to the other macro */
220 snprintf(varname
, sizeof(varname
), "ARG%d", argc
);
221 s
= pbx_builtin_getvar_helper(chan
, varname
);
223 oldargs
[argc
] = ast_strdup(s
);
224 pbx_builtin_setvar_helper(chan
, varname
, cur
);
227 autoloopflag
= ast_test_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
228 ast_set_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
229 while(ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
)) {
230 /* Reset the macro depth, if it was changed in the last iteration */
231 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
232 if ((res
= ast_spawn_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
))) {
233 /* Something bad happened, or a hangup has been requested. */
234 if (((res
>= '0') && (res
<= '9')) || ((res
>= 'A') && (res
<= 'F')) ||
235 (res
== '*') || (res
== '#')) {
236 /* Just return result as to the previous application as if it had been dialed */
237 ast_log(LOG_DEBUG
, "Oooh, got something to jump out with ('%c')!\n", res
);
241 case MACRO_EXIT_RESULT
:
244 case AST_PBX_KEEPALIVE
:
246 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
);
247 else if (option_verbose
> 1)
248 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
);
253 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
);
254 else if (option_verbose
> 1)
255 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
);
260 if (strcasecmp(chan
->context
, fullmacro
)) {
261 if (option_verbose
> 1)
262 ast_verbose(VERBOSE_PREFIX_2
"Channel '%s' jumping out of macro '%s'\n", chan
->name
, macro
);
265 /* don't stop executing extensions when we're in "h" */
266 if (chan
->_softhangup
&& strcasecmp(oldexten
,"h") && strcasecmp(chan
->macroexten
,"h")) {
267 ast_log(LOG_DEBUG
, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
268 chan
->exten
, chan
->macroexten
, chan
->priority
);
274 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
275 snprintf(depthc
, sizeof(depthc
), "%d", depth
);
277 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
278 ast_set2_flag(chan
, autoloopflag
, AST_FLAG_IN_AUTOLOOP
);
281 for (x
= 1; x
< argc
; x
++) {
282 /* Restore old arguments and delete ours */
283 snprintf(varname
, sizeof(varname
), "ARG%d", x
);
286 pbx_builtin_setvar_helper(chan
, varname
, oldargs
[x
]);
289 pbx_builtin_setvar_helper(chan
, varname
, NULL
);
293 /* Restore macro variables */
295 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", save_macro_exten
);
296 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", save_macro_context
);
297 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", save_macro_priority
);
299 if (save_macro_exten
)
300 free(save_macro_exten
);
301 if (save_macro_context
)
302 free(save_macro_context
);
303 if (save_macro_priority
)
304 free(save_macro_priority
);
306 if (!dead
&& setmacrocontext
) {
307 chan
->macrocontext
[0] = '\0';
308 chan
->macroexten
[0] = '\0';
309 chan
->macropriority
= 0;
312 if (!dead
&& !strcasecmp(chan
->context
, fullmacro
)) {
313 /* If we're leaving the macro normally, restore original information */
314 chan
->priority
= oldpriority
;
315 ast_copy_string(chan
->context
, oldcontext
, sizeof(chan
->context
));
316 if (!(chan
->_softhangup
& AST_SOFTHANGUP_ASYNCGOTO
)) {
317 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
319 ast_copy_string(chan
->exten
, oldexten
, sizeof(chan
->exten
));
320 if ((offsets
= pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"))) {
321 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
322 normally if there is any problem */
323 if (sscanf(offsets
, "%d", &offset
) == 1) {
324 if (ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
+ offset
+ 1, chan
->cid
.cid_num
)) {
325 chan
->priority
+= offset
;
333 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", save_macro_offset
);
334 if (save_macro_offset
)
335 free(save_macro_offset
);
337 /* Unlock the macro */
339 ast_log(LOG_DEBUG
, "Unlocking macrolock for '%s'\n", fullmacro
);
340 if (ast_context_unlockmacro(fullmacro
)) {
341 ast_log(LOG_ERROR
, "Failed to unlock macro '%s' - that isn't good\n", fullmacro
);
346 ast_module_user_remove(u
);
351 static int macro_exec(struct ast_channel
*chan
, void *data
)
353 return _macro_exec(chan
, data
, 0);
356 static int macroexclusive_exec(struct ast_channel
*chan
, void *data
)
358 return _macro_exec(chan
, data
, 1);
361 static int macroif_exec(struct ast_channel
*chan
, void *data
)
363 char *expr
= NULL
, *label_a
= NULL
, *label_b
= NULL
;
365 struct ast_module_user
*u
;
367 u
= ast_module_user_add(chan
);
369 if (!(expr
= ast_strdupa(data
))) {
370 ast_module_user_remove(u
);
374 if ((label_a
= strchr(expr
, '?'))) {
377 if ((label_b
= strchr(label_a
, ':'))) {
381 if (pbx_checkcondition(expr
))
382 macro_exec(chan
, label_a
);
384 macro_exec(chan
, label_b
);
386 ast_log(LOG_WARNING
, "Invalid Syntax.\n");
388 ast_module_user_remove(u
);
393 static int macro_exit_exec(struct ast_channel
*chan
, void *data
)
395 return MACRO_EXIT_RESULT
;
398 static int unload_module(void)
402 res
= ast_unregister_application(if_app
);
403 res
|= ast_unregister_application(exit_app
);
404 res
|= ast_unregister_application(app
);
405 res
|= ast_unregister_application(exclusive_app
);
407 ast_module_user_hangup_all();
412 static int load_module(void)
416 res
= ast_register_application(exit_app
, macro_exit_exec
, exit_synopsis
, exit_descrip
);
417 res
|= ast_register_application(if_app
, macroif_exec
, if_synopsis
, if_descrip
);
418 res
|= ast_register_application(exclusive_app
, macroexclusive_exec
, exclusive_synopsis
, exclusive_descrip
);
419 res
|= ast_register_application(app
, macro_exec
, synopsis
, descrip
);
424 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Extension Macros");