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 "Extensions: While a macro is being executed, it becomes the current context.\n"
65 " This means that if a hangup occurs, for instance, that the macro\n"
66 " will be searched for an 'h' extension, NOT the context from which\n"
67 " the macro was called. So, make sure to define all appropriate\n"
68 " extensions in your macro! (you can use 'catch' in AEL) \n"
69 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
70 " contained within it via sub-engine), and a fixed per-thread\n"
71 " memory stack allowance, macros are limited to 7 levels\n"
72 " of nesting (macro calling macro calling macro, etc.); It\n"
73 " may be possible that stack-intensive applications in deeply nested macros\n"
74 " could cause asterisk to crash earlier than this limit.\n";
76 static char *if_descrip
=
77 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
78 "Executes macro defined in <macroname_a> if <expr> is true\n"
79 "(otherwise <macroname_b> if provided)\n"
80 "Arguments and return values as in application macro()\n";
82 static char *exclusive_descrip
=
83 " MacroExclusive(macroname|arg1|arg2...):\n"
84 "Executes macro defined in the context 'macro-macroname'\n"
85 "Only one call at a time may run the macro.\n"
86 "(we'll wait if another call is busy executing in the Macro)\n"
87 "Arguments and return values as in application Macro()\n";
89 static char *exit_descrip
=
91 "Causes the currently running macro to exit as if it had\n"
92 "ended normally by running out of priorities to execute.\n"
93 "If used outside a macro, will likely cause unexpected\n"
96 static char *app
= "Macro";
97 static char *if_app
= "MacroIf";
98 static char *exclusive_app
= "MacroExclusive";
99 static char *exit_app
= "MacroExit";
101 static char *synopsis
= "Macro Implementation";
102 static char *if_synopsis
= "Conditional Macro Implementation";
103 static char *exclusive_synopsis
= "Exclusive Macro Implementation";
104 static char *exit_synopsis
= "Exit From Macro";
107 static struct ast_exten
*find_matching_priority(struct ast_context
*c
, const char *exten
, int priority
, const char *callerid
)
110 struct ast_include
*i
;
111 struct ast_context
*c2
;
113 for (e
=ast_walk_context_extensions(c
, NULL
); e
; e
=ast_walk_context_extensions(c
, e
)) {
114 if (ast_extension_match(ast_get_extension_name(e
), exten
)) {
115 int needmatch
= ast_get_extension_matchcid(e
);
116 if ((needmatch
&& ast_extension_match(ast_get_extension_cidmatch(e
), callerid
)) ||
118 /* This is the matching extension we want */
120 for (p
=ast_walk_extension_priorities(e
, NULL
); p
; p
=ast_walk_extension_priorities(e
, p
)) {
121 if (priority
!= ast_get_extension_priority(p
))
129 /* No match; run through includes */
130 for (i
=ast_walk_context_includes(c
, NULL
); i
; i
=ast_walk_context_includes(c
, i
)) {
131 for (c2
=ast_walk_contexts(NULL
); c2
; c2
=ast_walk_contexts(c2
)) {
132 if (!strcmp(ast_get_context_name(c2
), ast_get_include_name(i
))) {
133 e
= find_matching_priority(c2
, exten
, priority
, callerid
);
142 static int _macro_exec(struct ast_channel
*chan
, void *data
, int exclusive
)
150 char runningapp
[80], runningdata
[1024];
151 char *oldargs
[MAX_ARGS
+ 1] = { NULL
, };
154 char oldexten
[256]="";
155 int oldpriority
, gosub_level
= 0;
156 char pc
[80], depthc
[12];
157 char oldcontext
[AST_MAX_CONTEXT
] = "";
158 const char *inhangupc
;
159 int offset
, depth
= 0, maxdepth
= 7;
160 int setmacrocontext
=0;
161 int autoloopflag
, dead
= 0, inhangup
= 0;
163 char *save_macro_exten
;
164 char *save_macro_context
;
165 char *save_macro_priority
;
166 char *save_macro_offset
;
167 struct ast_module_user
*u
;
169 if (ast_strlen_zero(data
)) {
170 ast_log(LOG_WARNING
, "Macro() requires arguments. See \"show application macro\" for help.\n");
174 u
= ast_module_user_add(chan
);
176 /* does the user want a deeper rabbit hole? */
177 s
= pbx_builtin_getvar_helper(chan
, "MACRO_RECURSION");
179 sscanf(s
, "%d", &maxdepth
);
181 /* Count how many levels deep the rabbit hole goes */
182 s
= pbx_builtin_getvar_helper(chan
, "MACRO_DEPTH");
184 sscanf(s
, "%d", &depth
);
185 /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
186 if (strcmp(chan
->exten
, "h") == 0)
187 pbx_builtin_setvar_helper(chan
, "MACRO_IN_HANGUP", "1");
188 inhangupc
= pbx_builtin_getvar_helper(chan
, "MACRO_IN_HANGUP");
189 if (!ast_strlen_zero(inhangupc
))
190 sscanf(inhangupc
, "%d", &inhangup
);
192 if (depth
>= maxdepth
) {
193 ast_log(LOG_ERROR
, "Macro(): possible infinite loop detected. Returning early.\n");
194 ast_module_user_remove(u
);
197 snprintf(depthc
, sizeof(depthc
), "%d", depth
+ 1);
198 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
200 tmp
= ast_strdupa(data
);
202 macro
= strsep(&rest
, "|");
203 if (ast_strlen_zero(macro
)) {
204 ast_log(LOG_WARNING
, "Invalid macro name specified\n");
205 ast_module_user_remove(u
);
209 snprintf(fullmacro
, sizeof(fullmacro
), "macro-%s", macro
);
210 if (!ast_exists_extension(chan
, fullmacro
, "s", 1, chan
->cid
.cid_num
)) {
211 if (!ast_context_find(fullmacro
))
212 ast_log(LOG_WARNING
, "No such context '%s' for macro '%s'\n", fullmacro
, macro
);
214 ast_log(LOG_WARNING
, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro
, macro
);
215 ast_module_user_remove(u
);
219 /* If we are to run the macro exclusively, take the mutex */
221 ast_log(LOG_DEBUG
, "Locking macrolock for '%s'\n", fullmacro
);
222 ast_autoservice_start(chan
);
223 if (ast_context_lockmacro(fullmacro
)) {
224 ast_log(LOG_WARNING
, "Failed to lock macro '%s' as in-use\n", fullmacro
);
225 ast_autoservice_stop(chan
);
226 ast_module_user_remove(u
);
230 ast_autoservice_stop(chan
);
234 oldpriority
= chan
->priority
;
235 ast_copy_string(oldexten
, chan
->exten
, sizeof(oldexten
));
236 ast_copy_string(oldcontext
, chan
->context
, sizeof(oldcontext
));
237 if (ast_strlen_zero(chan
->macrocontext
)) {
238 ast_copy_string(chan
->macrocontext
, chan
->context
, sizeof(chan
->macrocontext
));
239 ast_copy_string(chan
->macroexten
, chan
->exten
, sizeof(chan
->macroexten
));
240 chan
->macropriority
= chan
->priority
;
244 /* Save old macro variables */
245 save_macro_exten
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_EXTEN"));
246 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", oldexten
);
248 save_macro_context
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_CONTEXT"));
249 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", oldcontext
);
251 save_macro_priority
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_PRIORITY"));
252 snprintf(pc
, sizeof(pc
), "%d", oldpriority
);
253 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", pc
);
255 save_macro_offset
= ast_strdup(pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"));
256 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", NULL
);
258 /* Setup environment for new run */
259 chan
->exten
[0] = 's';
260 chan
->exten
[1] = '\0';
261 ast_copy_string(chan
->context
, fullmacro
, sizeof(chan
->context
));
264 while((cur
= strsep(&rest
, "|")) && (argc
< MAX_ARGS
)) {
266 /* Save copy of old arguments if we're overwriting some, otherwise
267 let them pass through to the other macro */
268 snprintf(varname
, sizeof(varname
), "ARG%d", argc
);
269 s
= pbx_builtin_getvar_helper(chan
, varname
);
271 oldargs
[argc
] = ast_strdup(s
);
272 pbx_builtin_setvar_helper(chan
, varname
, cur
);
275 autoloopflag
= ast_test_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
276 ast_set_flag(chan
, AST_FLAG_IN_AUTOLOOP
);
277 while(ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
)) {
278 struct ast_context
*c
;
280 runningapp
[0] = '\0';
281 runningdata
[0] = '\0';
283 /* What application will execute? */
284 if (ast_rdlock_contexts()) {
285 ast_log(LOG_WARNING
, "Failed to lock contexts list\n");
287 for (c
= ast_walk_contexts(NULL
), e
= NULL
; c
; c
= ast_walk_contexts(c
)) {
288 if (!strcmp(ast_get_context_name(c
), chan
->context
)) {
289 if (ast_lock_context(c
)) {
290 ast_log(LOG_WARNING
, "Unable to lock context?\n");
292 e
= find_matching_priority(c
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
);
293 if (e
) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
294 ast_copy_string(runningapp
, ast_get_extension_app(e
), sizeof(runningapp
));
295 ast_copy_string(runningdata
, ast_get_extension_app_data(e
), sizeof(runningdata
));
297 ast_unlock_context(c
);
303 ast_unlock_contexts();
305 /* Reset the macro depth, if it was changed in the last iteration */
306 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
308 if ((res
= ast_spawn_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
, chan
->cid
.cid_num
))) {
309 /* Something bad happened, or a hangup has been requested. */
310 if (((res
>= '0') && (res
<= '9')) || ((res
>= 'A') && (res
<= 'F')) ||
311 (res
== '*') || (res
== '#')) {
312 /* Just return result as to the previous application as if it had been dialed */
313 ast_log(LOG_DEBUG
, "Oooh, got something to jump out with ('%c')!\n", res
);
317 case MACRO_EXIT_RESULT
:
320 case AST_PBX_KEEPALIVE
:
322 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
);
323 else if (option_verbose
> 1)
324 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
);
329 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
);
330 else if (option_verbose
> 1)
331 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
);
337 ast_log(LOG_DEBUG
, "Executed application: %s\n", runningapp
);
339 if (!strcasecmp(runningapp
, "GOSUB")) {
341 ast_log(LOG_DEBUG
, "Incrementing gosub_level\n");
342 } else if (!strcasecmp(runningapp
, "GOSUBIF")) {
343 char tmp2
[1024] = "", *cond
, *app
, *app2
= tmp2
;
344 pbx_substitute_variables_helper(chan
, runningdata
, tmp2
, sizeof(tmp2
) - 1);
345 cond
= strsep(&app2
, "?");
346 app
= strsep(&app2
, ":");
347 if (pbx_checkcondition(cond
)) {
348 if (!ast_strlen_zero(app
)) {
350 ast_log(LOG_DEBUG
, "Incrementing gosub_level\n");
353 if (!ast_strlen_zero(app2
)) {
355 ast_log(LOG_DEBUG
, "Incrementing gosub_level\n");
358 } else if (!strcasecmp(runningapp
, "RETURN")) {
360 ast_log(LOG_DEBUG
, "Decrementing gosub_level\n");
361 } else if (!strcasecmp(runningapp
, "STACKPOP")) {
363 ast_log(LOG_DEBUG
, "Decrementing gosub_level\n");
364 } else if (!strncasecmp(runningapp
, "EXEC", 4)) {
365 /* Must evaluate args to find actual app */
366 char tmp2
[1024] = "", *tmp3
= NULL
;
367 pbx_substitute_variables_helper(chan
, runningdata
, tmp2
, sizeof(tmp2
) - 1);
368 if (!strcasecmp(runningapp
, "EXECIF")) {
369 tmp3
= strchr(tmp2
, '|');
372 if (!pbx_checkcondition(tmp2
))
378 ast_log(LOG_DEBUG
, "Last app: %s\n", tmp3
);
380 if (tmp3
&& !strncasecmp(tmp3
, "GOSUB", 5)) {
382 ast_log(LOG_DEBUG
, "Incrementing gosub_level\n");
383 } else if (tmp3
&& !strncasecmp(tmp3
, "RETURN", 6)) {
385 ast_log(LOG_DEBUG
, "Decrementing gosub_level\n");
386 } else if (tmp3
&& !strncasecmp(tmp3
, "STACKPOP", 8)) {
388 ast_log(LOG_DEBUG
, "Decrementing gosub_level\n");
392 if (gosub_level
== 0 && strcasecmp(chan
->context
, fullmacro
)) {
393 if (option_verbose
> 1)
394 ast_verbose(VERBOSE_PREFIX_2
"Channel '%s' jumping out of macro '%s'\n", chan
->name
, macro
);
398 /* don't stop executing extensions when we're in "h" */
399 if (chan
->_softhangup
&& !inhangup
) {
400 ast_log(LOG_DEBUG
, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
401 chan
->exten
, chan
->macroexten
, chan
->priority
);
407 /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
408 snprintf(depthc
, sizeof(depthc
), "%d", depth
);
410 pbx_builtin_setvar_helper(chan
, "MACRO_DEPTH", depthc
);
411 ast_set2_flag(chan
, autoloopflag
, AST_FLAG_IN_AUTOLOOP
);
414 for (x
= 1; x
< argc
; x
++) {
415 /* Restore old arguments and delete ours */
416 snprintf(varname
, sizeof(varname
), "ARG%d", x
);
419 pbx_builtin_setvar_helper(chan
, varname
, oldargs
[x
]);
422 pbx_builtin_setvar_helper(chan
, varname
, NULL
);
426 /* Restore macro variables */
428 pbx_builtin_setvar_helper(chan
, "MACRO_EXTEN", save_macro_exten
);
429 pbx_builtin_setvar_helper(chan
, "MACRO_CONTEXT", save_macro_context
);
430 pbx_builtin_setvar_helper(chan
, "MACRO_PRIORITY", save_macro_priority
);
432 if (save_macro_exten
)
433 free(save_macro_exten
);
434 if (save_macro_context
)
435 free(save_macro_context
);
436 if (save_macro_priority
)
437 free(save_macro_priority
);
439 if (!dead
&& setmacrocontext
) {
440 chan
->macrocontext
[0] = '\0';
441 chan
->macroexten
[0] = '\0';
442 chan
->macropriority
= 0;
445 if (!dead
&& !strcasecmp(chan
->context
, fullmacro
)) {
446 /* If we're leaving the macro normally, restore original information */
447 chan
->priority
= oldpriority
;
448 ast_copy_string(chan
->context
, oldcontext
, sizeof(chan
->context
));
449 if (!(chan
->_softhangup
& AST_SOFTHANGUP_ASYNCGOTO
)) {
450 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
452 ast_copy_string(chan
->exten
, oldexten
, sizeof(chan
->exten
));
453 if ((offsets
= pbx_builtin_getvar_helper(chan
, "MACRO_OFFSET"))) {
454 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
455 normally if there is any problem */
456 if (sscanf(offsets
, "%d", &offset
) == 1) {
457 if (ast_exists_extension(chan
, chan
->context
, chan
->exten
, chan
->priority
+ offset
+ 1, chan
->cid
.cid_num
)) {
458 chan
->priority
+= offset
;
466 pbx_builtin_setvar_helper(chan
, "MACRO_OFFSET", save_macro_offset
);
467 if (save_macro_offset
)
468 free(save_macro_offset
);
470 /* Unlock the macro */
472 ast_log(LOG_DEBUG
, "Unlocking macrolock for '%s'\n", fullmacro
);
473 if (ast_context_unlockmacro(fullmacro
)) {
474 ast_log(LOG_ERROR
, "Failed to unlock macro '%s' - that isn't good\n", fullmacro
);
479 ast_module_user_remove(u
);
484 static int macro_exec(struct ast_channel
*chan
, void *data
)
486 return _macro_exec(chan
, data
, 0);
489 static int macroexclusive_exec(struct ast_channel
*chan
, void *data
)
491 return _macro_exec(chan
, data
, 1);
494 static int macroif_exec(struct ast_channel
*chan
, void *data
)
496 char *expr
= NULL
, *label_a
= NULL
, *label_b
= NULL
;
498 struct ast_module_user
*u
;
500 u
= ast_module_user_add(chan
);
502 if (!(expr
= ast_strdupa(data
))) {
503 ast_module_user_remove(u
);
507 if ((label_a
= strchr(expr
, '?'))) {
510 if ((label_b
= strchr(label_a
, ':'))) {
514 if (pbx_checkcondition(expr
))
515 res
= macro_exec(chan
, label_a
);
517 res
= macro_exec(chan
, label_b
);
519 ast_log(LOG_WARNING
, "Invalid Syntax.\n");
521 ast_module_user_remove(u
);
526 static int macro_exit_exec(struct ast_channel
*chan
, void *data
)
528 return MACRO_EXIT_RESULT
;
531 static int unload_module(void)
535 res
= ast_unregister_application(if_app
);
536 res
|= ast_unregister_application(exit_app
);
537 res
|= ast_unregister_application(app
);
538 res
|= ast_unregister_application(exclusive_app
);
540 ast_module_user_hangup_all();
545 static int load_module(void)
549 res
= ast_register_application(exit_app
, macro_exit_exec
, exit_synopsis
, exit_descrip
);
550 res
|= ast_register_application(if_app
, macroif_exec
, if_synopsis
, if_descrip
);
551 res
|= ast_register_application(exclusive_app
, macroexclusive_exec
, exclusive_synopsis
, exclusive_descrip
);
552 res
|= ast_register_application(app
, macro_exec
, synopsis
, descrip
);
557 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Extension Macros");